7

When writing transactional methods with @Async, it's not possible to catch the @Transactional exceptions. Like ObjectOptimisticLockingFailureException, because they are thrown outside of the method itself during eg transaction commit.

Example:

public class UpdateService {
    @Autowired
    private CrudRepository<MyEntity> dao;

    //throws eg ObjectOptimisticLockingFailureException.class, cannot be caught
    @Async
    @Transactional
    public void updateEntity {
        MyEntity entity = dao.findOne(..);
        entity.setField(..);
    }
}

I know I can catch @Async exceptions in general as follows:

@Component
public class MyHandler extends AsyncConfigurerSupport {
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            //handle
        };
    }
}

But I'd prefer to handle the given exception in a different way ONLY if it occurs within the UpdateService.

Question: how can I catch it inside the UpdateService?

Is the only chance: creating an additional @Service that wraps the UpdateService and has a try-catch block? Or could I do better?

membersound
  • 81,582
  • 193
  • 585
  • 1,120

1 Answers1

2

You could try self-injecting your bean which should work with Spring 4.3. While self-injecting is generally not a good idea, this may be one of the use-cases which are legitimate.

@Autowired
private UpdateService self;

@Transactional
public void updateEntity() {
    MyEntity entity = dao.findOne(..);
    entity.setField(..);
}

@Async
public void updateEntityAsync(){
    try {
       self.updateEntity();
    } catch (Exception e) {
        // handle exception
    }
}
Community
  • 1
  • 1
user140547
  • 7,750
  • 3
  • 28
  • 80
  • If this is indeed a valid usecase for self-injection that might be sufficient. The question is: is it? – membersound Sep 05 '16 at 10:16
  • @membersound: In my opinion, it is valid. It is also possible to misuse it so that you end up with a "god class" with 1000 lines. But your the exception handling is specific to this class, why not put it in the class. If anyone considers it a bad practise, they could leave a comment here... – user140547 Sep 05 '16 at 10:36
  • It does not work and fails with: `There is a circular dependency between 1 beans`. spring-boot.1.4.0 with spring.4.2.3 – membersound Sep 05 '16 at 10:47
  • @membersound: Well it works only with Spring 4.3. Otherwise, according to the link you could try to use `@Resource` and inject by name. – user140547 Sep 05 '16 at 10:52
  • Sorry typo. I mean: `spring-4.3.2`, so it should work. But does not. But I did not try using `@Resource`, just `@Autowired`. Is it required to use resource injection here? I woudn't want to rely on autowiring by resource service names. Or does `@Autowired` only work when using an interface (I'm just using the class itself)? – membersound Sep 05 '16 at 10:58
  • @membersound: autowiring works with classes as well. Maybe there is another problem? – user140547 Sep 05 '16 at 11:45
  • I don't think there's another problem, because without self-injection the application works already long time in production. – membersound Sep 05 '16 at 17:55
  • @membersound Strange, for me it worked. Maybe you could try a Hello-World-like app with only a few beans to see if it in isolation? – user140547 Sep 05 '16 at 18:36