DAO Design Problems
Jakob Jenkov |
The Data Access Object (DAO) pattern is now a widely accepted mechanism to abstract away the details of persistence in an application. In practice however, it itsn't always that easy to make your DAO's fully hide the underlying persistence layer. For instance, when a transaction span calls to multiple DAO's, where do you put the transaction demarcation code? This text will take a closer look at this and other problems and their solutions.
DAO's Defined
In short the purpose of DAO's is to hide the persistence mechanism of an application from its domain logic. In other words, to turn this sequence of calls
domain logic --> persistence mechanism
into this
domain logic --> DAO's --> persistence mechanism
The advantage of this abstraction is that you can change the persistence mechanism without affecting the domain logic. All you need to change is the DAO layer which, if designed properly, is a lot easier to do than changing all the domain logic. In fact you might be able to cleanly swap in a new DAO layer for your new database or alternate persistence mechanism.
You can also read more about the DAO pattern here:
http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
http://en.wikipedia.org/wiki/Data_Access_Object
DAO Connection Scoping
The first problem you run into when designing DAO classes is connection scoping. In other words, should each DAO method open and close its own connection (method scope)? Or should the DAO instance have a single internal connection that all methods use (instance scope)? Or should connections be shared between all DAO's used by the same thread for the same session (thread session scope)? The connection scopes are listed here and explained below:
Method Scope
A naive implementation of a DAO class using JDBC to target a relational database might look like below (at least I have designed DAO's like this in the past).
public class PersonDao{ protected DataSource dataSource = null; public PersonDao(DataSource dataSource){ this.dataSource = dataSource; } public Person readPerson(long personId){ Connection connection = this.dataSource.getConnection(); try{ Person person = ... return person; } finally { connection.close(); } } }
The PersonDao
shown here is a bit simplified but I am sure you get the point.
Notice how the readPerson()
method opens its own connection and closes it again
once it is done. The scope of the connection is the readPerson()
method.
Hence the term "method scope".
The first problem you run into with method connection scoped DAO's is when one DAO method needs to call another. In that case 2 connections are opened: One in the calling method, and one in the method called. This is a waste of connections. The two methods might as well use the same connection. This is also a problem if the two methods are to run in the same transaction. Then the two methods must use the same connection.
Instance Scope
To solve the problems of method connection scoped DAO's you can implement an instance connection scoped DAO instance. Here is a simple example:
public class PersonDao{ protected Connection connection = null; public PersonDao(Connection connection){ this.connection = connection; } public Person readPerson(long personId){ Person person = ... return person; } }
Notice how a connection is passed in the PersonDao
's constructor. All method
calls on this DAO instance will use that same connection. Hence the term "instance scope".
If a method calls another method in the same DAO instance they will
still use the same connection. The two method calls can also participate in the same
transaction.
The problem with instance scoped approach is that the DAO no longer knows how to obtain
the connection, nor when to close it. Obtaining the connection could be done within a
DAO factory or a dependency injection container like Butterfly Container,
to hide the connection creation from the domain logic code. You might also be able
to hide connection closing from the domain logic by adding a close()
method
to the DAO. But somewhere in the domain logic you would have to call that close()
method. Here is how it would look:
PersonDao personDao = daoFactory.createPersonDao(); Person person = personDao.readPerson(666); ... personDao.close();
While this approach does hide the database connection from the domain logic, it does leak the information that somewhere there is a resource (the connection) that needs to be closed. In addition, this approach also has another problem:
You may need more than one DAO and you may need them to use the same connection. For instance,
you may want to join their operations into the same transaction. In that case, how do you signal
to the DAO factory that the following n
DAO's created should use the same connection
rather than each their own?
Thread Scope
To solve the problems of instance scoped DAO's you can implement thread scoped DAO's. Or DAO factories that is. Here is how the interaction with a thread connection scoped DAO factory could look:
daoFactory.beginConnectionScope(); PersonDao personDao = daoFactory.createPersonDao(); VehicleDao vehicleDao = daoFactory.createVehicleDao(); ... daoFactory.endConnectionScope();
The method call daoFactory.beginConnectionScope()
marks the beginning of a connection
scope.
After this call all DAO's created will use the same connection if created by the same thread.
Hence the term "thread scope". Different threads calling the same DAO factory will create their own
scopes, so connections are not shared between threads.
The method call daoFactory.endConnectionScope()
ends the current connection scope and
closes the connection associated with the scope.
This way neither of the DAO's need to have
close()
methods. The DAO's no longer know anything about the scope of the connection.
Only the thread calling the DAO's does.
DAO Transaction Scoping
DAO transaction scoping looks similar to connection scoping. Here is a simple example:
daoFactory.beginConnectionScope(); daoFactory.beginTransaction(); PersonDao personDao = daoFactory.createPersonDao(); VehicleDao vehicleDao = daoFactory.createVehicleDao(); ... daoFactory.endTransaction(); daoFactory.endConnectionScope();
The methods beginTransaction()
and endTransaction()
mark the
beginning and end of the transaction. You can have more than one transaction within a connection
scope. The beginTransaction()
method will call connection.setAutoCommit(false)
for the connection associated with the transaction scope. The endTransaction()
method will attempt to commit the transaction and call setAutoCommit(false)
again.
If committing the transaction fails the transaction will be rolled back.
The transaction scoping mechanism shown above is also thread scoped. A transaction is associated with the current thread. All database operations carried out within the same transaction scope uses the same database connection and execute within the same transaction.
Thread scoped connection and transaction scoping hides most of the details of the persistence layer below the DAO layer from the domain logic layer. Two bits have still leaked though: The demarcation of the connection and transaction scopes.
Proper Exception Handling
The above example shown in the section above does not have proper exception handling. If an exception is thrown from one of the DAO method calls, the example does not properly end the connection or transaction scopes.
Here is a sketch of how the exception handling should look. Keep in mind though, that it's a sketch. You will have to modify it to suit your own purposes, error handling and reporting etc.
try{ daoFactory.beginConnectionScope(); try{ daoFactory.beginTransaction(); PersonDao personDao = daoFactory.createPersonDao(); VehicleDao vehicleDao = daoFactory.createVehicleDao(); daoFactory.endTransaction(); } catch(Exception e){ daoFactory.abortTransaction(e); } } finally { daoFactory.endConnectionScope(); }
Notice the new method call in the catch-block of the transaction scope:
daoFactory.abortTransaction(e)
. This method call rolls the
transaction back if an exception is thrown from any of the dao methods
or from endTransaction()
.
DAO Manager
The text DAO Manager explores further solutions to the connection and transaction scoping problems.
Tweet | |
Jakob Jenkov |