Issue
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>
Solution
It is because you do not enable @Transactional
yet and also not begin the transaction when calling session.saveOrUpdate()
, so it gives you the error 'not valid without an active transaction' .
To enable @Transactional
, 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.
Answered By - Ken Chan
Answer Checked By - Candace Johnson (JavaFixing Volunteer)