Java Spring #Transactional method not rolling back as expected - java

Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoB fails, I want anything that would be committed in insertIntoA to be rolled back.
public class Service {
MyDAO dao;
public void insertRecords(List<Record> records){
for (Record record : records){
insertIntoAAndB(record);
}
}
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
Where MyDAO dao is an interface that is mapped to the database using mybatis and is set using Spring injections.
Right now if insertIntoB fails, everything from insertIntoA still gets pushed to the database. How can I correct this behavior?
EDIT:
I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndB directly, the roll back works if there are any issues, but if I call insertIntoAAndB from insertRecords, the roll back doesn't work if any issues arise.

I found the solution!
Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.
public class Foo {
public void insertRecords(List<Record> records){
Service myService = new Service();
for (Record record : records){
myService.insertIntoAAndB(record);
}
}
}
public class Service {
MyDAO dao;
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}

I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.
If you do use hibernate enable SQL and transaction logging to see what it's doing:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2
// log4j.logger.org.hibernate.engine.transaction=DEBUG
If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level
log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG
Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)

Just because jdk parses aop annotation not only with the method, also parse annotation with the target class.
For example, you have method A with #transactional, and method B which calls method A but without #transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations.
So if your calling method in this class is not with the #transactional, it will not parse any other method in this method.
At last, show you the source code:
org.springframework.aop.framework.jdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}

Related

How to rollback transaction on calling #Transactional and non-Transactional method in same and different Service?

I am using spring data rest and Spring JPA. I am having one method which update one database table.
#Autowired InvoiceClient;
#Override
#Transactional
public String doBilling(String x){
//get date from TableOne
Bill bill = billsRepository.getBill(x);
if(bill.isPaid()){
generateInvoice();
}
bill.setPaymentDate(new Date());
return "SUCCESS";
}
generateInvoice is non Transactional method which calls #Transactional method from other service.
public void generateInvoice(){
invoiceClient.generateInvoice();//this is #Transactional, make changes in TableTwo
}
In case of any exception in generateInvoice method whole transaction is rolled back.
Now I want to add one more method which will have list of bill numbers. I call doBilling method in loop to do billing for all the bills.
#Override
#Transactional(readOnly = false, rollbackFor = {Throwable.class}, propagation = Propagation.REQUIRED)
public String doBillingForAll(List<String> tx){
for(String x: tx){
doBilling(x);
}
}
But now in case of any exceptions in doBilling method, all the setPayment methods are getting rolled back but generateInvoice is persisted.
I want to rollback generateInvoice also. How can I do it?
You don't need to define a rollbackFor = {Throwable.class}.
By default all RuntimeException do a rollback when using #Transactional.
It can be that because you are using and intermediate non #Transactional annotated method, the main Transaction is suspended and a nested one is created.
Try to put #Transactional in your public void generateInvoice() then Propagation.REQUIRED should be applied with rollback of your invoices

#Transactional(isolation = Isolation.SERIALIZABLE) retry mechanism

#Transactional(isolation = Isolation.SERIALIZABLE)
I have that annotation on several methods in my spring project. If there is an exception due to the "serialize access problems" what is the best approach if I want to retry the specific transaction. There is annotation #Retryable but it is not very straightforward to me how to use it so that the transaction will rollback and then retry only for that specific exception and just rollback for the other runtime exceptions. Thanks in advance.
A simple solution is to have a method that is the "entry point" for performing your logic; which delegates the actual logic to a transactional method. Typically a nice way of doing this is to have one class that has the Transactional annotations and that does the work and another which is the interface for clients to interact with that delegates; providing a form of indirection.
private static final int MAX_RETRY = 5;
public void doWork(T... parameters) {
doWork(0, parameters);
}
private void doWork(int retryLevel, T... parameters) {
if (retryLevel == MAX_RETRY) {
throw new MaximumRetryCountException(); //or any other exception
} else {
try {
//Get your Spring context through whatever method you usually use
AppContext().getInstance().getBean(classInterestedIn.class).doTransactionalMethod(parameters);
} catch (ExceptionToRetryFor e) {
doWork((retryLevel + 1), parameters);
}
}
}
#Transactional(isolation = Isolation.SERIALIZABLE)
public void doTransactionalMethod(parameters) {
...
}
Please note you may run into problems calling a Transactional method from a different method within that same class (i.e. calling this.doTransactionalMethod()) hence the invocation of Transactional Method is through the Spring Application Context. This is due to the way Spring AOP wraps classes to engage transactional semantics. See: Spring #Transaction method call by the method within the same class, does not work?

How to tell Hibernate NOT to store data while running JUnit tests?

I want to check my persistence logic and so I am running some test cases.
Repository class:
#Repository
public class MyRepository {
public void add(Object obj) {
/* Do some stuff here */
getSession().persist(obj);
}
}
Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { Context.class})
public class MyTests {
#Inject
private MyRepository myRepository;
#Test
#Rollback(true)
public void foo() {
/* Test logic */
myRepository.add(obj);
Assert.assert...;
}
}
The unit test: MyTests.java contains test cases which test some stuff that is related to persistence, but not the actual Hibernate persistence itself, so that's why the getSession.persist() statement is obsolete.
For performance reasons, I want to prevent Hibernate from storing data to my database, even if the whole data interaction is rolled back. My approach would be to mock: getSession().persist(). Is there a better, or more specifically, an easier way to achieve my intentions?
First of all, there are different id generators in Hibernate. If identity generator (not all the databases supports it), then to assign id to the entity, when session.persist method is called, insert query will be called. But if, for example, sequence or uuid generator is used, then insert won't be triggered (at least right away).
After that if methods session.get or session.load are called to load persisted (in the current session) object, then select query won't be called, because it gets object from Hibernate cache. But if HQL is used to select data, then select query is called. Moreover before it (by default) insert query for persisted object is called too.
This can be changed with FlushMode. By default is set to AUTO. It means:
The Session is sometimes flushed before query execution in order to
ensure that queries never return stale state.
But if getSession().setHibernateFlushMode(FlushMode.MANUAL) is set:
The Session is only ever flushed when Session.flush() is explicitly
called by the application.
Which means insert query won't be called until session.flush is called explicitly. If methods session.get and session.load are further used your code will still work (in the current session). But in case of select HQL query - it won't find the entity since it wasn't persisted. So beware.
Create an interface, implement it using the Hibernate persist() method, and use it in such a way, that:
the normal calls go through the implementation
the test calls go through a mock version of it
public interface MyRepository {
public void add(Object obj);
}
public class MyRepositoryImpl implements MyRepository {
public void add(Object obj) {
getSession().persist(obj);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { Context.class})
public class MyTests {
#Mock // we inject the mock instead of the true implementation
private MyRepository myRepository;
#Test
#Rollback(true)
public void foo() {
/* Test logic */
myRepository.add(obj); // the test uses the mocked version
Assert.assert...;
}
}
There are many Java libraries that let you mock objects, e.g.
Mockito
JMock
EasyMock
You need to be able to mock your repository object so that you can use the mock in tests, and use the real one in the rest of your application.
DAO:
#Repository(value="MockRepo")
public class MockMyRepositoryImpl implments MyRepository {
#Override
public void add(Foo foo) {
//Do nothing here
}
}
Test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { Context.class})
public class MyTests {
#Autowired
#Qualifier("MockRepo");
private MyRepository repo;
#Test
public void testFooSave() {
repo.add(obj);
}
}
The alternative is to use a mocking framework as detailed in another answer. Mocking frameworks are more flexible, but if you want something simple that's just going to work then try the above.

Java: How to handle multiple Hibernate transactions in one request?

I'm not sure where to open my Transaction object. Inside the service layer? Or the controller layer?
My Controller basically has two services, let's call them AService and BService. Then my code goes something like:
public class Controller {
public AService aService = new AService();
public BService bService = new BService();
public void doSomething(SomeData data) {
//Transaction transaction = HibernateUtil.getSession().openTransaction();
if (data.getSomeCondition()) {
aService.save(data.getSomeVar1());
bService.save(data.getSomeVar2());
}
else {
bService.save(data.getSomeVar2());
}
//transaction.commit(); or optional try-catch with rollback
}
}
The behavior I want is that if bService#save fails, then I could invoke a transaction#rollback so that whatever was saved in aService would be rolled back as well. This only seems possible if I create one single transaction for both saves.
But looking at it in a different perspective, it looks really ugly that my Controller is dependent on the Transaction. It would be better if I create the Transaction inside the respective services, (something like how Spring #Transactional works), but if I do it that way, then I don't know how to achieve what I want to happen...
EDIT: Fixed code, added another condition. I am not using any Spring dependencies so the usage of #Transactional is out of the question.
You can accomplish what you're asking with another layer of abstraction and using composition.
public class CompositeABService {
#Autowired
private AService aservice;
#Autowired
private BService bservice;
#Transactional
public void save(Object value1, Object value2) {
aservice.save( value1 );
bservice.save( value2 );
}
}
public class AService {
#Transactional
public void save(Object value) {
// joins an existing transaction if one exists, creates a new one otherwise.
}
}
public class BService {
#Transactional
public void save(Object value) {
// joins an existing transaction if one exists, creates a new one otherwise.
}
}
This same pattern is typically used when you need to interact with multiple repositories as a part of a single unit of work (e.g. transaction).
Now all your controller needs to depend upon is CompositeABService or whatever you wish to name it.

Recovering from hibernate optimistic locking exception

I have a method like this:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork(){
Entity = entity = dao.loadEntity();
// do some related work
...
try {
dao.saveEntity(entity);
}
catch(StaleObjectStateException sose){
dao.flush(entity);
doSomeWork();
}
}
I was expecting that by using REQUIRES_NEW transaction propagation and the recursion shown, the StaleObjectStateException would eventually clear but this isn't the case.
How do I recover from this exception?
Turns out there's a bit of a 'gatcha' which I've overlooked...
From spring docs
In proxy mode (which is the default), only 'external' method calls
coming in through the proxy will be intercepted. This means that
'self-invocation', i.e. a method within the target object calling some
other method of the target object, won't lead to an actual transaction
at runtime even if the invoked method is marked with #Transactional!
Because I've been recursing internally, the transaction demarcation wasn't getting applied.
The solution is to recurse via the proxy, like this...
#Autowired
private ApplicationContext applicationContext;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeWork(){
Entity = entity = dao.loadEntity();
// do some related work
...
try {
dao.saveEntity(entity);
}
catch(StaleObjectStateException sose){
dao.flush(entity);
applicationContext.getBean(this.getClass()).doSomeWork();
}
}

Categories