Handling specific DataIntegrityViolationException in a transaction - spring

I have a very basic create user controller.
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createUser(#RequestBody UserInput userInput) {
userControllerService.createUser(userInput);
return ResponseEntity.ok("success");
}
The UserControllerService#createUser method is a transaction containing multiple SimpleJpaRepository#save calls. E.g.
#Transactional
#Override
public void createUser(UserInput userInput) {
userRepository.save(userInput);
profileRepository.save(userInput.getProfile());
}
I would like to be able to have the db handle unique constraint violations and be able to inform the client about a specific violation.
For example if I want to inform the client if and only if I get specific constraint violations.
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createUser(#RequestBody UserInput userInput) {
try {
userControllerService.createUser(userInput);
} catch (DuplicateUserNameException e) {
return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
} catch (DuplicateEmailException e) {
return new ResponseEntity<>("", HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
return ResponseEntity.ok("success");
}
However any constraint violation throws a DataIntegrityViolationException at the end of UserControllerService#createUser. And DataIntegrityViolationException is too brittle to rely on. It only has the cause SQLState: 23505 (unique constraint violation) and an unstructured message, such as:
ERROR: duplicate key value violates unique constraint "unique_user"
Detail: Key (email)=(john#test.com) already exists.
Even if I add custom exception handling, it will never be run since the DataIntegrityViolationException isn't encounter until the end of the method when the db is actually called for the first time. E.g. this has no effect.
#Transactional
#Override
public void createUser(UserInput userInput) {
try
userRepository.save(userInput);
} catch (Exception e) {
throw new DuplicateUserNameException();
}
try {
profileRepository.save(userInput.getProfile());
} catch (Exception e) {
throw new DuplicateEmailException();
}
}
Am I going about this the wrong way? It seems like this is very basic functionality that should be possible somehow.
The best way I can think of is adding some code to parse the message from DataIntegrityViolationException, but this has its limitations, for example, for two inserts into the same table have a different meaning for the application. One insert might be directly from the user and the second might be something the application generates. It may not be possible to distinguish the two from the end of the transaction by just parsing the detailed message.
Are there other implementations I should consider instead?

If I understand correctly , it sounds like you want to have a reliable way to determine when DataIntegrityViolationException is thrown , what is the exact reason that causes it such as whether it is due to the duplicated email or duplicated username for a particular use case or anything else.
The simplest way is not to rely on the thrown exception to determine but actively issue some SQL to validate it before the data is saved to DB such as :
#Transactional
#Override
public void createUser(UserInput userInput) {
if(userRepository.existUsername(userInput.getUsername()){
throw new DuplicateUserNameException();
}
if(userRepository.existEmail(userInput.getEmail())){
throw new DuplicateEmailException();
}
userRepository.save(userInput);
profileRepository.save(userInput.getProfile());
}

The problem you mentioned
Even if I add custom exception handling, it will never be run since the DataIntegrityViolationException isn't encounter until the end of the method when the db is actually called for the first time. E.g. this has no effect.
should be solvable by telling Hibernate to execute the transaction right away, by calling
userRepository.flush();
This should cause the exception to be thrown on that line, at least.

Related

Transaction propagation in spring #required

#GetMapping("trans")
#Transactional()
public String primaryTrans() {
User u1 = new User(0,"test","test#email.com");
us.save(u1);
User u2 = new User(0,"test1","test1#email.com");
us.save(u2);
secondaryTrans();
return "index";
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void secondaryTrans() {
// TODO Auto-generated method stub
User u2 = new User(0,"test2","test3#email.com".repeat(300));
us.save(u2);
}
Here i am manually raising DATA TOO LONG exception from secondary transaction, But it causes primary transaction also rolled back. How can we make sure that primary transaction to be committed irrespective of secondary transaction
In this case, since the second method is called from the same class, the second transaction is most likely not created. Springs transactional support uses AOP proxies to create transactions. The docs contain a description on why this will not work.
The simplest way is to catch the exception thrown from secondaryTrans() method, so just wrap secondaryTrans() into try-catch block:
try {
secondaryTrans();
} catch (Exception e) {
//...
}

How to rollback a transaction on any Exception

#Override
#Transactional(rollbackFor=Exception.class)
public void deletePerson(String id) throws Exception{
PersonEntity personEntity = personrepository.findById(id);
if(personEntity == null){
throws new Exception("No Person found");
}
ElasticPersonEntity elasticPersonEntity = modelMapper.map(personEntity,ElasticPersonEntity.class);
try{
personrepository.delete(personEntity);
elasticpersonrepository.delete(elasticPersonEntity); // Error Occur as Elastic search is down
}
catch(Exception ex){
throws new RuntimeException();
}
}
In my above code, I have to save Person data at two places Cassandra and Elastic search. Hence I need to perform delete on both as well . However if due to some reason if my elastic search is down the above code does not rollback. i.e Data is deleted from Cassandra but is still present in Elastic. Any idea of how to do so. I need to do similar modification at update, create as well.
I dont understand why #Transactional(rollbackFor=Exception.class) dont do the job but as say in this topic you can try to run rollback manually
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Transactional Propagation.REQUIRES_NEW not work

I try save list of entities to Oracle Db.
#Transactional
public void save() {
//logick
for (QuittanceType quittanceType : quittance) {
quittancesService.parseQuittance(quittanceType);
}
//logick
}
On each step I call this method:
#Transactional
#Override
public void parseQuittance(QuittanceType quittance) {
try {
//logick create payToChargeDb
paymentToChargeService.saveAndFlush(payToChargeDb);
} catch (Exception e) {
log.warn("Ignore.", e);
}
}
and method
#Override
public PaymentsToCharge saveAndFlushIn(PaymentsToCharge paymentsToCharge) {
return paymentToChargeRepository.saveAndFlush(paymentsToCharge);
}
When I try save entity with constraint My main transaction rollback and I get stacktrace:
Caused by: java.sql.BatchUpdateException: ORA-02290: CHECK integrity constraint violated(MYDB.PAYMENTS_TO_CHARGE_CHK1)
But I want skip not success entities and save success. I marck my method
#Transactional(propagation = Propagation.REQUIRES_NEW)
and it look like this:
#Transactional
#Override
public void parseQuittance(QuittanceType quittance) {
try {
//logick create payToChargeDb
paymentToChargeService.saveAndFlushInNewTransaction(payToChargeDb);
} catch (Exception e) {
log.warn("Ignore.", e);
}
}
and
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Override
public PaymentsToCharge saveAndFlushInNewTransaction(PaymentsToCharge paymentsToCharge) {
return paymentToChargeRepository.saveAndFlush(paymentsToCharge);
}
But when I try save entity with constraint I not get exception and not enter to catcj block. just stop working debugging and the application continues to work. I do not get any errors. and as if rollback is happening
The proxy created by #Transactional does not intercept calls within the object.
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) does not lead to an actual
transaction at runtime even if the invoked method is marked with
#Transactional. Also, the proxy must be fully initialized to provide
the expected behavior, so you should not rely on this feature in your
initialization code (that is, #PostConstruct).
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative
The same documentation recommends use of AspectJ if you want this behaviour.

Handle Hibernate optimistic locking with Spring

I am using Hibernate and Spring Data, it will perform optimistic locking when insert or update an entity, and if the version in database doesn't match with the one to persist, it will throw exception StaleObjectStateException, in Spring, you need to catch it with ObjectOptimisticLockingFailureException.
What I want to do is catch the exception and ask the user to refresh the page in order to get the latest data from database like below:
public void cancelRequest()
{
try
{
request.setStatus(StatusEnum.CANCELLED);
this.request = topUpRequestService.insertOrUpdate(request);
loadRequests();
//perform other tasks...
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
}
I assume it will also work with the code below but I have not tested it yet.
public void cancelRequest()
{
RequestModel latestModel = requestService.findOne(request.getId());
if(latestModel.getVersion() != request.getVersion())
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
else
{
request.setStatus(StatusEnum.CANCELLED);
this.request = requestService.insertOrUpdate(request);
loadRequests();
//perform other tasks...
}
}
I need to apply this checking on everywhere I call requestService.insertOrUpdate(request); and I don't want to apply them one by one. Therefore, I decide to place the checking code inside the function insertOrUpdate(entity) itself.
#Transactional
public abstract class BaseServiceImpl<M extends Serializable, ID extends Serializable, R extends JpaRepository<M, ID>>
implements BaseService<M, ID, R>
{
protected R repository;
protected ID id;
#Override
public synchronized M insertOrUpdate(M entity)
{
try
{
return repository.save(entity);
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, FacesUtils.getMessage("message.actionFailed"),
FacesUtils.getMessage("message.pleaseReload"));
return entity;
}
}
}
My main question is, there will be one problem with this approach. The caller side will not know whether the entity persisted successfully or not since the exception will be caught and handled inside the function, so the caller side will always assume the persist was success, and continue do the other tasks, which is I don't want. I want it to stop performing tasks if fail to persist:
public void cancelRequest()
{
try
{
request.setStatus(StatusEnum.CANCELLED);
this.request = topUpRequestService.insertOrUpdate(request);
//I want it to stop here if fail to persist, don't load the requests and perform other tasks.
loadRequests();
//perform other tasks...
} catch (ObjectOptimisticLockingFailureException ex)
{
FacesUtils.showErrorMessage(null, "Action Failed.", FacesUtils.getMessage("message.pleaseReload"));
}
}
I know when calling the insertOrUpdate , I can catch the returned entiry by declaring new model variable, and compare it's version to the original one, if version is same, means the persistance was failed. But if I doing it this way, I have to write the version checking code on everywhere I call insertOrUpdate. Any better approach then this?
The closest way to being able to do this and not having to necessarily make significant code changes at all the invocation points would be to look into some type of Spring AOP advice that works similar to Spring's #Transactional annotation.
#FacesReloadOnException( ObjectOptimisticLockingFailureException.class )
public void theRequestHandlerMethod() {
// call your service here
}
The idea is that the #FacesReloadOnException annotation triggers an around advice that catches any exception provided in the annotation value and does basically handles the call the FacesUtils should any of those exception classes be thrown.
The other options you have available aren't going to be nearly as straight forward and will require that you touch all your usage points in some fashion, its just inevitable.
But I certainly would not consider putting the try/catch block in the service tier if you don't want to alter your service tier's method return types because the controllers are going to need more context as you've pointed out. The only way to push that try/catch block downstream would be if you returned some type of Result object that your controller could then inspect like
public void someControllerRequestMethod() {
InsertOrUpdateResult result = yourService.insertOrUpdate( theObject );
if ( result.isSuccess() ) {
loadRequests();
}
else {
FacesUtils.showErrorMessage( ... );
}
}
Otherwise you'd need to get creative if you want to somehow centralize this in your web tier. Perhaps a web tier utility class that mimics your BaseService interface like the following:
public <T extends BaseService, U> U insertOrUpdate(T service, U object, Consumer<U> f) {
try {
U result = service.insertOrUpdate( object );
f.accept( result );
return result;
}
catch ( ObjectOptimisticLockingFailureException e ) {
FacesUtils.showErrorMessage( ... );
}
}
But being frank, unless you have a lot of call sites that are similar enough to where such a generalization with a consumer like this makes sense, you may find its more effort and work to generalize it than it would to just place the try/catch block in the controller itself.

Get failure exception in #HystrixCommand fallback method

Is there a way to get the reason a HystrixCommand failed when using the #HystrixCommand annotation within a Spring Boot application? It looks like if you implement your own HystrixCommand, you have access to the getFailedExecutionException but how can you get access to this when using the annotation? I would like to be able to do different things in the fallback method based on the type of exception that occurred. Is this possible?
I saw a note about HystrixRequestContext.initializeContext() but the HystrixRequestContext doesn't give you access to anything, is there a different way to use that context to get access to the exceptions?
Simply add a Throwable parameter to the fallback method and it will receive the exception which the original command produced.
From https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica
#HystrixCommand(fallbackMethod = "fallback1")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
#HystrixCommand(fallbackMethod = "fallback2")
User fallback1(String id, Throwable e) {
assert "getUserById command failed".equals(e.getMessage());
throw new RuntimeException("fallback1 failed");
}
I haven't found a way to get the exception with Annotations either, but creating my own Command worked for me like so:
public static class DemoCommand extends HystrixCommand<String> {
protected DemoCommand() {
super(HystrixCommandGroupKey.Factory.asKey("Demo"));
}
#Override
protected String run() throws Exception {
throw new RuntimeException("failed!");
}
#Override
protected String getFallback() {
System.out.println("Events (so far) in Fallback: " + getExecutionEvents());
return getFailedExecutionException().getMessage();
}
}
Hopefully this helps someone else as well.
As said in the documentation Hystrix-documentation getFallback() method will be thrown when:
Whenever a command execution fails: when an exception is thrown by construct() or run()
When the command is short-circuited because the circuit is open
When the command’s thread pool and queue or semaphore are at capacity
When the command has exceeded its timeout length.
So you can easily get what raised your fallback method called by assigning the the execution exception to a Throwable object.
Assuming your HystrixCommand returns a String
public class ExampleTask extends HystrixCommand<String> {
//Your class body
}
do as follows:
#Override
protected ErrorCodes getFallback() {
Throwable t = getExecutionException();
if (circuitBreaker.isOpen()) {
// Log or something
} else if (t instanceof RejectedExecutionException) {
// Log and get the threadpool name, could be useful
} else {
// Maybe something else happened
}
return "A default String"; // Avoid using any HTTP request or ypu will need to wrap it also in HystrixCommand
}
More info here
I couldn't find a way to obtain the exception with the annotations, but i found HystrixPlugins , with that you can register a HystrixCommandExecutionHook and you can get the exact exception in that like this :
HystrixPlugins.getInstance().registerCommandExecutionHook(new HystrixCommandExecutionHook() {
#Override
public <T> void onFallbackStart(final HystrixInvokable<T> commandInstance) {
}
});
The command instance is a GenericCommand.
Most of the time just using getFailedExecutionException().getMessage() gave me null values.
Exception errorFromThrowable = getExceptionFromThrowable(getExecutionException());
String errMessage = (errorFromThrowable != null) ? errorFromThrowable.getMessage()
this gives me better results all the time.

Resources