Reputation: 2311
I have a problem related to Transactional boundaries and I am not able to figure out what is going wrong.
@Transactional( propagation = Propagation.REQUIRED )
Class A {
void methodA() {
try {
new B().callMethodB(obj)
} catch(Exception e) {
updateSomeProperty(obj1)
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
void updateSomeProperty(Object obj1) {
obj1.setProperty(1);
obj1.save();
}
}
Class B {
public void callMethodB(Object obj) throws Exception {
throws new Exception();
}
}
The problem is that my object is not updating when the error is thrown . I also tried firing SQL code from within the method updateSomeProperty
but that also did not work.
Basically I want to update the object's property whether despite the exception is thrown.
Any ideas ??
Upvotes: 4
Views: 2612
Reputation: 19185
Create a custom exception and use it to throw, Also please use @ApplicationException(rollback=false) on as class level annotation while defining custom exception.
e.g.
@ApplicationException(rollback=false)
public CustomException extends Exception{
Upvotes: 0
Reputation: 6073
And it should not work. Because you call updateSomeProperty(obj1) from another method of the class and try to change default Transactional behaviour (from REQUIRED to REQUIRED_NEW). But it will not work. Thats why all your changes will be rolled back when exception occurs.
By default Spring creates proxy for interface and @Transactional annotation should be used only for public method. And this method should be called from "outside". If you will call them from another method within the class then @Transactional annotation will not work.
You can also change default settings for transactions in xml (look at properties proxy-target-class and mode). But I have never changed this and don't remember how exactly it should work.
<tx:annotation-driven transaction-manager="txManager" mode="..." proxy-target-class="..."/>
EDIT:
By the way. Here is a very good article about transaction pitfalls. It helped me very much. There are also few other very interesting articles about transactions.
EDIT 2:
Hello again. I think that I find solution for your problem. At least I tested this and it works for me well. I proposed you to change transaction mode to "AspectJ" and to use AspectJ compile time wieving for project. This will give you a possibility to call a private transactional method from another method within one class with changing transactional behaviour (for started nested transaction). In such case you can commit some changes in nested transaction while outer transaction will be rolled back. For this you need to do such steps:
1) Change transaction mode in transactional definition: - if you use xml configuration then:
<tx:annotation-driven transaction-manager="txManager" mode="aspectj"/>
if you use java configuration then:
@EnableTransactionManagement(mode=AdviceMode.ASPECTJ, proxyTargetClass=false)
2) Add aspectj dependencies to pom:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
3) Add spring-aspects dependency to pom:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
4) Add maven plugin that enables compile time wieving:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<source>${compiler.version}</source>
<target>${compiler.version}</target>
<Xlint>ignore</Xlint>
<complianceLevel>${compiler.version}</complianceLevel>
<encoding>UTF-8</encoding>
<verbose>false</verbose>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<!-- <goal>test-compile</goal> -->
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
5) Also I have maven compiler plugin in my pom thats why I think that it is beter for you to add it too:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerVersion>${compiler.version}</compilerVersion>
<fork>true</fork>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
*Note: I use jdk version 1.7+. And my versions of the compiler and aspectj is sush:
<compiler.version>1.7</compiler.version>
<aspectj.version>1.6.12</aspectj.version>
Also I have such versions of other libraries (but I think this is not necessary):
<org.springframework.version>3.1.0.RELEASE</org.springframework.version>
<org.hibernate.version>4.1.0.Final</org.hibernate.version>
<org.springdata.version>1.0.2.RELEASE</org.springdata.version>
You also can try to use load time wieving in spring, but it is more hard to configure (this is my opinion) and it is not recommended to be used in production (as I read in few posts). But if you will decide to use it you can find a lot of info in web and spring reference dicumentation.
If you want to use compile time wieving without maven then I don't know how to configure this. (I tested only with maven). You can try to find such info in web but I don't recomend this because with maven it is much easier to handle dependencies (and in case of this example - to add necessary plugin).
Here is an example that I used for tests:
Some interface:
public interface TestClassInterface {
void testMethod();
}
Some test class that implements this interface:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor=Exception.class) @Component public class TestClass implements TestClassInterface {
@Autowired
private SpringDataFooDAO fooDao;
public void testMethod() {
try {
Foo foo = fooDao.findOne(2L);
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());
System.out.println(TransactionSynchronizationManager.isActualTransactionActive());
foo.setName("should be rolled back");
new ExceptionThrower().doSomething("default string");
} catch(Exception e) {
updateSomeProperty(1L, "Changed name");
throw new RuntimeException(e);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor=Exception.class)
private void updateSomeProperty(long id, String newFooName) {
System.out.println(" --- ");
System.out.println(TransactionSynchronizationManager.getCurrentTransactionName());
System.out.println(TransactionSynchronizationManager.isActualTransactionActive());
// Update property of test object.
Foo foo = fooDao.findOne(id);
foo.setName(newFooName);
}
}
Another class with method that throws exception:
public class ExceptionThrower {
public void doSomething(Object obj) throws Exception {
throw new Exception();
}
}
Note that I rethrow exception from catch block (I do this as Runtime exception because I don't need to handle it in upper classes). This is necessary for correct outer transaction rollback.
Upvotes: 3
Reputation: 2311
The above arrangement is perfect to start off a new transaction and do other stuff different from the original transaction.
The one thing which was going wrong in my case was that while in the other transaction, I was throwing an exception myself resulting in the second transaction being rolled back again..
So The thing is beware off exception in the transaction because they ensure that the database state rolls back. It is for what they are meant for.
Thanks
Upvotes: 0
Reputation: 1685
Check out the spring reference for how to use @Transactional. @Transactional when using spring proxies comes with a lot of **conditions apply, you need to understand them before applying it in your code.
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
Consider the use of AspectJ mode (see mode attribute in table below) if you expect self-invocations to be wrapped with transactions as well. In this case, there will not be a proxy in the first place; instead, the target class will be weaved (that is, its byte code will be modified) in order to turn @Transactional into runtime behavior on any kind of method.
Upvotes: 1
Reputation: 635
Can you try specifing noRollbackFor = RuntimeException.class or any other class which you want and hopefully it will let you update the database. i.e. @Transactional(noRollbackFor = RuntimeException.class)
Upvotes: 0