[英]Unique constraint violation when switching two unique fields in the same transaction in H2
I have a Spring Boot application using Spring Data repositories with entities mapped with JPA.我有一个 Spring 引导应用程序,它使用 Spring 数据存储库,其实体映射为 JPA。 The database is H2.
数据库是H2。 The table is auto-generated.
该表是自动生成的。 I'm doing two updates in the same transaction.
我在同一个事务中进行两次更新。 Each of them would on its own would violate a unique constraint, but when both are applied together, the result should be fine.
它们中的每一个都会单独违反唯一约束,但是当两者一起应用时,结果应该没问题。 However, I'm still getting an exception.
但是,我仍然遇到一个例外。
I tried to do the same update with a DB tool (Database view in IntelliJ IDEA) to try to see if that would work, getting this error:我尝试使用数据库工具(IntelliJ IDEA 中的数据库视图)进行相同的更新,以尝试查看是否可行,但出现此错误:
Unexpected update count received (Actual: 0, Expected: 1). All changes will be rolled back.
This is my entity in question:这是我有问题的实体:
@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;
}
Note the unique constraint on DATA_SET_ID
and ORDER_INDEX
.请注意对
DATA_SET_ID
和ORDER_INDEX
的唯一约束。
I'm using a Spring Data repository for access:我正在使用 Spring 数据存储库进行访问:
@Repository
public interface DataSetColumnRepository extends JpaRepository<DataSetColumn, String> {}
Saving the entities looks something like this:保存实体如下所示:
repository.save(entity1);
repository.save(entity2);
I have two DataSetColumns stored with the same DATA_SET_ID
and want to switch the ORDER_INDEX
(the one which has 0 gets 1 and vice versa).我有两个存储有相同
DATA_SET_ID
的 DataSetColumns 并且想要切换ORDER_INDEX
(具有 0 的那个得到 1,反之亦然)。 This happens in the same transaction (managed by Spring, as set up by Spring Boot).这发生在同一事务中(由 Spring 管理,由 Spring 引导设置)。
My expectation was that the unique constraint would not be violated since after both updates the table should be valid.我的期望是不会违反唯一约束,因为在两次更新之后表应该是有效的。 However, I'm getting this:
但是,我得到了这个:
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)
Is my expectation wrong and this doesn't work in principle?我的期望是错误的吗?这在原则上是行不通的? Or could there be something wrong in my code?
还是我的代码有问题?
Your expectation is wrong, to allow such modification the unique constraint should be explicitly defined as deferred (in databases that support optional feature F721) and H2 currently (as of 1.4.199) does not support this feature, there is a feature request for it: https://github.com/h2database/h2database/issues/223您的期望是错误的,为了允许这样的修改,唯一约束应该明确定义为延迟(在支持可选功能 F721 的数据库中)并且 H2 当前(截至 1.4.199)不支持此功能,有一个功能请求: https://github.com/h2database/h2database/issues/223
The typical workaround is to assign a some fake value to the first row, then change the second row to its new value and finally change the first row to its new value.典型的解决方法是为第一行分配一个假值,然后将第二行更改为其新值,最后将第一行更改为其新值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.