Репозиторий.save() выдает неправильное исключение

Я использую репозиторий jpa, который простирается от CrudRepository, чтобы сохранить объект в БД.

Объект имеет уникальный индекс в одном из столбцов, и если я попытаюсь сохранить одно и то же значение дважды, он выдаст java.sql.SQLIntegrityConstraintViolationException .

Когда я попытался создать тест, который воссоздает это поведение:

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

Я получил

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

Я также получаю сообщение об ошибке, если пытаюсь использовать SQLIntegrityConstraintViolationException в выражении catch для try-catch.

Это потому, что CrudRepository.save не должен вызывать такого рода исключения. Основываясь на этом ответе https://stackoverflow.com/a/28650987/5913107, репозиторий должен выдать NonTransientDataAccessException, в частности: DataIntegrityViolationException. Но исключение, кажется, обернуто SQLIntegrityConstraintViolationException. Если я попытаюсь поймать DataIntegrityViolationException, исключение не поймать.

Я не знаю, что делать, потому что у меня выбрасывается исключение, которое, поскольку оно не должно быть выброшено, я не могу поймать.

Код:

@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();

Лицо:

@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;
}

Репозиторий:

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

    Optional<Doc> findByHash(String hash);

    void deleteAll();
}

Это соответствующая часть трассировки:

"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)

Вы сначала регистрируете исключение с причиной, поэтому исключение, которое вам нужно создать в тесте, — это org.springframework.dao.DataIntegrityViolationException с причиной, установленной на org.hibernate.exception.ConstraintViolationException.

tgdavies 16.03.2022 11:18

Я пытался поместить в предложение try-catch и org.hibernate.exception.ConstraintViolationException, и org.springframework.dao.DataIntegrityViolationException, оба работают в модульном тесте, но не могут поймать исключение, когда я действительно пытаюсь запустить программу.

Dans Merino 16.03.2022 11:39

Исключение возникает при фиксации, которая выполняется аспектом на getDocument, просматривая трассировку стека.

tgdavies 16.03.2022 11:47

Может ли это быть вызвано @Transactional? Позвольте мне тогда добавить еще немного кода

Dans Merino 16.03.2022 11:50
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
45
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете найти эта тема полезным, это похоже на проблему, с которой вы столкнулись при перехвате исключения SQLIntegrityConstraintViolationException.

Ответ принят как подходящий

Проблема заключалась в том, что аннотация @Transactional «захватывала» исключение. В итоге мы удаляем аннотацию, так как поняли, что она нам не нужна для этой части кода.

Другая альтернатива, которую мы придумали, заключалась в том, чтобы захватить Exception и проверить instanceOf, но нам это не понравилось.

Другие вопросы по теме