Wrap A Callable and Runnable with Spring Transaction - spring

I have custom Anonymous classes Callable and Runnable and I need to enforce transactions around the ENTIRE call of Callable and Runnable.
I am using Spring JPA 2.0 and Spring framework 5. Is there a Programmatic way to surround #Transaction behavior for Anonymous classes Callable and Runnable?
#Transaction
public V call() throw Exception{
this.mymethod();// NOTE THIS IS NOT SURROUNDDED BY TXN
dao.method()
}
#Transaction
public void run(){
this.mymethod()// NOTE THIS IS NOT SURROUNDDED BY TXN
dao.method()
}
#Transaction
public void run(){
this.mymethod1()// NOTE THIS IS NOT SURROUNDED BY TXN because of this
this.mymethod2() // NOTE THIS IS NOT SURROUNDED BY TXN because of this
}
Putting #Transaction does not work. Can you please help how to wrap Programmatically Callable and Runnable?
Spring JPA: 2.2.3.RELEASE
Spring 5.2.2.RELEASE
Spring Boot: 2.2.2.RELEASE
Any pointer will greatly be appreciated.
Let me clarify some points:
I wanted to
CallableTXNDecorator<V>
public V call() throw Exception{
{
start TXN
try{
targetcallable.call();
commmit()
}catch(Exception e){
rollback()
}
}
So I wanted to write Wrapper that does that.

Related

Rollback does not work using MongoTransactionManager

Hello I am developping a back-end using Spring boot and MongoDB 4.0. In order to add transactions I have implemented the MongoTransactionManager as seen in the documentations spring mongo transactions
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
But when I annotate a method with #Transactional(rollbackFor = NullPointerException.class) it does not roll back for this exception.
For example the following test does not work.
Do you have any advices to fix this issue please ?
#Test
#Transactional(rollbackFor = NullPointerException.class)
public void testTransaction() {
try {
myRepo.deleteAll();
throw new NullPointerException();
} catch (
NullPointerException e) {
} finally {
assertThat(myRepo.findAll()).isNotEmpty();
}
}
Just figure it out that MongoTransactionManager does not work if you also register a Bean MongoTemplate.
Moreover, surprinsingly, #Transactional method does not work if it is a #Test method. You must extract the #Transactional method in a #Service.
Because you just caught your NPE and didn't do anything with it. For transaction being rollbacked your method should throw NPE out of the method.

Spring transaction: requires_new beharivour

May be I am misunderstand Spring Requires_new behavior. Here is my code:
#Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)
public void outterMethod() throws Exception{
innerMethod1();
innerMethod2();
}
#Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRES_NEW)
public void innerMethod1() throws Exception{
testService.insert(new Testbo("test-2", new Date()));
}
#Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRES_NEW)
public void innerMethod2() throws Exception{
testService.insert(new Testbo("test-2", new Date()));
throw new Exception();
}
When the innnerMethod2 throws Exception, I thought that innerMethod1 still able to commit. But the all the outer and inner transactions rollback. What am I wrong here? How can I do to commit innerMethod1 when innerMethod2 rollback?
Although you have correctly understood the behavior of Propagation.REQUIRES_NEW, you have stumbled upon a common misconception about Spring 's Transactional behavior.
In order for the transactional semantics to be applied (that is for the annotation of the method to have any effect), the method needs to be called from outside the class. Calling a method annotated with transactional from inside the class has absolutely no effect on the transactional processing (because the Spring generated proxy class that contains the transactional code does not come into play).
In your example innerMethod2 maybe annotated with #Transactional but since it is called from outterMethod, the annotation is not being processed.
Check out this part of the Spring documentation

Spring #Transactional rollbackFor not working

I have a code like below
public abstract class AffltTransactionService implements IAffltTransactionService {
....
#Override
#Transactional
public void processTransactions(List<? extends AffltTransaction> transactions) {
for (AffltTransaction transaction : transactions) {
if (transaction != null) {
processTransaction(transaction);
}
}
}
private void processTransaction(AffltTransaction transaction) {
try {
processTransactionInternal(transaction);
} catch (Exception exception) {
affltTransactionError = new AffltTransactionError(null, null, "error", new Date());
saveAffltTransactionError(affltTransactionError);
log.error(exception.getLocalizedMessage());
}
}
#Transactional(readOnly=false, rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void processTransactionInternal(AffltTransaction transaction) {
processTransactionInternal throws ServiceUnAvailableException which extends RuntimeException
But the transaction is not getting rolled back despite having rollbackFor = Exception.class .
Can you please help.
#Transactional annotation won't have any effect if you are calling the method directly, since Spring creates proxies above annotated classes and the aspect-defined functionality is implemented by proxy. So, when you call the method from within your class it doesn't get through proxy, and hence no transcation is created and/or rolled back.
Take a look at the Spring reference for detailed explanation.
Since you invoke one method from another within the same bean, the Spring AOP doesn't use any advices in this case.
Only processTransactions is wrapped with TransactionInteceptor.
To make it worked you should configure:
<aop:aspectj-autoproxy expose-proxy="true"/>
But it isn't recommened, though.
More info here: http://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy
Use getCurrentSession instead of opensession
I know it was asked a long time ago but I faced with the same issue and for me was missing Spring configurantion annotation:
#EnableTransactionManagement
After write it on ApplicationConfiguration class it was solved. I hope it helps someone on future.
The method you use apply #Transactional should throws exception. Don't use try-catch instead.(I guess you use try-catch in somewhere in your processTransaction function).
Code should be like this:
#Transactional
public void processTransactions(List<? extends AffltTransaction> transactions) threws Exception{
for (AffltTransaction transaction : transactions) {
if (transaction != null) {
processTransaction(transaction);
}
}
}

What is the proper way to save a resource in an #Async task?

I am trying to update a resource inside an asynchronous thread that repeats its task until cancelled. I am using Future to store the thread in a hashmap so it can be cancelled at a later time. The thread forks as expected, and the object instance within the Async method changes its values fine, but when I try to commit them, the changes are not reflected in the DB, and I cannot see any error messages on the console. I've tried many different combinations of #Async and #Transactional but still haven't gotten it. What am I doing wrong? Thanks in advance for any help!
RoomService.java
#Service
#Transactional
public class RoomService {
#Inject private FooService fooService;
...
public Future<String> startRoom(Room room) {
Future<String> future = fooService.startFoo(room.getId());
map.put(room.getId(), future);
return future;
}
FooService.java
#Service
#Transactional
public class FooService {
...
public void save(Foo foo) {
getSession().saveOrUpdate(foo);
}
#Async
public Future<String> startFoo(int id) {
Foo foo = getFooById(id);
try {
while (true) {
foo.setName("Peter");
save(foo);
Thread.sleep(10000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new AsyncResult<String>("done");
}
Having the #Async method be part of a #Transactional bean means Spring intercepts your method twice: once for #Transactional to wrap the method in a Transaction and once for #Async to execute the method in a separate Thread.
Because of the way proxying works, once you are inside a method of FooService, invoking
another method is done on the actual object, not on the proxy which has all the #Transactional behavior. As such, no new transaction is created/committed/rolled back. The actual Transaction boundary is the #Async method itself. But since your method executes an infinite loop, that commit will never happen.
I suggest you make your #Async method in a different class for which you create a bean and inject the FooService. This way, the #Transactional behavior is wrapping the actual call to
fooService.save(foo); // fooService is actually a proxy
and is therefore committed on each invocation.

How to rollback hibernate operation when Exception occured after the operation?

I have a java application created using spring+hibernate.
I have a code like this:
public class EmployeeDAO extends AbstractHibernateDAO {
public void save(Employee emp) throws HibernateException {
super.save(emp); // inside this method, it calls hibernate session.save(). This super.save method can throws HibernateException
doSometingElse(emp); // inside this method, it doesn't call any hibernate methods. It can throws Exception too.
}
}
I would like to make EmployeeDAO.save method as an atomic method in transactional view.
If super.save(emp) succeed but doSomethingElse(emp) failed (by throwing Exception) then I want the Employee record inserted in super.save(emp) be rollbacked.
How to do this?
All you need to do is annotate the method with #Transactional like this:
public class EmployeeDAO extends AbstractHibernateDAO {
#Transactional
public void save(Employee emp) throws HibernateException {
super.save(emp); // inside this method, it calls hibernate session.save(). This super.save method can throws HibernateException
doSometingElse(emp); // inside this method, it doesn't call any hibernate methods. It can throws Exception too.
}
}
That way if an exception is thrown in the EmployeeDAO save the whole method worth of hibernate operations will be rolled back.
If you prefer for all the methods in this class to run in their own transaction, then annotate the class #Transactional instead.
You'll also need to make sure you do have a transaction manager configured.
If you are using Spring Java configuration you'll want your transaction manager to look something like this:
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory());
transactionManager.setDataSource(datasource());
transactionManager.setJpaDialect(new HibernateJpaDialect());
return transactionManager;
}

Resources