I couldn’t let this year end without fulfilling my promise, so i had to publish this last episode of the Java – Data Access Objects – Considerations series.
We will be talking about a simple problem: when each method of a DAO opens a new connection to the database in order to fetch or modify data, a new concern pops up: how to handle transactions? How do we group DAO operations in a single ACID transaction? Being a Javatic article, we will be assuming JDBC as the API used to interact with the database.
Transacting on a JDBC connection
In JDBC, transactions are bound to connections. In order to group multiple DML statements in a transaction, we must disable the auto-commit on the connection object we want to transact on and then we can start throwing DML statement at it and at the end we get to commit or rollback the transaction.
If you take a pause and go back to our problem statements, upstairs, you might find out that we can already answer that question. We just found the answer. The answer is: the different DAO operations must share the connection object in order to have their DML statements grouped in a single transactional unit which will be later committed or rolled back.
It’s important to make sure that the connection object is shared among the DAO operations only when we are in the middle of a transaction, otherwise, each operation should open it’s own connection.
This means that we need a way to check if we are in the middle of a transaction or not, so that we can decide to use the shared connection object or to create a new connection.
We will put the getConnection() method in our AbstractDAO, so that specific DAO classes are not aware (abstraction) of the existence of a shared connection and all that mambo-djambo.
The CustomerDAO example
See below the code of the CustomerDAO, which extends the AbstractDAO, that we will soon be discussing.
public class CustomerDAO extends AbstractDAO {
public void updateEmail(String personId, String email){
//TODO: Validate arguments
Connection connection = this.getConnection();
try{
//Execute the update operation here
}finally{
this.close(connection);
}
}
public void updateMobile(String personId, String mobile){
//TODO: Validate arguments
Connection connection = this.getConnection();
try{
//Execute the update operation here
}finally{
this.close(connection);
}
}
}
Both methods close and getConnection belong to the AbstractDAO class.
Abstract DAO
This would be the parent class of all our DAO classes. Providing the logic required to make the DAO objects transaction capable.
public abstract class AbstractDAO {
private static ThreadLocal<Connection> sharedConnection = new ThreadLocal<>();
//Returns the shared connection, if exists,
//otherwise, creates a new one
protected Connection getConnection(){
Optional<Connection> optional = getSharedConnection();
if(optional.isPresent())
return optional.get();
else return openNewConnection();
}
//Keep the connection open in case it's shared
//Close the connection if not shared
protected close(Connection connection){
if(getSharedConnection().isPresent())
return;
connection.close();
}
private Optional<Connection> getSharedConnection(){
return Optional.ofNullable(sharedConnection.get());
}
private static Connection openNewConnection(){
//Open a new database connection here
}
//Starts a new transaction (on the current Thread).
//Creates a shared connection
//with auto-commit disabled
public static void beginTransaction(){
Connection connection = openNewConnection();
connection.setAutoCommit(false);
sharedConnection.set(connection);
}
//Commits the active-transaction
//Closes and clears the shared connection
public static void commitTransaction(){
Optional<Connection> optional = getSharedConnection();
if(!optional.isPresent())
throw new IllegalStateException("No active transaction");
Connection connection = optional.get();
try{
connection.commit();
}finally{
connection.close(); //close the shared connection
sharedConnection.remove();//remove the shared connection;
}
}
//Rolls back the active-transaction
//Closes and clears the shared connection
public static void rollbackTransaction(){
Optional<Connection> optional = getSharedConnection();
if(!optional.isPresent())
throw new IllegalStateException("No active transaction");
Connection connection = optional.get();
try{
connection.rollback();
}finally{
connection.close(); //close
sharedConnection.remove(); //remove
}
}
}
You might have noticed that the AbstractDAO class has a few static methods. That methods are intended to be invoked from the transaction-demarcation layer, which is the services-layer.
In a nutshell, we could for example, have a service method that updates customer information as below:
public void updateCustomerDetails(String id, String email, String mobile){
CustomerDAO dao = new CustomerDAO();
//Start a new transaction
AbstractDAO.beginTransaction();
try{
//Update the details
dao.updateEmail(id,email);
dao.updateMobile(id,mobile);
//Commit the transaction
AbstractDAO.commitTransaction();
}catch(RuntimeException ex){
//Rollback the transaction
AbstractDAO.rollbackTransaction();
throw ex;
}
}
We made sure that all DAO operations executed in the same transaction will share the connection, while operations executed out of a transaction won’t share the connection object.
We also made sure that multiple Threads executing transactions wont be sharing the connection object, meaning, each Thread will have its own connection object, thanks to the ThreadLocal variable. In this model, one Thread can only execute one transaction per time, no chained transactions are supported. Despite the fact that it works, it’s worth asking if it’s the best way to deal with transactions across DAOs.
Is it the best way?
While what we have done in this article is a good way to explain how transaction-demarcation works behind the scenes, it’s probably not the best way to deal with transactions across DAOs in a J2EE application. But what would the real cons be? Here they follow:
- Its coupled to JDBC and only supports transactions through direct JDBC connections.
- No JPA support – coding is required
- No support for transactions across multiple databases/resources.
- No child transactions support
While the cons list above might not seem to be a big deal, I have the duty to tell you about two technologies that are out there that can make you re-think about writing your own transaction-management library: JTA and EJB (container managed transaction).
JTA enables distributed transactions across multiple resources. Such resources can be jdbc enabled databases, message driven beans, session beans, resource-adapters or custom resources. This literally means that anything can participate in a transaction, as long as such thing implements a specific JTA contract.
JTA is just an API that defines contracts. You need more than the API itself in order to use JTA, you need a JTA transaction-manager. Any JAVA EE certified server comes with a built-in transaction-manager and you don’t have to worry about it. If you intend to use JTA in a SE environment, you will need to include a transaction manager in your application.
What about EJB? EJBs are divided in : session beans and message-driven beans (MDBs).
MDBs are EJBs that interact with message queue resources, while session beans are meant to be used by client applications or components to access the system’s functionalities/services.
The special thing about session beans is that simply by annotating a method with the @Transactional annotation, you will be telling the container to begin a transaction every-time that method is invoked and the transaction will be automatically rolled-back if any exception is thrown by the method. You can also annotate the methods to join a transaction in case it’s already active or to fail when invoked in the middle of a transaction. You have a considerable set of options for transactions demarcation.
This is why EJB session beans are the perfect example of Container Managed Transactions (CMT). The EJB API is built on top of both the CDI and the JTA APIs. JTA is what an EJB/J2EE container uses for transactions. The CDI API is what allows the @Transactional annotation to function, enabling interception capabilities, but these details are simply way too out of our scope here.
This was me giving you an idea of these two JavaEE APIs that might help you save a few code lines and years of maintenance, unless of-course you know what you are doing.
This is it. We are done here. Remember that in order for this To be a consummated discussion, you must talk back to me, so please, leave a comment, ask, make an observation, criticize, give an example, contribute.
It’s always my pleasure.
#TheLastOf2017







Comentários Recentes