I couldn't find a good solution: In my Spring Boot app, as an #ExceptionHandler method, I need to define a handler not for a specific exception, but for any exception caused by a specific exception (i.e. a wrapped exception).
Example: Sometimes I get this:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:541) ~[spring-orm-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746) ~[spring-tx-5.1.4.RELEASE.jar:5.1.4.RELEASE]
... 121 common frames omitted
Caused by: custom.TechRoleException: My custom TechRoleException
at myapp.method1[..]
at myapp.methodOuter[..]
My custom TechRoleException is an exception I throw inside some Hibernate EventListener's pre-update method, and the direct exception is that Persistence couldn't occur.
However, the following method that tries to use my custom exception is never reached:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(TechRoleException.class)
public String techRoleException(HttpServletRequest request, Exception ex) {
System.out.println("Got here");
return "home";
}
}
Here's a similar thread where the answer is wrong and didn't answer this question:
#ExceptionHandler for Wrapped Exception / getCause() in Spring
Maybe something like that?
#ExceptionHandler(Exception.class)
public String techRoleException(HttpServletRequest request, Exception ex) {
if(ex instanceof TechRoleException) {
System.out.println("Got here");
return "home";
} else {
throw ex; //or something else
}
}
My final working answer is to handle a General Exception, and then use Apache ExceptionUtils.getRootCause() to detect the specific Caused-By I'm looking for within this general handler.
(Other specific Exceptions won't come to this method if they have their dedicated Handlers. But if there's no dedicated Handler, the exception will come here.) This is the only way to detect some target Caused-By.
#ExceptionHandler(Exception.class)
public String handleGeneralException(HttpServletRequest request, Exception ex) {
Throwable rootCause = ExceptionUtils.getRootCause(ex);
if (rootCause != null && "com.myapp.TechRoleException".equals(rootCause.getClass().getName())
{
//... handle this Caused-By Exception here
// ...
}
// All other exceptions that don't have dedicated handlers can also be handled below...
// ...
}
Related
I'm using this code in order to save an entity:
public Mono<QdCFPresenter> store(QdCF qdcf) {
return this.qdcfRepository.save(qdcf);
}
According to documentation, ReactiveCrudRepository.save method throws two exceptions:
IllegalArgumentException
OptimisticLockingFailureException
I want to wrap them into my business exception service:
Mono.error(
GitException.builder()
.reason(GitReason.QDCF_NOT_STORED)
.build()
)
);
However, I don't quite figure out how to catch case any exception is raised.
I have the following method:
#Transactional
public Store handle(Command command) {
Store store= mapper.map(command.getStoreDto(), Store.class);
Store persistedStore = storeService.save(store);
addressService.saveStoreAddress(store, command.getEmployeeId()); //this method is not crucial, should be called independently and in another transaction, without any rollback in case of exception
return persistedStore;
}
addressService.saveStoreAddress is not crucial - when this method will throw any exception, store should be saved anyway (storeService.save(store);). What is the best solution in my case?
Use #Transactional(propagation=REQUIRES_NEW) on the saveStoreAddress() such that it will execute in a new and separate transaction.
To prevent the transaction of the handle() will be rollback because of the exception throw from saveStoreAddress() , you also have to try-catch when calling saveStoreAddress().
In the end , it looks something like:
#Service
public class AddressService {
#Transactional(propagation=REQUIRES_NEW)
public void saveStoreAdress(.....){
}
}
#Transactional
public Store handle(Command command) {
.......
try{
addressService.saveStoreAddress(store, command.getEmployeeId());
}catch (Exception ex){
/***
* handle the exception thrown from saveStoreAddress.
* If you want the current transaction not rollback just because of the
* exception throw from saveStoreAddress(), do not re-throw the exception when
* handling this exception
*/
}
return ....;
}
I have the following method:
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void applyLog(int codFilial, List<LogLojaCentralCompactoEntity> items) {
}
which internally calls:
#Override
#Transactional(noRollbackFor = PersistenceException.class)
public void apply(LogCompactoEntity entity) {
}
The second method has a try/catch a PersistenceException. The problem is the transaction rolls back then it reaches PersistenceException.
I know Spring #Transactional defaults to roll back in any unchecked exception, but I am explicitly telling noRollbackFor the PersistenceException.
Why its not working? Any way to threat it?
Thanks.
Edit - the try/catch method inside apply does this:
try {
insert();
}
catch(PersistenceException e)
{
update();
}
Edit2 - log:
Edit3 - exception handling part:
if (acao.equalsIgnoreCase("I")) {
try {
insertQuery.executeUpdate();
}
catch(PersistenceException e) {
int affected = updateQuery.executeUpdate();
if(affected == 0)
throw new LogApplyException("O insert falhou e o update não afetou registros.");
}
}
Edit4 - Some more info:
1) Removing #Transactional from apply, leaving #Transaction just on applyLog results on this exception:
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not execute statement
2) Same as 1, but adding noRollbackFor = PersistenceException.class (on applyLog) results on this exception:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
Edit 5:
#lzagkaretos solution is valid (Thank you), but I want to know how to handle this for future cases.
UPDATE
I think that relying in primary key violation exception in order to find if record should be inserted or updated is not something you should do. Another implementation you can use instead is finding before the execution if record is already saved in the database to perform an update, or not to perform an insert.
For example, if you can use spring data repositories, the code might seem like this.
public void apply(LogCompactoEntity entity) {
LogCompactoEntity logCompactoEntity = (entity.getId() == null) ? new LogCompactoEntity() : logCompactoRepository.findOne(entity.getId());
if (logCompactoEntity == null) {
logCompactoEntity = new LogCompactoEntity();
}
copyProperties(entity, logCompactoEntity);
logCompactoRepository.save(logCompactoEntity);
}
--
Maybe you shouldn't have a try/catch block in apply method and declare it with throws PersistenceException in method signature.
In order for noRollbackFor to work, PersistenceException has to be thrown from apply method. You can try the following.
try {
insert();
}
catch(PersistenceException e) {
update();
throw e;
}
method(){
try{
some code..
}
catch(Exception e)
{
throw new userDefineException();
}
}
// while calling the above method from the java client I am gettine remote exception but i am expecting to get UserdefineException.
EJB container will wrap undeclared (system) exceptions in RemoteException (or EJBException for local views). To avoid this, you should either:
Change UserDefineException to extend Exception rather than RuntimeException, and add UserDefineException to the throws clause of the remote interface.
Annotation UserDefineException with #ApplicationException, or specify it as <application-exception>com.example.UserDefineException</application-exception> in ejb-jar.xml.
Some code like this:
public class A {
#Autoware
private B b;
public void a() {
//AAA: some logic process that maybe throw exception
b.b();
}
}
public class B {
public void b() {
//BBB: some logic process maybe also throw exception
}
}
Both exceptions in A.a() and B.b() need to be intercept, so i use #AfterThrowing annotation do it. but the question is, when i call A.a() in other code and exception has occurred in B.b(), the Advice will execute twice! because exception that occurred in B.b() was propagating to its caller A.a().
I can't swallow the exception silently, because i use spring-amqp, above codes is on Consumer side, i need some message processing that based on the exceptions that occurred in Consumer.
#Around does not work too since i can't swallow the throwed exception.
So, How can i intercept a exception just when it occurred? ignore propagation of it.
Any reply is greatly appreciated.