The other day, on my current project, I had a situation where I needed to change some transaction parameters on a DAO to start a new transaction whenever methods on that DAO were invoked.
The application architecture is based on a generic DAO interface with standard CRUD operations implemented by a GenericDao. All DAOs have defined their own interface extending the generic DAO and likewise the implementation is extending the GenericDao:
The goal was to have all methods, defined on either the GenericDao or the (in this example) UserDaoImpl , to start a new transaction when invoked.
The GenericDao is annotated with Springs @Transactional annotation with default parameters (Propagation.REQUIRED) which will be inherited to all extending DAOs.
My first attempt to achieve my goal was to annotate the UserDaoImpl with @Transactional(propagation=Propagation.REQUIRED_NEW). Well, this did not have the effect I had hoped for. Now all methods declared on the UserDaoImpl will start a new transaction, but methods on the GenericDao will not and if I removed the annotation on the GenericDao and defined it on all DAOs then invoked methods on the GenericDao will not be transactional at all.
This forced me to investigate how Spring handles the @Transactional annotation and it all comes down to how the method getTransactionAttribute(Method method, Class targetClass) on the class AbstractFallbackTransactionAttributeSource works.
I'll try to explain:
- the method will be invoked where parameter method is Dao#update and targetClass is UserDaoImpl.
- The method is from the Dao interface so firstly the actual implementation will be found = GenericDao#update.
- This will be the first place to look for the annotation - on the method on the declaring class. Not the case here.
- If not on the method then look if the declaring class has defined the annotation. Yep - in this case the GenericDao has defined the annotation for default behavior for all extending DAOs, which means that a new transaction will not be started for invoked methods defined on GenericDao. If I override the method in UserDaoImpl and simply call super I would get the expected behavior, but I do not find this as a convenient solution.
- If the target class is a interface and the annotation is not yet present then the interface method and class is searched.
In the above list this could be number 2.5 just after searching on the method then look at the targetClass before the declaring class... (if they differ at all...) What do you think? Is this a reasonable suggestion?
Well, back to my problem. My solution was of course keep the @Transactional on the GenericDao and remove it from UserDaoImpl and then create a new service in the service layer annotated with @Transactional(propagation=Propagation.REQUIRES_NEW) and actually I am more happy with this solution as it keeps the Dao operations simple and atomic.