简体   繁体   中英

DB constraint violation not throwing Exception in Hibernate

I have the following code:

try {
    userDAO1.save(userRecord);
    userDAO2.save(userRecord);
}
catch(DataIntegrityViolationException e) {
    throw new ApplicationException("Contraint violated")
}

userDAO1.save(userRecord) violates an integrity constraint - so after the entire code has been run, there is nothing written to the table userDAO1 refers to.

However, the userDAO1.save() statement doesn't throw an error/exception - so userDAO2.save() is executed as well.

But the DataIntegrityViolationException is caught, and the stack trace is null .

How do I check where the DataIntegrityViolationException is thrown from, and prevent userDAO2.save() from being executed if userDAO1.save() violates a constraint?

I tried adding a @Transactional annotation around this code, but that didn't work either.

Stack trace:

org.springframework.dao.DataIntegrityViolationException: ORA-00001: unique constraint (UNIQUE_EMAIL) violated
; SQL [n/a]; constraint [UNIQUE_EMAIL]; nested exception is org.hibernate.exception.ConstraintViolationException: ORA-00001: unique constraint (UNIQUE_EMAIL) violated

    at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:643)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:104)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:516)
    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 org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy76.updateUser(Unknown Source)
    at com.osiris.UserReg.UpdateUserCommand.execute(UpdateUserCommand.java:63)

The code I've posted is in UpdateUserCommand, which is annotated with @Transactional(rollbackFor=Exception.class, propagation=Propagation.REQUIRES_NEW)

Ok, this is a bit of a tricky one, but I'll do my best. Hibernate will only commit a transaction when the method annotated with @Transactional exits. Hence your DataIntegrityViolationException will only be catchable after that method returns. There is no way that you can get Hibernate to not call UserDAO2.save() because it can't detect that a violation has occurred. I'll provide an example below

@Service
/*These variable names are used for clarity's sake, I don't actually use these names myself*/
public UserServiceImpl implements UserService{
    @Autowired
    private HibernateUserDAO1 userDao1;
    @Autowired
    private HibernateUserDAO2 userDao2

    @Transactional
    /*Put your try catch block around where this method is called*/
    public void saveUserDao1(User user){
         userDao1.saveOrUpdate(user);
    }

    @Transactional
    /*Only call this if saveUserDao1 succeeds*/
    public void saveUserDao2(User user){
          userDao2.saveOrUpdate(user)
    }
}

Then in your HibernateUserDAO1:

public void saveOrUpdate(User user){
     currentSession().saveOrUpdate(user);
}

The exception can only be caught above your service layer. Ideally what you want to be doing, is individual saves using 2 different DAO's and checking that the first succeeded before doing the second.

EDITED: Also be aware that Hibernate will not pick up private methods annotated with @Transactional because Hibernate depends on creating Proxy objects from the interface that your class implements. No interface definition = no proxy object = no Hibernate Session. So you can't call a private method annotated with @Transactional. I'd try to make your SessionFactory an object in an abstract superclass and have both DAO's inherit from this. A better option is to use 2 transaction managers each pointing to your different databases, then specify which database things are saving too. That way you can use just 1 DAO, and use whichever session factory you require to do your saves.

What made you to believe that DataIntegrityViolationException was not thrown while the statement userDAO1.save() was executed? Also, why do you believe that the statement userDAO2.save() was executed as well?

If the above opinions were made upon observations of code execution progression in an IDE debug console such as that of Eclipse then the interpretations might be wrong.

Please try to observe the results by punching-in some debug statements like the ones below, and executing the code. This may help you to find out the root cause of the failure -

try {
    userDAO1.save(userRecord);
    System.out.println("-- After userDAO1.save(userRecord) --");
    userDAO2.save(userRecord);
    System.out.println("-- After userDAO2.save(userRecord) --");
} catch(DataIntegrityViolationException e) {
    e.printStackTrace();
    throw new ApplicationException("Contraint violated")
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM