In Spring batch Writer I'm updating the db row status from 0 to 1. If any exception occurs update to 2.
However due to #transaction rollback I'm unable to update the status to 2.
(I'm throwing exception to trigger the rollback)
#Override
#Transactional
public void write(List<? extends TestEntity> enityList) throws Exception {
for(TestEntity testEntity : enityList) {
try {
testEntity.setStatus(2);
testRepository.save(testEntity);
testRepository.flush();
testMethod(testEntity); (which throws exception)
}catch (Exception exception) {
testEntity.setStatus(2);
testRepository.save(testEntity);
}
}
}
#Transactional
public void testMethod(TestEntity testEntity) throws Exception {
try{
//Some service call
//...
} catch(Exception e) {
log.error("error", e);
throw new Exception("exp");
}
}
Methods that have the #Transactional will rollback the transaction when they throw an exception. So if an exception is an expected and okay-ish flow of your code, you shouldn't throw an exception and return some kind of result object or status code instead.
#Transactional
public void testMethodThatIsAllowedToFail(TestEntity testEntity) {
try{
//Some service call
} catch(Exception e) {
return Status.FAILURE; // enum you have to create
}
return Status.SUCCESS;
}
// spring batch writer
public void write(List<? extends TestEntity> enityList) throws Exception {
[...]
Status result = testMethod(testEntity); (which throws exception);
if (result != Status.SUCCESS) {
// do something with it
}
[...]
}
you could also play around with #Transactional(propagation = Propagation.REQUIRES_NEW) but you would have to think hard whether having an extra transaction is desireable.
Related
I'm working on a microservice app, in service layout I want to invoke with CompletableFuture.runAsync(). The problem is when I want to throw exception, I have my own Handler Exception, but I can't capture error when it is produced in my catch block inside CompletedFuture shown below:
Controller:
#PostMapping(path="/offers/offer")
public CompletableFuture<Oferta> infoPropiedad(#Valid #RequestBody OfertaRequest inDTO) throws
WebServiceBadResponseException, SOAPException, IOException, InterruptedException, ExecutionException {
System.out.println("THREAD: "+Thread.currentThread().getName());
CompletableFuture<Oferta> outTO = new CompletableFuture<Oferta>();
return CompletableFuture.supplyAsync(()->{
try {
return ofertasService.ofertasService(inDTO);
} catch (Exception e) {
System.out.println("Error inesperado en la capa del controlador");
}
return null;
});
}
Service:
CompletableFuture<OfertaCrm> completableFutureCRM =
CompletableFuture.supplyAsync(()-> {
try {
return clientOferta.llamadaWebServiceOfertas(inDTOCrm);
} catch (Exception e1) {
//throw Exception and capture it with my handler class
}
});
ClientWs:
public OfertaCrm llamadaWebServiceOfertas(OfertaRequestCRM inDtoCrm)
throws SOAPException, IOException {
CompletableFuture<OfertaCrm> completableFuture = new CompletableFuture<OfertaCrm>();
logger.info("Iniciamos la llamada al WS");
//Error produces here and I want to controle it and capture with my handler class
Error handler:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler({
WebServiceBadResponseException.class,
SOAPException.class,
IOException.class
})
#ResponseBody
public ErrorMessage internalError(Exception exception) {
return new ErrorMessage(exception,exception.getMessage());
}
I could not be applying the correct form. Any idea how to throw the exception inside the supplyAsync block?
CompletableFuture will wrap the exception thrown within the execution inside a CompletionException. You can handle it by intercepting the root cause exception directly. Below is a simplified example.
Controller:
#RestController
public class SimpleController {
#Autowired
SimpleService simpleService;
#GetMapping("/testing")
public CompletableFuture<Integer> testing(){
return simpleService.doStuff();
}
}
Service:
#Service
public class SimpleService {
public CompletableFuture<Integer> doStuff(){
// 1 / 0 will throw ArithmeticException
return CompletableFuture.supplyAsync(() -> 1 / 0);
}
}
Controller Advice:
#RestControllerAdvice
public class SimpleControllerAdvice {
#ExceptionHandler(ArithmeticException.class)
public String handleCompletionException(ArithmeticException ex){
return "hello world";
}
}
GET /testing
hello world
The transactional roll back is not working when the exception is caught on the catch block, and another method is called for throw the exception. The pseudo code for the above is:
#Transactional(rollBackFor = Exception.class)
public void method1() {
// Calling another method
method2();
}
private void method2() {
try {
dbOperation1();
} catch (Exception e) {
handleFault()
}
}
handleFault() {
// Calling another method and throwing an exception
throwException()
}
throwException() {
//....
throw new Exception();
}
This is the class to save:
#Service
public class DataService {
#Transactional(readOnly = true)
public String fetchData() { //no exception signature
try {
//some operations
checkData();
}
catch(Exception e) {
throw new CanerRuntimeException("an error occurred in fetchdata: " + e.getMessage(), e);//it cant come here with exception from child
}
}
private void checkData() throws SystemException { //intellj made me put that exception
try {
//some operations
if (!isCanerNotMade) {
String errorMessage = "It is not caner made by";
throw new CanerBusinessException(errorMessage);
}
}
} catch(CanerBusinessException e) {
logger.error("CheckForFksLimitations CanerBusinessExceptionerror {}", e.getMessage());
throw e;
}
} catch(Exception e) {
logger.error("CheckForFksLimitations Exception error {}", e.getMessage());
throw e;
} finally {
if (fksLog != null) {
saveLog(fksLog);
}
logger.info("CheckForFksLimitations ended for identityNumber: {}", identityNumber);//3
}
}
#Transactional
private void saveLog(FksLog fksLog) {
try {
logger.info("CheckForFksLimitations saving fksControlLog: {}", mobilityUtil.getObjectAsJson(fksControlLog));//1
FksControlLog savedfksControlLog = fksControlLogRepository.saveAndFlush(fksControlLog);
logger.info("CheckForFksLimitations saved fksControlLog: {}", mobilityUtil.getObjectAsJson(savedfksControlLog));//2
} catch(CanerBusinessException e) {
logger.info("CheckForFksLimitations error: {}", e.getMessage(), e);
}
}
and that exceptions are:
public class CanerBusinessException extends RuntimeException {}
public class CanerRuntimeException extends RuntimeException {}
I send data for both cases. One for not to throw exception and it can save without any rollback. I made saveAndFlush because it cant save inside a readonly=False parent method. That is how it can save as child.
But when i send the case to throw exception, it throws exception. It goes to finally block then save method. But after that, it rolls back
I see those logs:
CheckForFksLimitations saving fksControlLog: {"id":null,
CheckForFksLimitations saved fksControlLog: {"id":91,
CheckForFksLimitations ended for identityNumber: ARJUNA016129: Could not end XA resource com.ibm.db2.jcc.t4.a4#2a5410b8 com.ibm.db2.jcc.am.XaException: [jcc][t4][10401][12066][4.24.92] Xa exception: XA_RBROLLBACK ERRORCODE=-4228, SQLSTATE=null
It is oracle db.
I did not put any rollback class for exception. It is because of this?
I also put exception to parent signatures but did not work. This service called by a controller.
I have some Ten API which talks to redis for storing the data.
Currently it is throwing nested exception, so I have done like below to handle the nested exception.
#Override
public boolean delMyStatus(String key) {
try{
return redisTemplate.delete(key);
}
catch (Exception e){
if(e.getCause() != null && e.getCause().getCause() instanceof RedisException) {
RedisException ex = (RedisException)e.getCause().getCause();
log.error("RedisException " + ex.getMessage());
/* Do some action*/
} else {
throw new IllegalStateException("...");
}
}
return false;
}
But I dont want to do this for all the APIS of redis dao, Is there any better way to handle exception.
You can use #RestControllerAdvice. Make a custom exception class CustomRedisException throw CustomRedisException Exception from every controller and handle this in separate class annotated with #RestControllerAdvice.
#Override
public boolean delMyStatus(String key) {
try{
return redisTemplate.delete(key);
}
catch (Exception e){
if(e.getCause() != null && e.getCause().getCause() instanceof RedisException) { RedisException ex = (RedisException)e.getCause().getCause();
throw new CustomRedisException(ex);
} else {
throw new IllegalStateException("...");
}
}
return false;
}
Make GlobalExceptionHandler like below.
#RestControllerAdvice(basePackages = "your base package here", basePackageClasses = RepositoryRestExceptionHandler.class)
public class GlobalRestExceptionHandler {
#ExceptionHandler
public ResponseEntity<ErrorResponse> handleCustomException(final CustomRedisExceptionex) {
// code for exception handling here.
return new ResponseEntity<>(
new ErrorResponse(HttpStatus.PRECONDITION_FAILED.value(), ex.getMessage()),
HttpStatus.PRECONDITION_FAILED);
}
}
You can achieve it with aspects and #AfterThrowing annotation.
First make sure you allowed Spring to use aspects using #EnableAspectJAutoProxy annotation on any of your configuration classes.
Then define an #Aspect class with method annotated with #AfterThrowing like this:
#Aspect
public class GenericExceptionHandler {
// you can use more specific path here
#AfterThrowing ("execution(* *.*(..))", throwing = "ex")
public void handleException(Exception ex) throws Exception {
// handle the exception here
}
}
I need to insert data in two different table. After insert in the first table (save1 function) I start an cycle for on array String where foreach element i call save2 function. In the save2 function maybe raised an ConstraintViolationException. I've annotaded method as can you see with Transacational.
I would that exception raised in method save2 not propagate rollback for save1 method. Is it possible?
#Service
#Transactional(propagation = Propagation.REQUIRED, value = "transactionManager")
public class MyService extends BaseService {
public void insert(AppFileForm appFileForm) {
try {
_insert(appFileForm);
setStatusOk();
} catch (IOException ioe) {
setStatusKo(msg.getProperty("file.upload.error"));
} catch (Exception e) {
e.printStackTrace();
setErrorFromException(e);
}
}
public void _insert(){
save1();
save2();
}
#Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor={Exception.class})
public void save1(){
for(String s: myArray){
save2();
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor={Exception.class})
public void save2(){
}
}
The dao method are irrilevant
public void save(MyClass mc) throws Exception{
em.persist(mc);
}
#Transactional annotation has no effect when called from another method of the same component.