Handling exceptions during a #Transactional method in Spring - java

I am trying to figure out how to best handle persistence (and potentially other) exceptions in combination with Spring's #Transactional.
For this post I am just going to take the simple example of a user registration, which can cause DataIntegrityViolationException due to duplicate username.
The following things I have tried and they are not really satisfactory to me:
1. Naive approach: Just catch the Exception
val entity = UserEntity(...)
try {
repo.save(entity)
} catch (e: DataIntegrityViolationException) {
// not included: some checks for which constraint failed
throw DuplicateUsername(username) // to be handled by the controller
}
This does not work when in a #Transactional method, since the persistence exceptions won't happen until the transaction is commited, which happens outside my service method in the spring transaction wrapper.
2. Flush the EntityManager before exiting
Explicitly call flush on the EntityManager at the end of my service methods. This will force the write to the database and as such trigger the exception. However it is potentially inefficient, as I now must take care to not flush multiple times during a request for no reason. I also better not ever forget it or exceptions will disappear into thin air.
3. Make two service classes
Put the #Transactional methods in a separate spring bean and try-catch around them in the main service. This is weird, as I must take care to do one part of my code in place A and the other in place B.
4. Handle DataIntegrityViolationException in the controller
Just... no. The controller has no business (hue hue hue) in handling exceptions from the database.
5. Don't catch DataIntegrityViolationException
I have seen several resources on the web, especially in combination with Hibernate, suggesting that catching this exception is wrong and that one should just check the condition before saving (i.e. check if the username exists with a manual query). This does not work in a concurrent scenario, even with a transaction. Yes, you will get consistency with a transaction, but you'll still get DataIntegrityViolationException when "someone else comes first". Therefor this is not an acceptable solution.
7. Do not use declarative transaction management
Use Spring's TransactionTemplate instead of #Transactional. This is the only somewhat satisfactory solution. However it is quite a bit more "clunky" to use than "just throwing #Transactional on the method" and even the Spring documentation seems to nudge you towards using #Transactional.
I would like some advice about how to best handle this situation. Is there a better alternative to my last proposed solution?

I use the following approach in my project.
Custom annotation.
public #interface InterceptExceptions
{
}
Bean and aspect at spring context.
<beans ...>
<bean id="exceptionInterceptor" class="com.example.ExceptionInterceptor"/>
<aop:config>
<aop:aspect ref="exceptionInterceptor">
<aop:pointcut id="exception" expression="#annotation(com.example.InterceptExceptions)"/>
<aop:around pointcut-ref="exception" method="catchExceptions"/>
</aop:aspect>
</aop:config>
</beans>
import org.aspectj.lang.ProceedingJoinPoint;
public class ExceptionInterceptor
{
public Object catchExceptions(ProceedingJoinPoint joinPoint)
{
try
{
return joinPoint.proceed();
}
catch (MyException e)
{
// ...
}
}
}
And finally, usage.
#Service
#Transactional
public class SomeService
{
// ...
#InterceptExceptions
public SomeResponse doSomething(...)
{
// ...
}
}

Voted to (3),like:
#Service
public class UserExtService extend UserService{
}
#Service
public class UserService {
#Autowired
UserExtService userExtService;
public int saveUser(User user) {
try {
return userExtService.save(user);
} catch (DataIntegrityViolationException e) {
throw DuplicateUsername(username);// GlobalExceptionHandler to response
}
return 0;
}
#Transactional(rollbackFor = Exception.class)
public int save(User user) {
//...
return 0;
}
}

You can use a class annotated with #ControllerAdvice or #RestControllerAdvice to handle the exceptions
When a controller throw a exception you can catch it at this class and change the response status to a suitable one or add an extra info of the exception
This method helps you to maintain a clean code
You have numerous examples:
https://www.javainuse.com/spring/boot-exception-handling
https://dzone.com/articles/best-practice-for-exception-handling-in-spring-boo

Related

Why transactional does not rollback when RuntimeException occur?

I want to test a non-transactional method in the service layer when inner methods are transactional separately. I want to know what will happen if some method throws an exception. Does it properly roll back or not? I also tried rollbackFor but it did not help. Any suggestions?
Spring v-2.5.1
app.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create-drop
server.error.include-message=always
server.error.include-binding-errors=always
Controller
#PostMapping("/test")
public void test(){
service.test();
}
Service
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
public void test() {
User user1 = new User();
String encodedPassword = passwordEncoder.encode("123");
user1.setUsername("username");
user1.setPassword(encodedPassword);
user1.setFirst_name("name1");
user1.setLast_name("family1");
save(user1);
User update = update(user1.getUsername());
throw new RuntimeException();// I expect Spring rollback all data for save and update methods
//but it seems data has been committed before an exception occur.
}
#Transactional
public void save(User user) {
if (userExists(user.getUsername()))
throw new ApiRequestException("Username has already taken!");
else {
User user1 = new User();
String encodedPassword = passwordEncoder.encode(user.getPassword());
user1.setUsername(user.getUsername());
user1.setPassword(encodedPassword);
user1.setFirst_name(user.getFirst_name());
user1.setLast_name(user.getLast_name());
user1.setCreate_date(user.getCreate_date());
user1.setModified_date(user.getModified_date());
user1.setCompany(user.getCompany());
user1.setAddresses(user.getAddresses());
userRepository.save(user1);
// throw new RuntimeException(); Similarly I expect rollback here.
}
}
#Transactional
public User update(String username) {
User userFromDb = userRepository.findByUsername(username);
userFromDb.setFirst_name("new username");
userFromDb.setLast_name("new lastname");
return userRepository.save(userFromDb);
}
}
Due to way spring proxying works by default, you can not call a "proxied" method from within the instance.
Consider that when you put #Transactional annotation on a method, Spring makes a proxy of that class and the proxy is where the transaction begin/commit/rollback gets handled. The proxy calls the actual class instance. Spring hides this from you.
But given that, if you have a method on the class instance (test()), that calls another method on itself (this.save()), that call doesn't goes through the proxy, and so there is no "#Transactional" proxy. There is nothing to do the rollback when the RuntimeException occurs.
There are ways to change the how Spring does the proxying which would allow this to work, but it has changed over the years. There are various alternatives. One way is to create create separate classes. Perhaps UserServiceHelper. The UserServiceHelper contains #Transactional methods that get called by UserService. This same issue occurs when different #Transactional isolations and propagations are needed.
Related answers and info:
Spring #Transaction method call by the method within the same class, does not work?
Does Spring #Transactional attribute work on a private method?
Spring Transaction Doesn't Rollback
Your example code is not very clear as to what you are trying to do. Often, #Transactional would be put on the service class and apply to all public methods (like test()).
To begin with, I'm not sure if you understand what a transaction represents. If there's a unit of work that contains several functionalities to be executed, and you either want them to completely fail or completely pass - that's when you use a transaction.
So in the case of test() method - why should underlying mechanism (in this case Hibernate) care about rolling back something that happened within save() and update() when the test() itself isn't a transaction?
Sorry for asking more than answering, but as far as I can see - you need to annotate test() with the annotation, and not the individual methods. Or you can annotate them all.

Rolling back transaction with aspect in Spring boot application

I am new to the concept of Aspect-Oriented Programming. I am writing this following aspect using AspectJ in my spring boot application:
#Aspect
public class MyAspect {
private final AspectUtil aspectUtil;
public MyAspect(AspectUtil aspectUtil)) {
this.aspectUtil = aspectUtil;
}
#Pointcut("#within(org.springframework.stereotype.Service)")
public void applicationServicePointcut() {
}
#AfterReturning(value = ("applicationServicePointcut()"))
public void process(JoinPoint joinPoint) {
HttpServletRequest request = aspectUtil.getRequest();
aspectUtil.getHttpVerb(request);
processUtil(request, joinPoint);
}
public void processUtil(HttpServletRequest request) {
...
}
All of the services defined in my application are annotated with #Transactional, one example is given below:
#Service
#Transactional
public class MyService {
..
}
So, here my question is if any exception I throw from my aspect as defined above, then would the associated transaction in the service layer get rolled back?
Actually, my goal is to roll back the entire transaction in case, any exception occurs at this aspect.
An example for a similar requirement 1.4.8. Advising Transactional Operations is available in the documentation.
Exerpt :
The ordering of advice is controlled through the Ordered interface.
.........
The result of the preceding configuration is a fooService bean that
has profiling and transactional aspects applied to it in that order.
If you want the profiling advice to run after the transactional advice
on the way in and before the transactional advice on the way out, you
can swap the value of the profiling aspect bean’s order property so
that it is higher than the transactional advice’s order value...
Please do go through : Advice Ordering to understand the concept better and control the order of advice.
Also go through the conceptual view of calling a method on a transactional proxy:
Basically the idea would be to throw the exception from the custom aspect that should result in the rollback from the Transaction Advisor.
The short answer is it depends. Aspects are ordered in Spring, meaning that they run with a specific order. That means that if your aspect is set to run before the transactional aspect, then no transaction will be created and there would be nothing to actually rollback.
On the other hand, if you set this to run after the transaction aspect and it indeed throws an exception, this would automatically mean that the transaction would have ran and would be committed.
I believe you need to check on ordering of aspects to find the pattern that suits your needs the best.

Spring UnexpectedRollbackException in nested #Transactional method

I have a dao class (MyDao) which is marked with #Transactional annotaion (on class level) with no additional parameters. In this dao class I have a method which in some case needs to throw a checked exception and perform a transaction rollback. Something like this:
#Transactional
public class MyDao {
public void daoMethod() throws MyCheckedException() {
if (somethingWrong) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new MyCheckedException("something wrong");
}
}
This works perfectly fine. However, this dao method is called from a service method, which is also marked as #Transactional:
public class MyService {
#Autowired
private MyDao myDao;
#Transactional
public void serviceMethod() throws MyCheckedException {
myDao.daoMethod();
}
}
The problem is that when daoMethod() is called from serviceMethod() and marks the transaction as rollback only, I get an UnexpectedRollbackException.
Under the hood, Spring creates two transactional interceptors: one for MyDao and one for MyService. When daoMethod() marks the transaction for rollback, the interceptor for MyDao performs the rollback and returns. But then the stack moves to the interceptor for MyService, which finds out about the previous rollback, and throws the UnexpectedRollbackException.
One solution would be to remove the #Transactional annotation from MyDao. But this is not feasible right now because MyDao is used at a lot of places and this could cause errors.
Another solution would be to not set the transaction as rollback only in daoMethod() but rather mark serviceMethod() to revert the transaction on MyCheckedException. But I don't like this solution because I have a lot of these "service methods" and I would have to mark all of them explicitly to rollback on that exception. Also everyone who would add a new service method in the future would have to think of this and therefore it creates opportunities for errors.
Is there a way I could keep setting the transaction as rollback only from daoMethod() and prevent Spring from throwing the UnexpectedRollbackException? For instance, with some combination of parameters isolation and propagation?
I figured it out. I have to explicitly tell also the "outer" interceptor, that I want to rollback the transaction. In other words, both interceptors need to be "informed" about the rollback. This means either catching MyCheckedException in serviceMethod() and setting the transaction status to rollback only, or to mark serviceMethod() like this #Transactional(rollbackFor=MyCheckedException.class).
But as I mentioned in the OP, I want to avoid this because it's prone to errors. Another way is to make #Transactional rollback on MyCheckedException by default. But that's a completely different story.
Throwing an exception inside the transaction already trigger a rollback, using setRollbackOnly() is redundant here and that's probably why you have that error.
If the Transactionnal on the DAO is set up with Propagation.REQUIRED which is the default, then it will reuse an existing transaction if there is already one or create one if there is none. Here it should reuse the transaction created at the service layer.

Transaction not rollbacked

I have a set of operation i would like to be rollbacked if there are an error.
My class
public class BSException extends RuntimeException{
...
}
public class saleFacade{
public update(){
for (){
try{
renewSale();
}
catch(BSException){
logger.error();
}
}
}
#Transactional
public renewSale(){
try{
findSale(); // read only Transactional
xxx.renewSpecialSale();
}
catch(Exception e){
logger.error(...);
}
}
}
public class xxx(){
public void renewSpecialSale(){
payFee(); //write to db
if(error){
throw new BSException();
}
}
#Transactional(propagation = Propagation.REQUIRED)
public payFee(){
try{
...
}
catch(BsException e){
...
}
catch(Exception e){
...
}
}
}
#Configuration
#EnableTransactionManagement
public class DBConfiguration{
#Bean(name = "dataSource")
public BasicDataSource dataSource(){
...
}
}
Inn renewSpecialSale error is throw.
In the renewSale method, if there is an error, i would like to rollback.
Right now nothing is rollbacked
any idea?
If you catch the exception before it leaves the method, then there is no way the proxy wrapping the method can know that an exception was thrown.
Either remove the try-catch entirely or rethrow the exception so that the exception can leave the method that you marked #Transactional (and get intercepted by the proxy), and the rollback will take place.
I recommend removing exception-handling from all these methods. Set up a central exception handler so that anything thrown from the controllers gets caught and logged, and otherwise let exceptions get thrown.
Make sure each of these classes that does something transactional is annotated separately, if you annotate on the method-level then each method that does something transactional should be annotated. Calling a transactional method from a non-transactional method on the same object doesn't go through the proxy (the proxy intercepts only those calls coming in from outside the object, and only then if that method is marked as transactional) so it isn't part of a transaction (+1 to Peter's answer for pointing this out).
I have no idea what your error flag is doing, it seems odd. Spring services shouldn't have state. If you fix the exception-handling you shouldn't need error flags.
The problem comes from your nesting of Method calls and the usage of #Transactional. By default Springs Transaction Management
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. Spring Transaction Management
This means, that the call of
xxx.payFee()
is not surrounded by a transaction when it's called through
saleFacade.update() -> saleFacade.renewSale() -> xxx.renewSpecialSale()
As far as I got it, you have at least these options
Mark xxx.renewSpecialSale() as #Transactional
Mark saleFacade.update() as #Transactional
Mark both classes xxx and saleFacade #Transactional
Also you can create your own custom exception class and throw it.
Just throw any RuntimeException from a method marked as #Transactional.
By default all RuntimeExceptions rollback transaction whereas checked exceptions don't. This is an EJB legacy. You can configure this by using rollbackFor() and noRollbackFor() annotation parameters:
#Transactional(rollbackFor=Exception.class)
This will rollback transaction after throwing any exception.
#Transactional(rollbackFor = MyCheckedException.class)
public void foo() {
throw new RuntimeException();
}
remove try catch block from your method and put below line of code
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
use this line of code insted of
#Transactional(propagation = Propagation.REQUIRED)
In above code spring container handle whole transaction management and here if we provide rollback attribute it automatically manage if any kind of exception occur it will rollback your transaction
and make sure that you have entry of transaction in your configuration file as below
<tx:annotation-driven />
and also
I hope it will sure help you

Creating a post commit when using transaction in Spring

Due to certain reasons i have manually performed transaction commit and roll back using Spring PlatformTransactionManager, what i need to do is setup a hook so that a post commit action takes place after transaction has been committed.
By looking at:
void commit(TransactionStatus status) throws TransactionException;
I cant see how i can determine a transaction was successful other than assumming it so if no expception are thrown.
And i could use AOP as one option, but what about programmitcally doing it, maybe using callback method?
You could get exactly what you want by a simpler way, with TransactionSynchronizationManager and TransactionSynchronization
With TransactionSynchronizationManager, you have static methods to get information about current transaction, and you can register a TransactionSynchronization wich allows you to automatically do a post-commit as you call that
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
void afterCommit(){
//do what you want to do after commit
}
})
Be aware that the TransactionSynchronization is on a per-thread basis (which is often not a problem for a basic web request).
Credit to Grooveek's answer and Alex's comment under it - I put this here because the combined suggestions provide a solid and cleaner solution that is hard to find around the net.
Using Spring 4+. if you need a callback on a #Transactional method after it successfully commits just add that in the beginning of the method:
#Service
public class OneService {
#Autowired
OneDao dao;
#Transactional
public void a transactionalMethod() {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
public void afterCommit(){
//do stuff right after commit
System.out.println("commit!!!");
}
});
//do db stuff
dao.save();
}
}
Since Spring 4.2 it has been possible to define listeners for post commit events (or more generally transaction synchronization events e.g. rollbacks) using annotation based configuraton. This is based off event handling in core spring. It is easier to test code using this approach since you avoid a direct dependency on TransactionSynchronizationManager, which will likely not be active in a unit test. You can easily test that your transactional service publishes an event and also that your listener performs the right action when you receive an event.
So without further ado this is how to set it up:
In this example we'll assume you have a Customer entity and a CustomerRepository (and ORM to go with it).
First you need a new event type NewCustomerEvent:
// NewCustomerEvent.java
// Just a regular pojo for the event
public class NewCustomerEvent {
public String email;
// constructor, setters and getters omitted
}
Then you define a listener using #TransactionalEventListener. By default this will execute after a sucessful commit but this can be changed using the phase parameter:
// NewCustomerEventListener.java
#Component
public class NewCustomerEventListener {
#TransactionalEventListener
public void handleNewCustomerEvent(NewCustomerEvent newCustomerEvent) {
// handle new customer event
}
}
Finally you augment your transaction service with an ApplicationEventPublisher on which you call publish once all the transaction statements have been sent.
// CustomerRespositoryService.java
#Service
public class CustomerRepositoryService {
#Inject
private ApplicationEventPublisher applicationEventPublisher;
#Inject
private CustomerRepository customerRepository;
#Transactional
public void createCustomer(String email) {
Customer customer = new Customer(email);
customerRespotory.save(customer);
applicationEventPublisher.publish(new NewCustomerEvent(email));
}
}
See also:
https://dzone.com/articles/simpler-handling-of-asynchronous-transaction-bound
https://dzone.com/articles/transaction-synchronization-and-spring-application
In one of my projects because of certain reasons I also had to use PlatformTransactionManager. So I forced to use org.springframework.transaction.support.TransactionTemplate.
http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/transaction/support/TransactionTemplate.html
The main benefit is that if you have implemented PlatformTransactionManager correctly, you don't need to bother with manual commit/rollback. At least source code of TransactionTemplate may help you if you need more specific thing.
It's pretty simply to use:
config.xml
<bean name="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="platformTransactionManager"/>
</bean>
MyServiceImpl.java
#Service
public class MyServiceImpl implements MyService {
#Autowired
private TransactionTemplate transactionTemplate;
public Entity getSomethingWithTx(final long id) {
return transactionTemplate.execute(new TransactionCallback<Entity>() {
#Override
public Entity doInTransaction(TransactionStatus status) {
//TODO implement
}
});
}

Categories