Нарушение уникального ограничения при переключении двух уникальных полей в одной транзакции в H2 - PullRequest
0 голосов
/ 09 октября 2019

У меня есть приложение Spring Boot, использующее репозитории Spring Data с сущностями, сопоставленными с JPA. База данных H2. Таблица генерируется автоматически. Я делаю два обновления в одной транзакции. Каждый из них сам по себе будет нарушать уникальное ограничение, но когда оба они применяются вместе, результат должен быть хорошим. Тем не менее, я все еще получаю исключение.

Я попытался сделать то же самое обновление с помощью инструмента БД (представление базы данных в IntelliJ IDEA), чтобы попытаться проверить, сработает ли это, получая эту ошибку:

Unexpected update count received (Actual: 0, Expected: 1). All changes will be rolled back.

Это мой вопрос:

@Entity
@Table(
        name = "DATA_SET_COLUMN",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"DATA_SET_ID", "ORDER_INDEX"})
        }
)
@Data
@EqualsAndHashCode(callSuper=true)
@NoArgsConstructor
@ToString(exclude = {"dataSet", "elements"})
public class DataSetColumn extends UniteFlowEntity {
    @Column(name = "ORDER_INDEX", nullable = false)
    private int order;

    @Column(name = "DATA_SET_ID", nullable = false)
    private String dataSetId;

    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumn(name="DATA_SET_ID", nullable = false, insertable = false, updatable = false)
    @JsonIgnore
    private DataSet dataSet;

    @OneToMany(mappedBy = "column", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JsonIgnore
    List<DataSetElement> elements;
}

Обратите внимание на уникальное ограничение для DATA_SET_ID и ORDER_INDEX.

Я использую репозиторий Spring Data дляaccess:

@Repository
public interface DataSetColumnRepository extends JpaRepository<DataSetColumn, String> {}

Сохранение сущностей выглядит примерно так:

        repository.save(entity1);
        repository.save(entity2);

У меня есть два DataSetColumns, хранящиеся с тем же DATA_SET_ID, и я хочу переключить ORDER_INDEX (одинкоторый имеет 0 получает 1 и наоборот). Это происходит в той же транзакции (управляемой Spring, как настроено Spring Boot).

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

update unitelabs.data_set_column set creation_date=?, modification_date=?, data_set_id=?, order_index=? where id=? [23505-197]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
    at org.h2.message.DbException.get(DbException.java:179)
    at org.h2.message.DbException.get(DbException.java:155)
    at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:101)
    at org.h2.mvstore.db.MVSecondaryIndex.requireUnique(MVSecondaryIndex.java:236)
    at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:202)
    at org.h2.mvstore.db.MVTable.addRow(MVTable.java:732)
    at org.h2.table.Table.updateRows(Table.java:509)
    at org.h2.command.dml.Update.update(Update.java:177)
    at org.h2.command.CommandContainer.update(CommandContainer.java:102)
    at org.h2.command.Command.executeUpdate(Command.java:261)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:199)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:153)
    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:175)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3356)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3229)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3630)
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:146)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3283)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2479)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:532)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
    at uniteflow.dataset.api.rest.DataSetApi$$EnhancerBySpringCGLIB$$839b47f6.saveColumns(<generated>)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
    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:800)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)

Мои ожидания неверны, и это не работает в принципе? Или может быть что-то не так в моем коде?

1 Ответ

0 голосов
/ 10 октября 2019

Ваше ожидание неверно, чтобы разрешить такую ​​модификацию, уникальное ограничение должно быть явно определено как отложенное (в базах данных, которые поддерживают необязательную функцию F721), а H2 в настоящее время (по состоянию на 1.4.199) не поддерживает эту функцию, есть функциязапрос на это: https://github.com/h2database/h2database/issues/223

Типичным обходным решением является присвоение некоторой ложной стоимости первой строке, затем изменение второй строки на новое значение и, наконец, изменение первой строки на новое значение.

...