You're right to be thinking carefully about enforcing proper separation of concerns in a layered architecture like this. The challenge you're facing is common when trying to implement transactions that span multiple DAOs or entities.
I would approach it this way: transaction coordination should happen in the service layer, not in the DAO.
Why?
The service layer is responsible for business logic and orchestration of multiple DAO operations.
DAOs should be limited to CRUD operations on their own specific entity or table.
Managing a transaction is not a low-level persistence detail, but rather a unit of work defined by business requirements.
Suggested approach:
Use the service layer to manage the transaction boundaries and delegate individual operations to each DAO. For example:
In your AccountService, start a transaction manually (if using JDBC) or via a transaction manager (if using Spring or similar).
Call AccountDAO.insert(account).
Call CustomerDAO.updateHasBankAccount(customerId, true).
Commit the transaction if both succeed, or roll it back on failure.
Here is a simplified example using plain JDBC:
public class AccountService {
private final DataSource dataSource;
private final AccountDAO accountDAO;
private final CustomerDAO customerDAO;
public AccountService(DataSource dataSource, AccountDAO accountDAO, CustomerDAO customerDAO) {
this.dataSource = dataSource;
this.accountDAO = accountDAO;
this.customerDAO = customerDAO;
}
public void createAccount(Account account, long customerId) throws SQLException {
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
accountDAO.insert(account, conn);
customerDAO.setHasBankAccount(customerId, true, conn);
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
}
This requires that your DAO methods accept a Connection parameter, for example:
void insert(Account account, Connection conn);
void setHasBankAccount(long customerId, boolean flag, Connection conn);
This keeps your DAO classes focused only on their specific domain logic, while the service layer handles the coordination of multiple actions within a transactional context.
If you’re using a framework like Spring, you can simplify this further by annotating the service method with @Transactional, and Spring will handle transaction boundaries for you.
In short, the transaction is a business concern, and the service layer feels like the right place to define that scope to me.