Hibernate:使用继承时的多个 @Version 列和字段

问题描述 投票:0回答:1

在 Hibernate 中处理继承时,我遇到了

@Version
的问题。

我使用InheritanceType.JOINED解决方案

我有一个

BaseEntity
定义了一个公共字段 - 包括
@Version
:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

  @Id
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "uuid")
  private String id;

  @Column(nullable = false)
  @Temporal(TemporalType.TIMESTAMP)
  @CreatedDate
  private Date createdAt;

  @Column
  @Temporal(TemporalType.TIMESTAMP)
  @LastModifiedDate
  private Date updatedAt;

  @Version @Builder.Default private long version = 1;
}

Credential
实体,是该层次结构中的“中间”部分。它是下面
UserIdCredentials
实体的父级:

@Entity
@Table(name = "credentials")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Credential extends BaseEntity {
  @ManyToOne
  @JoinColumn(name = "online_account_id")
  private OnlineAccount onlineAccount;
}

UserIdCredentials
这是继承中最低的成员:

@Entity
@Table(
    name = "userid_credentials",
    uniqueConstraints = {@UniqueConstraint(columnNames = "username")})
@PrimaryKeyJoinColumn(name = "id")
public class UserIdCredentials extends Credential {
  @Column(unique = true)
  private String username;

  @Transient private String password;
  @Column private String passwordEncoded;
}

因为我使用

InheritanceType.JOINED
,所以上面的每个类都有自己的数据库表,因此也有
version
列。这是我的 DDL:

这是我的 DDL:

REATE TABLE credentials (
    id VARCHAR(255) NOT NULL PRIMARY KEY,
    online_account_id VARCHAR(255) NOT NULL,

    created_at DATETIME NOT NULL,
    updated_at DATETIME,
    version BIGINT NOT NULL,

    FOREIGN KEY (online_account_id) REFERENCES online_accounts(id)
);

CREATE TABLE userid_credentials (
    id VARCHAR(255) NOT NULL PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    password_encoded VARCHAR(60) NOT NULL,

    created_at DATETIME NOT NULL,
    updated_at DATETIME,
    version BIGINT NOT NULL,

    CONSTRAINT uc_username UNIQUE (username),
    FOREIGN KEY (id) REFERENCES credentials(id)
);

但是在保存

UserIdCredentials
实体后,我收到错误:

    com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert the value NULL into column 'version', table 'testdb.dbo.userid_credentials'; column does not allow nulls. INSERT fails.
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:262) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1632) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:602) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:524) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7375) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:3200) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:247) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:222) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:473) ~[mssql-jdbc-8.4.0.jre14.jar:na]
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-3.4.5.jar:na]
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-3.4.5.jar:na]
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3254) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3779) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:107) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:723) ~[na:na]
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1360) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:451) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3210) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2378) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:534) ~[spring-orm-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:632) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) ~[spring-tx-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.2.7.RELEASE.jar:5.2.7.RELEASE]
   ...

看起来 hibernate 仅使用在

version
中声明的
BaseEntity
字段,这意味着当 Hibernate 对
credentials
userid_credentials
表执行插入时,只有第一个获得非空版本值并向
userid_credentials
插入语句具有空值,因此失败。

当我从

version
中删除公共字段(
updated_at
created_ad
userid_credentials
)时,一切都会正常工作。

但是这是处理

@Version
字段的正确方法吗?我认为这两个表都应该更新。

java sql-server hibernate jpa spring-data-jpa
1个回答
0
投票

javax.persistence.Version的Javadoc说:

每个类只能使用一个 Version 属性或字段; 使用多个 Version 属性或字段的应用程序将不会 便于携带。

即使您尝试将 version2-Field 添加到子类中,您也会遇到异常。

我们对

@Version

@Inheritance(strategy = InheritanceType.JOINED)
 也有同样的问题。

但是,我们观察到,每次更新子对象时,例如

UserIdCredentials

 
Credential
 的父表也会更新,并且在该更新中,使用 
version
-属性。

您可以通过将

org.hibernate.SQL

 的日志级别设置为 
DEBUG
org.hibernate.type.descriptor.sql
TRACE
 来验证自己 - 这将记录 SQL 查询和所有使用的 SQL 参数。

我们最终省略了子表的版本列,并添加了一个单元测试(带有内存数据库)来验证旧版本子对象的更新是否会导致

PessimisticLockException

© www.soinside.com 2019 - 2024. All rights reserved.