📜 ⬆️ ⬇️

Spring: Transactional TaskExecutor Implementation

Spring, taking care of the developers, offers a convenient and simple facade for interacting with the transaction manager. But will a standard mechanism always be enough to implement sophisticated architectural ideas? Obviously not.

In this post we will talk about the possibilities of Spring -


The most commonly used setting of transactions is through the @Transactional annotation. To take advantage of this mechanism, it is enough to configure the preferred TransactionManager and enable annotation processing. In the case of configuration using XML files, it looks like this:
..... <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="dataSource" ref="dataSource" /> <property name="entityManagerFactory" ref="entityManagerFactory" /> <property name="jpaDialect" ref="jpaDialect"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> .... 


To use this further is as simple as “hanging” the @Transactional annotation on the implementation of an interface method or over the entire implementation class.
 @Service public class FooServiceImpl implements FooService { @Autowired private FooDao fooDao; @Transactional @Override public void update(Foo entity) { fooDao.merge(entity); } } 

However, to effectively use this mechanism, you need to remember a few, not always obvious subtleties:

Such a mechanism allows you not to write a transaction management code every time!
')
However, can there be situations when this mechanism will not be enough? or is the context in which annotations fail? I'm afraid the answer is obvious. For example, we may want to execute part of the code in one transaction, and the other part in the second (here it’s rather architecturally correct to divide the method into two). Readers, I think, have their own examples.

A more realistic example is when part of the code needs to be executed asynchronously:
 @Service public class FooServiceImpl implements FooService { @Autowired private TaskExecutor taskExecutor; @Autowired private FooDao fooDao; @Transactional @Override public void update(Foo entity) { fooDao.merge(entity); taskExecutor.run(new Runnable() { public void run() { someLongTimeOperation(entity); } }); } @Transactional @Override public void someLongTimeOperation(Foo entity) { //     } } 


What happens: before the start of the update () method, a transaction is created, then operations are performed from the body, and upon exiting the method, the transaction is closed. But in our case a new thread is created in which the code will be executed. And it is quite obvious that at the time of exit from the update () method and the attendant destruction of a transaction, the execution of the code in the second launched thread can / will continue. As a result, upon the completion of the method, in the second thread, we get an exception and the entire transaction is a “rollback”.

To the previous example, add a manual transaction creation:
 @Service public class FooServiceImpl implements FooService { @Autowired private TaskExecutor taskExecutor; @Autowired private FooDao fooDao; @Transactional @Override public void update(final Foo entity) { fooDao.merge(entity); final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); taskExecutor.execute(new Runnable() { @Override public void run() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { someLongTimeOperation(entity); } }); } }); } @Transactional @Override public void someLongTimeOperation(Foo entity) { //     } } 


Now someLongTimeOperation () is executed asynchronously and in a dedicated transaction. However, I want a generic implementation, so as not to duplicate the cumbersome code of manual control.

Well ... here she is:
 public interface TransactionalAsyncTaskExecutor extends AsyncTaskExecutor { void execute(Runnable task, Integer propagation, Integer isolationLevel); } 

 public class DelegatedTransactionalAsyncTaskExecutor implements InitializingBean, TransactionalAsyncTaskExecutor { private PlatformTransactionManager transactionManager; private AsyncTaskExecutor delegate; private TransactionTemplate sharedTransactionTemplate; public DelegatedTransactionalAsyncTaskExecutor() { } public DelegatedTransactionalAsyncTaskExecutor(PlatformTransactionManager transactionManager, AsyncTaskExecutor delegate) { this.transactionManager = transactionManager; this.delegate = delegate; } @Override public void execute(final Runnable task, Integer propagation, Integer isolationLevel) { final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.setPropagationBehavior(propagation); transactionTemplate.setIsolationLevel(isolationLevel); delegate.execute(new Runnable() { @Override public void run() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { task.run(); } }); } }); } @Override public void execute(final Runnable task) { execute(task, TransactionDefinition.PROPAGATION_REQUIRED, TransactionDefinition.ISOLATION_DEFAULT); } @Override public void execute(final Runnable task, long startTimeout) { final TransactionTemplate transactionTemplate = getSharedTransactionTemplate(); delegate.execute(new Runnable() { @Override public void run() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { task.run(); } }); } }, startTimeout); } @Override public Future<?> submit(final Runnable task) { final TransactionTemplate transactionTemplate = getSharedTransactionTemplate(); return delegate.submit(new Runnable() { @Override public void run() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { task.run(); } }); } }); } @Override public <T> Future<T> submit(final Callable<T> task) { final TransactionTemplate transactionTemplate = getSharedTransactionTemplate(); return delegate.submit(new Callable<T>() { @Override public T call() throws Exception { return transactionTemplate.execute(new TransactionCallback<T>() { @Override public T doInTransaction(TransactionStatus status) { T result = null; try { result = task.call(); } catch (Exception e) { e.printStackTrace(); status.setRollbackOnly(); } return result; } }); } }); } public PlatformTransactionManager getTransactionManager() { return transactionManager; } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public AsyncTaskExecutor getDelegate() { return delegate; } public void setDelegate(AsyncTaskExecutor delegate) { this.delegate = delegate; } public TransactionTemplate getSharedTransactionTemplate() { return sharedTransactionTemplate; } public void setSharedTransactionTemplate(TransactionTemplate sharedTransactionTemplate) { this.sharedTransactionTemplate = sharedTransactionTemplate; } @Override public void afterPropertiesSet() { if (transactionManager == null) { throw new IllegalArgumentException("Property 'transactionManager' is required"); } if (delegate == null) { delegate = new SimpleAsyncTaskExecutor(); } if (sharedTransactionTemplate == null) { sharedTransactionTemplate = new TransactionTemplate(transactionManager); } } } 


This implementation is a wrapper, which eventually delegates calls to any TaskExecutor of which there are several in Spring. In addition, each call is "wrapped" in the transaction. You can manually manage transactions in Spring using a TransactionTemplate, but EntityManager # getTransaction () throws an exception.

And finally, about a practical example in action:

Configuring TaskExecutor:
  <bean id="transactionalTaskExecutor" class="ru.habrahabr.support.spring.DelegatedTransactionalAsyncTaskExecutor"> <property name="delegate"> <bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="threadNamePrefix" value="Habrahabr example - "/> <property name="threadGroupName" value="Habrahabr examples Group"/> <property name="corePoolSize" value="10"/> <property name="waitForTasksToCompleteOnShutdown" value="true"/> </bean> </property> <property name="transactionManager" ref="transactionManager"/> </bean> 


Service example:

 @Service public class FooServiceImpl implements FooService { @Autowired private TransactionalAsyncTaskExecutor trTaskExecutor; @Autowired private FooDao fooDao; @Transactional @Override public void update(Foo entity) { fooDao.merge(entity); //     Spring' (tr_1). trTaskExecutor.run(new Runnable() { //       (tr_2),  run()         tr_2. public void run() { someLongTimeOperation(); } }); } //        tr_1 .  tr_2    TransactionTemplate. @Transactional @Override public void someLongTimeOperation(Foo entity) { //     } } 


Thus, we have a completely generalized implementation of the wrapper for TaskExecutor, which allows us to avoid duplication of the transaction creation code.

Source: https://habr.com/ru/post/228953/


All Articles