在 H2 中切换同一事务中的两个唯一字段时违反唯一约束

Posted

技术标签:

【中文标题】在 H2 中切换同一事务中的两个唯一字段时违反唯一约束【英文标题】:Unique constraint violation when switching two unique fields in the same transaction in H2 【发布时间】:2019-10-09 16:52:47 【问题描述】:

我有一个 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_IDORDER_INDEX 的唯一约束。

我正在使用 Spring Data 存储库进行访问:

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

我的期望是错误的吗?这在原则上是行不通的?还是我的代码有问题?

【问题讨论】:

见***.com/questions/47317706/… 【参考方案1】:

您的期望是错误的,为了允许这样的修改,唯一约束应该明确定义为延迟(在支持可选功能 F721 的数据库中)并且 H2 当前(截至 1.4.199)不支持此功能,有一个功能要求: https://github.com/h2database/h2database/issues/223

典型的解决方法是为第一行分配一个假值,然后将第二行更改为其新值,最后将第一行更改为其新值。

【讨论】:

以上是关于在 H2 中切换同一事务中的两个唯一字段时违反唯一约束的主要内容,如果未能解决你的问题,请参考以下文章

在导入Oracle数据库的时候违反唯一约束条件是为啥?要怎么解决?

ORA-00001: 违反唯一约束条件

mysql插入记录时违反唯一索引的处理

mysql插入记录时违反唯一索引的处理

mysql插入记录时违反唯一索引的处理

MongoDb在并非所有文档中的字段上创建唯一索引[重复]