9

I do have a generics-based DAO class, which is base for all other DAO classes in my project and contains common functionality:

public class Dao<E> {

    private SessionFactory factory;

    public void setSessionFactory(SessionFactory factory) {
        this.factory = factory;
    }

    public E get(int id) {
        // ....
    }

    public void save(E entity) {
        // ...
    }

    public void delete(E entity) {
        // ...
    }

}

In my project I am using multiple datasources, pointing to different databases, therefore, I do have multiple session and transaction managers:

<bean id="factory1" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="source1" />
</bean>

<bean id="manager1" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="factory1" />
</bean>

<bean id="factory2" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="source2" />
</bean>

<bean id="manager2" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="factory2" />
</bean>

Now I want to create couple of DAOs, operating on different databases:

@Repository
@Transactional("manager1")
public class Dao1 extends Dao<Entity1> {

    @Overrides
    @Autowired
    @Qualifier("factory1")
    public void setSessionFactory(SessionFactory factory) {
        super.setSessionFactory(factory);
    }

}

@Repository
@Transactional("manager2")
public class Dao1 extends Dao<Entity2> {

    @Overrides
    @Autowired
    @Qualifier("factory2")
    public void setSessionFactory(SessionFactory factory) {
        super.setSessionFactory(factory);
    }

}

but the problem is, that all public methods from Dao aren't covered by @Transactional from a child classes, so no transaction management occurs. The only option what I can think of is to override the parent class methods, so they are defined in inherited classes and therefore are taken care by @Transactional:

@Repository
@Transactional("manager1")
public class Dao1 extends Dao<Entity1> {

    @Overrides
    @Autowired
    @Qualifier("factory1")
    public void setSessionFactory(SessionFactory factory) {
        super.setSessionFactory(factory);
    }

    @Overrides
    public Entity1 get(int id) {
        return super.get(id);
    }

    @Overrides
    public void save(Entity1 entity) {
        super.save(entity);
    }

    @Overrides
    public void delete(Entity1 entity) {
        super.delete(entity);
    }

}

But then I would need to do this in every DAO class, and code would be everywhere the same...

Is there a better way how to share common functionality across all classes and still have all benefits of declarative transaction management?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Laimoncijus
  • 8,615
  • 10
  • 58
  • 81
  • you know that @Transactional can be applied to methods. So Dao methods can be anottated ? Also transaction belongs in Service layer better compared to Dao layer. – Subin Sebastian Jan 04 '13 at 07:46
  • AFAIK, if you annotate the class, every method, including the inherited ones, are transactional. That said, transaction demarcation should not be at the DAO level anyway. – JB Nizet Jan 04 '13 at 07:50
  • 1
    @Subin: if I am not mistaken - if I start annotating methods of `Dao` - they will still require me to specify which transaction manager I wan to use - and at that place it is not clear, as there is no default one... Transaction manager is inherited class specific, as only then is clear, which of them is needed by particular DAO... – Laimoncijus Jan 04 '13 at 07:50
  • as @JBNizet said I too think inherited methods will be transactional . Did you test to see that those methods are not transactional – Subin Sebastian Jan 04 '13 at 07:52
  • Only way to pass something from child class to parent is using abstract. Have a abstract method in DAO may be protected String getTmName() and implement itin child classes. But I dont know how to use that info dynamically in @Transactional annotation – Subin Sebastian Jan 04 '13 at 07:57
  • Unless I did something completely wrong - the example code above isn't working and complaining about not allowed to use non-transactional sessions unless I override parent class methods. Then it works properly. So I would assume - that inherited methods aren't transactional – Laimoncijus Jan 04 '13 at 07:59
  • 1
    Transactional DOES belong at the DAO layer. Spring Data JPA (http://projects.spring.io/spring-data-jpa/) puts Transactional on all of it's repositories. What people are referring to here is who starts the transaction, which is controlled by Transactional's "propagation" property. Service layer should have it set to something like REQUIRED (which will create a new transaction if onen doesn't exist) and then DAO's should use PROPAGATION_MANDATORY (which will throw an exception if an transaction doesn't exist - should have been started by the Service layer). – SergeyB Feb 12 '14 at 22:43
  • 1
    Transacional should be at the service layer and not the repository layer, the reason is that the Dao has no no way of knowing what business logic it's participating in. The same method dao.insertEntity1 might run on a single transaction, or as part of multi entity insert and there is no way for the Dao to determine this. The current implementation is almost certainly not what you want – Angular University Feb 13 '14 at 13:26

3 Answers3

2

In case you don't mind the super class would be @Transactional as well, you should put the annotation on the super DAO. In case you do mind, I suggest creating one extending class TransactionalDao that will extend the main DAO and be @Transactional and all the DAOs that should be @Transactional as well will extend it:

public class Dao<E> {

    private SessionFactory factory;

    public void setSessionFactory(SessionFactory factory) {
        this.factory = factory;
    }

    public E get(int id) {
        // ....
    }

    public void save(E entity) {
        // ...
    }

    public void delete(E entity) {
        // ...
    }

}

@Transactional
public class TransactionalDao<E> extends Dao<E>{

    private SessionFactory factory;

    public void setSessionFactory(SessionFactory factory) {
        this.factory = factory;
    }

    public E get(int id) {
        return super.get(id);
    }

    public void save(E entity) {
        super.save(entity);
    }

    public void delete(E entity) {
        super.delete(entity);
    }
}

And now the extending class would look like that:

@Repository
@Transactional("manager1") // You'd probably still want the @Transactional for new methods
public class Dao1 extends TransactionalDao<Entity1> {

    @Overrides
    @Autowired
    @Qualifier("factory1")
    public void setSessionFactory(SessionFactory factory) {
        super.setSessionFactory(factory);
    }   
}

This way you only have to do this super wrapping thing only once.

Avi
  • 21,182
  • 26
  • 82
  • 121
1

Have you tried putting @Transaction on parent DAO class's methods?

public class Dao<E> {

    private SessionFactory factory;

    public void setSessionFactory(SessionFactory factory) {
        this.factory = factory;
    }
    @Transactional(readOnly = true)
    public E get(int id) {
        // ....
    }
    @Transactional
    public void save(E entity) {
        // ...
    }
    @Transactional
    public void delete(E entity) {
        // ...
    }
}

That way when you call save on DAO1, it will pick up class-level @Transaction from the sub class (which will specify which TX manager to use) and then pick up method-level @Transactional from the parent's save method.

SergeyB
  • 9,478
  • 4
  • 33
  • 47
-2

Instead of adding @Transactional annotation everywhere, you should use AOP based Transaction Management like -

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

  <tx:advice id="txAdvice" transaction-manager="txManager">  
  <tx:attributes>  
    <tx:method name="get*" read-only="true"/>  
    <tx:method name="*"/>
  </tx:attributes>
  </tx:advice>

It's good practice to use transaction on service layer.

Avinash T.
  • 2,280
  • 2
  • 16
  • 23
  • Err, Transactional *IS* AOP based transaction management. And you're linking to obsolete documentation. – JB Nizet Jan 04 '13 at 07:54