Home > database >  Spring @transactional with hibernate issue - not valid without an active transaction
Spring @transactional with hibernate issue - not valid without an active transaction

Time:10-23

Preamble - using Spring

I am confused as to the purpose of the spring @Transactional annotation. I thought from a few blog posts I've read that it would allow me to simplify transaction management and just write this, and it would handle connection/commit/rollback automagically:

public class DaoImpl implements Dao {
   @Autowired
   private SessionFactory sessionFactory;

    @Transactional
    public void saveOrUpdateOne(final AdditionalDataItem item) {
        Session session = sessionFactory.getCurrentSession();
            session.saveOrUpdate(p_item);
    }
}

However this gives me an exception: " Calling method 'saveOrUpdate' is not valid without an active transaction"

If I instead change the save method to this, it all works - so my question is, what is @Transactional doing?

@Override
@Transactional
public void saveOrUpdateOne(final AdditionalDataItem p_item) {
    Session session = null;
    Transaction trans = null;
    try {
        session = sessionFactory.getCurrentSession();
        trans = session.beginTransaction();
        TransactionStatus status = trans.getStatus();
        session.saveOrUpdate(p_item);
        trans.commit();
    } catch (Exception e) {
        LOGGER.error("Exception saving data: {}", e.getMessage());
        if (trans != null) {
            try {
                trans.rollback();
            } catch (RuntimeException rbe) {
                LOGGER.error("Couldn’t roll back transaction", rbe);
            }
        }
    } finally {
        if (session != null && session.isOpen()) {
            try {
                session.close();
            } catch (HibernateException ne) {
                LOGGER.error("Couldn’t close session", ne);
            }
        }
    }
}

For reference, I'm using Java 11 with Spring Framework 5.3.7 and hibernate 5.5.7 and have appropriate dao, session factory and tx manager beans:

    <bean id="sessionFactory" 
      class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="${sessionFactory.datasource}" />
    <property name="configLocation" value="${sessionFactory.configLocation}" />
</bean>

<bean id="txManager" 
  class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="Dao" class="com.xxxxx.dao.DaoImpl">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

CodePudding user response:

@Transactional is used if you want to update 2 tables if one of them failed the other one will rollback automatic you can use it above method and you can call save or update using bean of a Repository class

CodePudding user response:

You do not enable @Transactional yet and so it gives you the error 'not valid without an active transaction' .

To enable it , you have to use @EnableTransactionManagement or add <tx:annotation-driven/> in case you are using XML configuration . It basically does the following for you (source) :

@EnableTransactionManagement and <tx:annotation-driven/> are responsible for registering the necessary Spring components that power annotation-driven transaction management, such as the TransactionInterceptor and the proxy- or AspectJ-based advice that weaves the interceptor into the call stack when JdbcFooRepository's @Transactional methods are invoked.

Your working example works because you manually manage the transaction by yourself .It is nothing to do with @Transactional since you never enable it.

Take the working codes as an example , what @Transactional does for you is that you no longer need to manually write the following transaction codes as all of them will be encapsulated in the TransactionInterceptor and execute around your @Transactional method based on AOP :

public Object invoke() {
    Session session = null;
    Transaction trans = null;
    try {
        session = sessionFactory.getCurrentSession();
        trans = session.beginTransaction();
        TransactionStatus status = trans.getStatus();
             
        /***********************************************/
         Here it will invoke your @Transactional Method
        /************************************************/

        trans.commit();
    } catch (Exception e) {
        LOGGER.error("Exception saving data: {}", e.getMessage());
        if (trans != null) {
            try {
                trans.rollback();
            } catch (RuntimeException rbe) {
                LOGGER.error("Couldn’t roll back transaction", rbe);
            }
        }
    } finally {
        if (session != null && session.isOpen()) {
            try {
                session.close();
            } catch (HibernateException ne) {
                LOGGER.error("Couldn’t close session", ne);
            }
        }
    }
}

So you can see that your @Transactional method will become very clean after removing these "ceremony" codes.

  • Related