0

I am using a jpa repository that extends from CrudRepository to save an object into a DB.

The object has a unique index on one of the columns and if I try to save the same value twice it throws an java.sql.SQLIntegrityConstraintViolationException .

When I tried to create a test that recreates this behaviour:

when(docsRepository.save(doc)).thenThrow(SQLIntegrityConstraintViolationException.class);
classThatCallsToSave(doc)

I got a

org.mockito.exceptions.base.MockitoException: 
Checked exception is invalid for this method!
Invalid: java.sql.SQLIntegrityConstraintViolationException

I also get an error if I try to use the SQLIntegrityConstraintViolationException in the catch expression of the try-catch.

This is because CrudRepository.save is not supposed to throw that kind of exception. Based on this answer https://stackoverflow.com/a/28650987/5913107, the repository should throw a NonTransientDataAccessException in particular: DataIntegrityViolationException. But the exception seems to be wrapped by the SQLIntegrityConstraintViolationException. If I try to catch DataIntegrityViolationException the exception is not catch.

I don't know what to do because I have an exception being thrown that because it is not supposed to be thrown, I cannot catch.

The code:

@Transactional
public getDocument(String docName){
    var hash = hash(docName);
    createDoc(docName, hash)
}

private String createDoc(String name, String hash) {
    var doc = new Doc(
        name,
        hash);

    try {
        docsRepository.save(doc);
    } catch (ConstraintViolationException e) {
        token = tokensRepository.findByHash(hash).get();
    }

    return token.getValue();

The entity:

@Entity
@Table(name = "docs",
    indexes = {
        @Index(name = "HASH_IDX", columnList = "hash", unique = true)
    })
public class Doc {
    public String getValue() {
        return value;
    }
    @Id
    @NotNull
    private String value;

    @NotNull
    private String hash;
}

The repository:

@Repository
public interface DocsRepository extends CrudRepository<Doc, String> {
    Collection<Doc> findAll();

    Optional<Doc> findByHash(String hash);

    void deleteAll();
}

This is the relevant part of the trace:

"stack_trace":"java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'efb12e37f6b898b576dd051cf1fcbd78be6bbea19316e591b03ac98e3f922f18' for key 'docss.HASH_IDX'
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1098)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1046)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1371)
    at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1031)
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
    ... 102 common frames omitted
Wrapped by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3298)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3825)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
    at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
    at java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:344)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:99)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1362)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:453)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3212)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2380)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:448)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562)
    ... 82 common frames omitted
Wrapped by: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [docs.HASH_IDX]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:276)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:566)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
    at com.foo.bar.Document.domain.DocumentService$$EnhancerBySpringCGLIB$$1801847c.getDocument(<generated>)
    at com.foo.bar.Document.rest.GetDocumentResource.getDocument(GetDocumentResource.java:21)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
Dans Merino
  • 133
  • 1
  • 8
  • 1
    You're logging the exception with the cause first, so the exception you need to throw in your test is a `org.springframework.dao.DataIntegrityViolationException` with the cause set to a `org.hibernate.exception.ConstraintViolationException`. – tgdavies Mar 16 '22 at 10:18
  • I've tried to put in the try-catch clause both`org.hibernate.exception.ConstraintViolationException` and `org.springframework.dao.DataIntegrityViolationException`, both work in the unit test, but fail to catch the exception when I actually try running the program. – Dans Merino Mar 16 '22 at 10:39
  • The exception happens on commit, which is done by an aspect on `getDocument`, looking at the stack trace. – tgdavies Mar 16 '22 at 10:47
  • Could it be caused by a @Transactional? Let me add some more code then – Dans Merino Mar 16 '22 at 10:50

2 Answers2

0

You may find this thread useful, it is similar to the issue you are facing about catching the SQLIntegrityConstraintViolationException.

0

The issue was that the annotation @Transactional was "hijacking" the exception. We end up removing the annotation as we realized we didn't needed it for that part of the code.

The other alternative we came up to solve it was to capture Exception and check for instanceOf but we didn't like this.

Dans Merino
  • 133
  • 1
  • 8