There are two database tables role and role_menu. There is foreign key in role_menu referencing role's primary key.
Now I am deleting a row of the master table role which row has a foreign key associated in role_menu. I tried to enclose the delete code inside try catch but the execution does not enter in the catch block :
#Override
#Transactional
public void delete(String role_code) {
try {
sessionFactory.getCurrentSession().delete((Role) sessionFactory.getCurrentSession().get(Role.class, role_code));
} catch (Exception e) {
System.out.println("error : there is fk !");
}
}
In console I got these texts :
WARN : org.hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL Error: 2292, SQLState: 23000
ERROR: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ORA-02292: integrity constraint (PTA.FK_ROLE_MEN_R_ROLE_ME_ROLE) violated - child record found
So how to deal with integrity constraint deletion error ?
The problem came from the fact that the exception is thrown only when the session is flush to the database just before the transaction is committed. Since you are using the #Transactional annotation, it is happening at the end of the method right after the catch block. Flushing the session at the end of the try block should do the trick :
#Override
#Transactional
public void delete(String role_code) {
try {
Session session = sessionFactory.getCurrentSession();
session.delete((Role) session.get(Role.class, role_code));
session.flush();
} catch (Exception e) {
System.out.println("error : there is fk !");
}
}
Note that it may be better to avoid using the flush there and catch the exception at another level, but it depends on the use case.
Related
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.
Spring 4.1.4
Hibernate 4.2.0
JDK 1.8
My context: I have a Controller calling --> Service --> calling Dao
The business funcionality is to delete ( in 1 to many DB relation) some child ,but not all child .
Then ,after deleting some child I try to delete the Parent and offcourse I got java.sql.SQLIntegrityConstraintViolationException
But the question is why transaction is market for Rollback ? ( in other words why I don't got the deletion of some child ?)
SQLIntegrityConstraintViolationException is a checked exception and stating Spring documentation the behaviour would be the same of EJB : Note that by default, rollback happens for runtime, unchecked exceptions only. The checked exception does not trigger a rollback of the transaction.
I need to remove some child anf trying to remove the parent if possible, if not I need to commit the transaction maintaining the parent and remaining of childs
Note I tried also to specify in Service and Dao methods the Spring Annotation
#Transactional(noRollbackFor = SQLIntegrityConstraintViolationException.class)
To request explicitly the behaviour expected , but not even like this work for me
Controller code method:
public void delete() {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Data deleted.","");
try{
memoTemplateService.delete(memoTemplate);
memoTemplates.remove(memoTemplate);
}
catch (Exception e){
msg=new FacesMessage(FacesMessage.SEVERITY_WARN, "A","B");
}
reset();
FacesContext.getCurrentInstance().addMessage(null, msg);
}
Service method :
#Override
#Transactional(noRollbackFor = {SQLIntegrityConstraintViolationException.class,DBConstraintException.class})
public void delete(MemoTemplate memoTemplate)throws BusinessException {
// deleting some ,not all , child
phaseAndMemoGenerator.deleteMemosForVisibleTimeHorizon(memoTemplate);
try{// some times Template cannot be deleted
memoTemplateDao.delete(memoTemplate);
}
catch (Exception e){
throw new DBConstraintException("Partial Delete", "Template cannot be deleted, Memo in the past are present");
}
}
Dao
#Repository(value = "memoTemplateDao")
public class MemoTemplateDaoImpl extends GenericJpaDaoImpl<MemoTemplate, Long> implements MemoTemplateDao {
#Override
#Transactional(noRollbackFor = SQLIntegrityConstraintViolationException.class)
public void delete(MemoTemplate t) {
super.delete(t);
em.flush();
}
}
Just an Update : it's incredible but I can't catch neither doing the catch in Dao method ,debugger go in catch block but before this still a java.sql.SQLIntegrityConstraintViolationException is fired , incredible !
#Transactional(noRollbackFor = {SQLIntegrityConstraintViolationException.class,PersistenceException.class})
public void tryToDelete(MemoTemplate t)throws Exception {
super.delete(t);
try{
em.flush();
}
catch (Exception e){
throw new Exception("ddddd");
}
}
If there are constraints defined in DB, you won't be able to bypass them by committing without rollback.
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.
I am doing a simple experiment for debugging purpose.
First I insert serveral records to database, and then I do a invalid data conversion which will throw DataIntegrityViolationException, but I will catch the exception.
I expected the records being successfully inserted into the db, since I catch the checked exception. But the whole thing is rolled back.
I do the experiment again using TransactionTemplate instead of using annotation, same result.
My questions are:
is this the expected behavior?
If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
Here is my code:
public void insertValue() {
jdbcTemplate.execute("insert into people (person_id, name) values (4, 'asjkdhadsjkqhweqkewhkashdkahd')");
jdbcTemplate.execute("insert into people (person_id, name) values (5, 'tttqqq')");
}
// this should throw exception
public void truncateValue() {
jdbcTemplate.execute("alter table people alter column name varchar(7)");
}
public void jdbc_calls() {
insertValue();
try {
truncateValue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("Finish");
}
public void run() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
transactionTemplate.execute(transactionStatus -> {
try {
jdbc_calls();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
return null;
});
}
More about question No.2.
Here is the source code of TransactionTemplate.execute()
From my understanding, if I don't throw an exception, rollbackOnException won'r be triggered.
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
is this the expected behavior?
Yes, it is.
If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
When an exception occurs, spring will mark your transaction as rollbackOnly.
So even when you catch your exception, at the end of your method, your transaction still rolled back.
In your case, I don't get why you use #Transaction since you want to commit regardless if exception occurs.
Edit
When you're using transaction with DB, the transaction invocation is delegated to EntityManager.
Look at AbstractEntityManagerImpl#handlePersistenceException:
#Override
public void handlePersistenceException(PersistenceException e) {
if ( e instanceof NoResultException ) {
return;
}
if ( e instanceof NonUniqueResultException ) {
return;
}
if ( e instanceof LockTimeoutException ) {
return;
}
if ( e instanceof QueryTimeoutException ) {
return;
}
try {
markForRollbackOnly();
}
catch ( Exception ne ) {
//we do not want the subsequent exception to swallow the original one
LOG.unableToMarkForRollbackOnPersistenceException(ne);
}
}
When exception occurs, the EntityManager mark your transaction as rollbackOnly before throws out the exception for you to catch.
After the exception is catched in your service, the AbstractPlatformTransactionManager will try to commit (because, as you know, no exception is detected there), but the EntityManager refuses to commit because its detect that the transaction marked as rollback-only.
If you read the exception, you will see something like:
javax.persistence.RollbackException: Transaction marked as rollbackOnly
I'm having some trouble trying to catch an exception when there are concurrency violations using hibernate and Spring AOP. This is my scenario:
(My MyConcurrentStateControl has a #Version column)
Service layer
#Override
public Integer saveWork(WorkDto dto) throws MyException {
try {
return workBusinessLogicService.saveWork(dto);
} catch (PersistenceException ex) {
// -- Concurrent insert exception
if (ex.getCause() instanceof ConstraintViolationException) {
String errorMsg = "The same item is being created by another user. Please refresh.";
LOGGER.error(errorMsg, ex);
throw new MyException(errorMsg);
}
} catch (StaleObjectStateException ole){
// -- Concurrent update exception
String errorMsg = "The same item is being updated by another user. Please refresh";
LOGGER.error(errorMsg, ole);
throw new MyException(errorMsg);
}
return -1;
}
Business Logic layer
#Override
#Transactional(rollbackFor = {Exception.class, MyException.class, PersistenceException.class, StaleObjectStateException.class})
public Integer saveWork(WorkDto dto) throws MyException, PersistenceException, StaleObjectStateException {
MyConcurrentStateControl concurrentState = concurrentStateManager.getState(dto.getId());
if (concurrentState == null) {
concurrentState = new MyConcurrentStateControl();
}
// -- Do updates in some other tables --
// Save Concurrent State for concurrency check (Optimistic Locking)
Integer id = concurrentStateManager.save(concurrentState);
// -- Also tried entityManager.flush();
}
This is the error I'm getting in the log, and it is thrown in this line:
return workBusinessLogicService.saveWork(dto);
It is the error I'm expecting when multiple threads call the service, but I can't do anything with it.
[#|2015-01-30T20:53:49.793+0000|WARNING|glassfish3.1.2|javax.enterprise.system.core.transaction.com.sun.jts.jta|_ThreadID=32;_ThreadName=Thread-12;|JTS5054: Unexpected error occurred in after completion
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.mymodel.entity.ConcurrentState#7]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2471)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3123)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3021)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3350)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:140)
......................
org.springframework.transaction.UnexpectedRollbackException: JTA transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException
at org.springframework.transaction.jta.JtaTransactionManager.doCommit(JtaTransactionManager.java:1012)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at net.bull.javamelody.MonitoringSpringInterceptor.invoke(MonitoringSpringInterceptor.java:74)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
at com.mymodel.business.core.service.WorkServiceImpl$$EnhancerByCGLIB$$b1800d24.saveWork(<generated>)
I know the actual queries are triggered on transaction commit, and at that point the control has moved out of the method, therefore I'm not being able to catch the StaleObjectStateException. But how can I do that, or is there some alternative? All I want is:
Roll back all transactions.
Show a reasonable message to the user that there have been concurrent updates and he needs to refresh the UI.