Mybatis sqlsession unable to rollback - java

I'm working in Spring project, using mybatis 3 and oracle 11g.
I tried to rollback transactions when errors happen. However, rollback seems not to be working.
Source code bellow:
ApplicationContext.xml
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Service
int changeLimitSrcAcc(String src_acctno,String dest_acctno, String amt);
ServiceImpl
#Override
public int changeLimitSrcAcc(String src_acctno, String dest_acctno,
String amt) {
int result = 0;
SqlSessionFactory sqlMapper = MyBatisService.getSessionFactory();
SqlSession sqlSession = sqlMapper.openSession();
CustomerAccountMapper mapper = sqlSession
.getMapper(CustomerAccountMapper.class);
try {
int result1 = mapper.changeLimitSrcAcc(src_acctno, amt);
int result2 = mapper.changeLimitDescAcc(dest_acctno, amt);
if (result1 != 1 || result2 != 1)
throw new Exception("Error happened");
else result = 1;
sqlSession.commit();
} catch (Exception e) {
System.out.println(e.getMessage());
sqlSession.rollback();
} finally {
sqlSession.close();
}
return result;
}
I also tried to rollback a single transaction but it still committed.
I read on Mybatis home page and it said #transaction annotations doesn't need. I also put the annotations and nothing happened.
Any solutions?
Thanks.

You have 2 ways of managing transactions:
Declarative Transaction Management (#Transactional annotation)
Programmatic Transaction Management (manually do commit/rollback)
You try to do both. Decide - either let Spring manage transactions - configure myBatis with Spring config - more simple way or manually create DefaultTransactionDefinition etc.

Related

Spring #transactional with hibernate issue - not valid without an active transaction

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>
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.
#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

Handling Hibernate Transactions

Currently I have this code duplicated in each one of my Controller methods:
Transaction transaction = HibernateUtil.getSessionFactory().getCurrentSession().getTransaction();
if (!HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().isActive()) {
transaction.begin();
}
Is this the correct way or is there a better way of doing this, perhaps in a separate class that I can reference? If so, how? Every time I've tried to put it in a separate class and reference it from other classes, it failed.
edit: I'm trying to use as few external libraries as possible. I wouldn't use Hibernate if Java had an ORM/JPA implementation built into the JDK
I've run into this myself many times. Ordinarily my first recommendation would be Spring transaction management, however I understand you are trying to limit the number of third party libraries you are using.
Since you're using a static API in your HibernateUtil class, you may find it helpful to consolidate your logic in a method, and putting the 'what you want to do in a transaction' code (which varies controller to controller) in a callback.
First, define an interface to describe each controller's inTransaction behavior:
public interface TransactionCallback {
void doInTransaction();
}
Now, create a static method in your HibernateUtil class to handle beginning, committing, and if necessary rolling back your transactions:
public class HibernateUtil {
public static void inTransaction(TransactionCallback tc) {
Transaction transaction = HibernateUtil.getSessionFactory().getCurrentSession().getTransaction();
if (!HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().isActive()) {
transaction.begin();
try {
tc.doInTransaction();
transaction.commit();
} catch (Exception e) {
transaction.rollback();
}
}
}
}
In your controller, you'd use your new method with an anonymous inner class:
....
HibernateUtil.inTransaction(new TransactionCallback() {
void doInTransaction() {
// do stuff for this controller
}
});
....
This approach should at least take care of the duplication you'd like to eliminate, and there's plenty of room for extending it to handle particular exceptions, etc.
You have to close hibernate transaction after each transaction (e.g. Controller request).
In this case you will not need
if (!HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().isActive())
and you WILL need to call .close() each time after request.
It is better to use code like:
class Controller {
//...
commonActionMethod() {
begin transaction
specificActionMethod();
close transaction
}
And childs of this Controller class should implement specificActionMethod().
Code is clean. Transactions are safe. No third-party libs required.
You can Very well use JDK Proxies to implement your own AOP .
Ref : Link1 Link2
Have Service Layer to intract with DAO framework such as Hibernate and so. So that your controller is just controll the flow and your service can implement business.
Have SeviceLocator / FactoryPattern to get hold of your Service instances ( In other words return proxies instead of Actual instance).
Define your own Annotations and identify your methods required transaction or not. if required handle transaction around your method call in your proxy handler.
In this way you don't need to depend on any library other than JDK. and you can turn off or on transaction just by having Annotations.
If you start manage the instances ( services) you can lot of magics with combination of FactoryPattern + JDK Proxies ( Actual Interfaces) + AOP Concepts.
you can create separate class for connection.
public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
#SuppressWarnings("deprecation")
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from Annotation
return new AnnotationConfiguration().configure().buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
On Server Side you can write :-
Session session=null;
Transaction tx=null;
try {
session =HibernateUtil.getSessionFactory().openSession();
tx=session.beginTransaction();
} catch (HibernateException e) {
e.printStackTrace();
}finally
{
session.close();
}
Avoiding the use of additional, external libraries, you may wish to supply an interceptor that implements that standard J2EE servlet Filter interface. Such implementation is sometimes referred to as the Open Session in View pattern. I cite the following from this page:
When an HTTP request has to be handled, a new Session and database transaction will begin. Right before the response is send to the client, and after all the work has been done, the transaction will be committed, and the Session will be closed.
If you are using spring in your project. I will suggest to use the TX using spring AOP, In that you just have to specify the pointcuts for your transactions. The Spring AOP TX will taken care of begin and commit the transaction on basis of your point cut and could also roll-back the TX in case of exception occurred. Please go through the link of example - here
package com.project.stackoverflow;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class HibernateUtil {
private static final ThreadLocal threadSession = new ThreadLocal();
private static SessionFactory sessionFactory;
/**
* A public method to get the Session.
*
* #return Session
*/
public static Session getSession() {
Session session = (Session) threadSession.get();
// Open a Session, if this thread has none yet
if ((null == session) || !session.isOpen()) {
logger.info("Null Session");
session = sessionFactory.openSession();
logger.info("Session Opened");
threadSession.set(session);
}
return session;
}
public static void closeSession() {
Session session = (Session) threadSession.get();
// Open a Session, if this thread has none yet
if (null != session) {
session.close();
session = null;
threadSession.set(null);
}
}
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
logger.info("Inside set session Factory");
this.sessionFactory = sessionFactory;
logger.info("After set session Factory");
}
public static void save(Object obj) {
getSession().save(obj);
getSession().flush();
}
public static void saveOrUpdate(Object obj) {
getSession().saveOrUpdate(obj);
getSession().flush();
}
public static void batchUpdate(Object obj) {
getSession().saveOrUpdate(obj);
getSession().flush();
}
public static void update(Object obj) {
getSession().update(obj);
getSession().flush();
}
public static void delete(Object obj) {
getSession().delete(obj);
getSession().flush();
}
}
You can probably go for this solution. I have made a separate JavaClass for Hibernate Instantiation and use. You can get the session from here itself which may suffice your need. Hope it helps :)
I used this technique.
My Servlet context is like this:
<beans:bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.databaseurl}" p:username="${jdbc.username}" p:password="${jdbc.password}" />
<beans:bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<beans:property name="dataSource" ref="dataSource" />
<beans:property name="configLocation">
<beans:value>classpath:hibernate.cfg.xml</beans:value>
</beans:property>
<beans:property name="configurationClass">
<beans:value>org.hibernate.cfg.AnnotationConfiguration</beans:value>
</beans:property>
<beans:property name="hibernateProperties">
<beans:props>
<beans:prop key="hibernate.dialect">${jdbc.dialect}</beans:prop>
<beans:prop key="hibernate.show_sql">true</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
<beans:bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<beans:property name="sessionFactory" ref="sessionFactory" />
</beans:bean>
<tx:annotation-driven transaction-manager="transactionManager" />
Then you can simply use
#Autowired
private SessionFactory sessionFactory;
Whenever I want to use a session or do any operations I simply do it like this:
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
session.save(userAccount);
transaction.commit();
session.close();
I think it will help.
If you have application server like glassfish, it has imbended eclipselink JPA/ORM implementation and you can manage transaction using standart JEE annotations.

JOOQ & transactions

I've been reading about transactions & jooq but I struggle to see how to implement it in practice.
Let's say I provide JOOQ with a custom ConnectionProvider which happens to use a connection pool with autocommit set to false.
The implementation is roughly:
#Override public Connection acquire() throws DataAccessException {
return pool.getConnection();
}
#Override public void release(Connection connection) throws DataAccessException {
connection.commit();
connection.close();
}
How would I go about wrapping two jooq queries into a single transaction?
It is easy with the DefaultConnectionProvider because there's only one connection - but with a pool I'm not sure how to go about it.
jOOQ 3.4 Transaction API
With jOOQ 3.4, a transaction API has been added to abstract over JDBC, Spring, or JTA transaction managers. This API can be used with Java 8 as such:
DSL.using(configuration)
.transaction(ctx -> {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
});
Or with pre-Java 8 syntax
DSL.using(configuration)
.transaction(new TransactionRunnable() {
#Override
public void run(Configuration ctx) {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
}
});
The idea is that the lambda expression (or anonymous class) form the transactional code, which:
Commits upon normal completion
Rolls back upon exception
The org.jooq.TransactionProvider SPI can be used to override the default behaviour, which implements nestable transactions via JDBC using Savepoints.
A Spring example
The current documentation shows an example when using Spring for transaction handling:
http://www.jooq.org/doc/latest/manual/getting-started/tutorials/jooq-with-spring/
This example essentially boils down to using a Spring TransactionAwareDataSourceProxy
<!-- Using Apache DBCP as a connection pooling library.
Replace this with your preferred DataSource implementation -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
init-method="createDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/maven-test" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<!-- Using Spring JDBC for transaction management -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionAwareDataSource"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg ref="dataSource" />
</bean>
<!-- Bridging Spring JDBC data sources to jOOQ's ConnectionProvider -->
<bean class="org.jooq.impl.DataSourceConnectionProvider"
name="connectionProvider">
<constructor-arg ref="transactionAwareDataSource" />
</bean>
A running example is available from GitHub here:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-example
A Spring and Guice example
Although I personally wouldn't recommend it, some users have had success replacing a part of Spring's DI by Guice and handle transactions with Guice. There is also an integration-tested running example on GitHub for this use-case:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-guice-example
This is probably not the best way but it seems to work. The caveat is that it is not the release but the commit method which closes the connection and returns it to the pool, which is quite confusing and could lead to issues if some code "forgets" to commit...
So the client code looks like:
final PostgresConnectionProvider postgres =
new PostgresConnectionProvider("localhost", 5432, params.getDbName(), params.getUser(), params.getPass())
private static DSLContext sql = DSL.using(postgres, SQLDialect.POSTGRES, settings);
//execute some statements here
sql.execute(...);
//and don't forget to commit or the connection will not be returned to the pool
PostgresConnectionProvider p = (PostgresConnectionProvider) sql.configuration().connectionProvider();
p.commit();
And the ConnectionProvider:
public class PostgresConnectionProvider implements ConnectionProvider {
private static final Logger LOG = LoggerFactory.getLogger(PostgresConnectionProvider.class);
private final ThreadLocal<Connection> connections = new ThreadLocal<>();
private final BoneCP pool;
public PostgresConnectionProvider(String serverName, int port, String schema, String user, String password) throws SQLException {
this.pool = new ConnectionPool(getConnectionString(serverName, port, schema), user, password).pool;
}
private String getConnectionString(String serverName, int port, String schema) {
return "jdbc:postgresql://" + serverName + ":" + port + "/" + schema;
}
public void close() {
pool.shutdown();
}
public void commit() {
LOG.debug("Committing transaction in {}", Thread.currentThread());
try {
Connection connection = connections.get();
if (connection != null) {
connection.commit();
connection.close();
connections.set(null);
}
} catch (SQLException ex) {
throw new DataAccessException("Could not commit transaction in postgres pool", ex);
}
}
#Override
public Connection acquire() throws DataAccessException {
LOG.debug("Acquiring connection in {}", Thread.currentThread());
try {
Connection connection = connections.get();
if (connection == null) {
connection = pool.getConnection();
connection.setAutoCommit(false);
connections.set(connection);
}
return connection;
} catch (SQLException ex) {
throw new DataAccessException("Can't acquire connection from postgres pool", ex);
}
}
#Override
//no-op => the connection won't be released until it is commited
public void release(Connection connection) throws DataAccessException {
LOG.debug("Releasing connection in {}", Thread.currentThread());
}
}
Easiest way,(I have found) to use Spring Transactions with jOOQ, is given here: http://blog.liftoffllc.in/2014/06/jooq-and-transactions.html
Basically we implement a ConnectionProvider that uses org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(ds) method to find and return the DB connection that holds transaction created by Spring.
Create a TransactionManager bean for your DataSource, example shown below:
<bean
id="dataSource"
class="org.apache.tomcat.jdbc.pool.DataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="mysql://locahost:3306/db_name"
p:username="root"
p:password="root"
p:initialSize="2"
p:maxActive="10"
p:maxIdle="5"
p:minIdle="2"
p:testOnBorrow="true"
p:validationQuery="/* ping */ SELECT 1"
/>
<!-- Configure the PlatformTransactionManager bean -->
<bean
id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<!-- Scan for the Transactional annotation -->
<tx:annotation-driven/>
Now you can annotate all the classes or methods which uses jOOQ's DSLContext with
#Transactional(rollbackFor = Exception.class)
And while creating the DSLContext object jOOQ will make use of the transaction created by Spring.
Though its an old question, Please look at this link to help configure JOOQ to use spring provided transaction manager. Your datasource and DSLContext have to be aware of Transacation.
https://www.baeldung.com/jooq-with-spring
You may have to change
#Bean
public DefaultDSLContext dsl() {
    return new DefaultDSLContext(configuration());
}
to
#Bean
public DSLContext dsl() {
    return new DefaultDSLContext(configuration());
}

Connection reset in long session

I have a standalone Thread application. Which is a listener waiting for a message, and when it arrives do somethings, in which I have to save in the DB the message.
But I have problems because if I run the application and "send manually a message" everythings works fine, but if I run the application and wait for a message of the system (for instance one hour after it arrives) when the APP 's going to save to the DB it sinks, telling:
java.sql.SQLException: Io exception: Connection reset
or
java.sql.BatchUpdateException: Io Exception: Connection reset
I'm using Hibernate 3.2.6 with C3p0 0.9.2.1
The configuration of the session is:
public final class PersistenceUtil{
private static final SessionFactory sessionFactory;
static {
try {
String location = ServiceLocator.getInstance().getConfigurationService().getProperty("adaptor.location");
if (LOC_1.equals(location)) {
sessionFactory = new AnnotationConfiguration().configure("hibernate.1.cfg.xml").buildSessionFactory();
} else if(LOC_2.equals(location)) {
sessionFactory = new AnnotationConfiguration().configure("hibernate.2.cfg.xml").buildSessionFactory();
}else {
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
}
sessionFactory.openSession();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
private PersistenceUtil() {
}
public static void shutdown() {
try {
sessionFactory.close();
} catch (HibernateException e) {
LOG.error("PersistanceUtil.shutdown Error: " + e.getMessage());
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
When I want to save I do (and where is the error):
public T save(T entity) {
if (!getSession().getTransaction().isActive()) {
log.warn("Session not active. Starting the session");
getSession().beginTransaction();
}
getSession().save(entity);
getSession().getTransaction().commit();
return entity;
}
And my hibernate.cfg.xml is:
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="connection.url">URL</property>
<property name="connection.username">USER</property>
<property name="connection.password">Password</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>
<!-- <property name="hibernate.hbm2ddl.auto">update</property> -->
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="hibernate.c3p0.min_size">1</property>
<property name="hibernate.c3p0.max_size">100</property>
<property name="hibernate.c3p0.timeout">3000</property>
<property name="hibernate.c3p0.max_statements">0</property>
<property name="hibernate.c3p0.idle_test_period">0</property>
<mapping class="MessageEO" />
<mapping class="CustomerEO" />
</session-factory>
</hibernate-configuration>
What am I doing wrong?
Thanks in advance
Why don't you create new session before attempting to save your data? According to Hibernate's docs this is proper approach for atomic operation. As I can see your comments in code, you think that starting transaction means starting new session or opening new connection whitch is not true. You can have multiple (but not always nested) transactions per session.
I always use following template for atomic operation - it never let me down. I even have this piece of code as template in Eclipse:
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
// save/ update / delete your entities here
tx.commit();
} catch (RuntimeException ex) {
if (tx != null) {
tx.rollback();
}
throw ex;
} finally {
session.close();
}
Principles:
Hold single SessionFactory object. Creating it is expensive.
For every bulk of operation (saving, modifing etc.) or single entities open new session using factory - it is lightweight and thread safe.
Sessions itself are not thread safe.
Always start new transactions and roll commit/rollback them if needed. Even for read-only data fetches.
Always close your session after your bulk operations are done (releasing connections etc.)
Never use same same session in whitch exception has had occured.

Spring Optimistic Locking:How to retry transactional method till commit is successful

I use Spring 2.5 and Hibernate JPA implementation with Java and "container" managed Transactions.
I have a "after user commit" method that updates data in background and need to be committed regardless of ConcurrencyFailureException or StaleObjectStateException exception, because it will never be shown to client. In other words, need to make Optimistic Lock to Pessimistic. (Could happen if methods execution will take little bit longer and someone changed data in other transaction)
I read a a lot about idempotent stuff, retry if exception in search for DEFAULT_MAX_RETRIES or 6.2.7. Example or chapter 14.5. Retry. I also found in stackoverflow here and here.
I tried this:
public aspect RetryOnConcurrencyExceptionAspect {
private static final int DEFAULT_MAX_RETRIES = 20;
private int maxRetries = DEFAULT_MAX_RETRIES;
Object around(): execution( * * (..) ) && #annotation(RetryOnConcurrencyException) && #annotation(Transactional) {
int numAttempts = 0;
RuntimeException failureException = null;
do {
numAttempts++;
try {
return proceed();
}
catch( OptimisticLockingFailureException ex ) {
failureException = ex;
}
catch(ConcurrencyFailureException ex) {
failureException = ex;
}
catch( StaleObjectStateException ex) {
failureException = ex;
}
} while( numAttempts <= this.maxRetries );
throw failureException;
}
}
RetryOnConcurrencyException is my Annotation to mark methods that need to be retried, if a exception occurrs. Didn't work... I also tried several ways like SELECT ... FOR UPDATE, EntityManager.lock(...)
What is the best way to avoid stale data, dirty reads etc. such a strategy with Spring? Retry?, synchronized?, JPA lock?, isolation?, select ... for update? I could not get it to work and I really happy about any help.
Here is some pseudo code what I like to do:
void doSomething(itemId) {
select something into A;
select anotherthing into B;
// XXX
item = getItemFormDB( itemId ); // takes long for one user and for other concurrent user it could take less time
item.setA(A);
item.setB(B);
// YYYY
update item;
}
Between // XXX and // YYY another session could modify the item, then the StaleObjectStateException gets thrown.
Use Spring Retry to retry whole method when a version number or timestamp check failed (optimistic lock occurs).
Configuration
#Configuration
#EnableRetry
public class FooConfig {
...
}
Usage
#Retryable(StaleStateException.class)
#Transactional
public void doSomethingWithFoo(Long fooId){
// read your entity again before changes!
Foo foo = fooRepository.findOne(fooId);
foo.setStatus(REJECTED) // <- sample foo modification
} // commit on method end
Project configuration
Spring Boot application has defined valid spring-retry version, so only this is required:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
I got a solution but I think it's ugly. I catch all RuntimeException and it only works for new transactions. Do you know how to make it better? Do you see any problems?
First, I made an Annotation:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface RetryingTransaction {
int repeatCount() default 20;
}
Then I made a interceptor like this:
public class RetryingTransactionInterceptor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 20;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
#Resource
private PlatformTransactionManager transactionManager;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object retryOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
Exception failureException = null;
do {
numAttempts++;
try {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
Object obj = pjp.proceed();
transactionManager.commit(status);
return obj;
}
catch( RuntimeException re ) {
failureException = re;
}
} while( numAttempts <= this.maxRetries );
throw failureException;
}
}
Spring applicationConfig.xml:
<tx:annotation-driven transaction-manager="transactionManager" order="10" />
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionSynchronizationName">
<value>SYNCHRONIZATION_ALWAYS</value>
</property>
</bean>
<bean id="retryingTransactionInterceptor" class="com.x.y.z.transaction.RetryingTransactionInterceptor">
<property name="order" value="1" />
</bean>
<aop:config>
<aop:aspect id="retryingTransactionAspect" ref="retryingTransactionInterceptor">
<aop:pointcut
id="servicesWithRetryingTransactionAnnotation"
expression="execution( * com.x.y.z.service..*.*(..) ) and #annotation(com.x.y.z.annotation.RetryingTransaction)"/>
<aop:around method="retryOperation" pointcut-ref="servicesWithRetryingTransactionAnnotation"/>
</aop:aspect>
</aop:config>
And a method annotated like this:
#RetryingTransaction
public Entity doSomethingInBackground(params)...
We have this and what we do is:
Flush the session (to make sure the upcoming update will be the only one queued)
Load the instance
Do the change
On StaleObjectStateException, clear the action queue
((EventSource) session).getActionQueue().clear()
and retry from #2
We have a retry counter to re-throw the exception in the end.
NOTE: This is not an officially supported method (Hibernate clearly states that a session which has thrown an exception should be discarded and not re-used), but it's a known work-around (with the limitation that you can't selectively remove the update action, but must clear the whole queue).
Throwing out another option here: BoneCP (http://jolbox.com) has support to automatically retry transactions upon failure (including when DB goes down, network fails, etc).

Categories

Resources