在 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
字段的正确方法吗?我认为这两个表都应该更新。
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
。