当有两个更新并且在两者之间进行搜索时,使用 Envers 的 Hibernate Audit 会跳过一个版本
Hibernate Audit using Envers skips a version when there are two updates and a search in between
问题
我在 JPA 中使用 hibernate envers (@Audited) 并具有以下行为。如果在两次更新之间对实体进行了搜索,它似乎会跳过历史记录中的一个版本 table。
使用 Hibernate 5.5.7,MSSQL Server 15,Java 1.8
步骤如下
- 创建实体。 (刷新并提交)(我的实体有一个带@Version 的版本列)
- 开始交易
- 检索实体并更新 属性(例如更改名称)。不要冲洗
- 再次检索实体并更新另一个 属性(例如更改
id)
- 提交交易。
预期结果
在我的历史记录 table 中,应该有相同数量的记录与我的版本列中的数字相匹配。 (例如,如果版本为 2,则 0,1 和 2 应该有 3 条记录)
实际结果
历史记录中只有两行 table 版本 =0,另一行版本 = 2
所以审计中缺少版本 1 table.
更多想法
这似乎正在发生,因为在第 3 步中,当我们搜索时,Hibernate 会自动刷新。当有自动刷新时,我的实体版本会更新,因为它有变化。
但是对于那个版本没有生成历史记录。
是否有一些变通方法可以在我的历史 table 中获取 'missing' 版本?或者在设置我的@Audit tables.
时可能缺少一些步骤
这是我的代码
Main.java
package se.navod.platform.test.hibernate;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
public class Main {
private static EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PlatformTest");
public static void main(String[] args) {
EntityTransaction transaction = null;
try {
int id = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE - 1);
String name = "Name_" + id;
create(id, name, 10);// Create the student
EntityManager manager = entityManagerFactory.createEntityManager();
transaction = manager.getTransaction();
transaction.begin();// Start the transaction
Student student1 = findStudentByPropertyValue(manager, name, "name");// Retrieve the student by name
student1.setName("NewName");// Change the name
// Retrieve the same student again (by id)
Student student2 = findStudentByPropertyValue(manager, "" + id, "studentId");
student2.setAge(50);// Change the age
manager.persist(student2);// Persist and commit
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
} finally {
if (entityManagerFactory != null) {
entityManagerFactory.close();
}
}
}
public static Student create(int id, String name, int age) {
EntityManager manager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = null;
try {
transaction = manager.getTransaction();
transaction.begin();
Student stu = new Student(id, name, age);
manager.persist(stu);
transaction.commit();
return stu;
} catch (Exception ex) {
if (transaction != null) {
transaction.rollback();
}
ex.printStackTrace();
throw ex;
} finally {
manager.close();
}
}
private static Student findStudentByPropertyValue(EntityManager manager, String value, String property) {
CriteriaBuilder criteriaBuilder = manager.getCriteriaBuilder();
CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class);
Root<Student> itemRoot = criteriaQuery.from(Student.class);
Predicate predicate = criteriaBuilder.equal(itemRoot.get(property), value);
criteriaQuery.where(predicate);
criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get("id")));
List<Student> items = manager.createQuery(criteriaQuery).getResultList();
return items.size() == 0 ? null : items.get(0);
}
}
实体:Student.java
package se.navod.platform.test.hibernate;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import org.hibernate.envers.Audited;
@Entity
@Table(name = "student")
@Audited
public class Student implements Serializable {
private static final long serialVersionUID = -834531085311709309L;
@Column(name = "student_id", unique = true)
private int studentId;
@Id
@Column(name = "primaryKey")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "student_name", nullable = false)
private String name;
@Column(name = "student_age", nullable = false)
private int age;
@Version
private long version;
public Student() {
super();
}
public Student(int studentId, String name, int age) {
super();
this.studentId = studentId;
this.name = name;
this.age = age;
}
public void setId(int id) {
this.id = id;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return id + "\t" + name + "\t" + age;
}
}
Presistance.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="PlatformTest"
transaction-type="RESOURCE_LOCAL">
<!-- Persistence provider -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- Entity classes -->
<class>se.navod.platform.test.hibernate.Student</class>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:jtds:sqlserver://localhost:1433/Test;prepareSQL=0;" />
<!-- The database username -->
<property name="javax.persistence.jdbc.user" value="spider3" />
<!-- The database password -->
<property name="javax.persistence.jdbc.password"
value="spider3" />
<property name="hibernate.dialect"
value="org.hibernate.dialect.SQLServer2012Dialect" />
<property name="org.hibernate.envers.audit_table_suffix"
value="_History" />
<property
name="org.hibernate.envers.do_not_audit_optimistic_locking_field"
value="false" />
<property name="org.hibernate.envers.revision_field_name"
value="revision" />
<property
name="org.hibernate.envers.revision_type_field_name"
value="revisionType" />
</properties>
</persistence-unit>
</persistence>
DB table 脚本
USE [Test]
GO
/****** Object: Table [dbo].[student] Script Date: 9/13/2021 12:07:13 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[student](
[student_id] [int] NULL,
[student_name] [nchar](1000) NULL,
[student_age] [int] NULL,
[primaryKey] [bigint] IDENTITY(1,1) NOT NULL,
[version] [bigint] NOT NULL,
CONSTRAINT [PK_persionId] PRIMARY KEY NONCLUSTERED
(
[primaryKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[student_History](
[student_id] [int] NULL,
[student_name] [nchar](1000) NULL,
[student_age] [int] NULL,
[primaryKey] [bigint] NOT NULL,
[version] [bigint] NOT NULL,
[revision] [int] NOT NULL,
[revisionType] [tinyint] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[REVINFO](
[id] [int] NULL,
[REVTSTMP] [bigint] NOT NULL
) ON [PRIMARY]
GO
这是意料之中的。如果您想创建多个版本,请查看以下讨论:https://discourse.hibernate.org/t/envers-create-multiple-revisions-in-one-transaction/5138/2
问题 我在 JPA 中使用 hibernate envers (@Audited) 并具有以下行为。如果在两次更新之间对实体进行了搜索,它似乎会跳过历史记录中的一个版本 table。
使用 Hibernate 5.5.7,MSSQL Server 15,Java 1.8
步骤如下
- 创建实体。 (刷新并提交)(我的实体有一个带@Version 的版本列)
- 开始交易
- 检索实体并更新 属性(例如更改名称)。不要冲洗
- 再次检索实体并更新另一个 属性(例如更改 id)
- 提交交易。
预期结果 在我的历史记录 table 中,应该有相同数量的记录与我的版本列中的数字相匹配。 (例如,如果版本为 2,则 0,1 和 2 应该有 3 条记录)
实际结果 历史记录中只有两行 table 版本 =0,另一行版本 = 2 所以审计中缺少版本 1 table.
更多想法 这似乎正在发生,因为在第 3 步中,当我们搜索时,Hibernate 会自动刷新。当有自动刷新时,我的实体版本会更新,因为它有变化。 但是对于那个版本没有生成历史记录。
是否有一些变通方法可以在我的历史 table 中获取 'missing' 版本?或者在设置我的@Audit tables.
时可能缺少一些步骤这是我的代码 Main.java
package se.navod.platform.test.hibernate;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
public class Main {
private static EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PlatformTest");
public static void main(String[] args) {
EntityTransaction transaction = null;
try {
int id = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE - 1);
String name = "Name_" + id;
create(id, name, 10);// Create the student
EntityManager manager = entityManagerFactory.createEntityManager();
transaction = manager.getTransaction();
transaction.begin();// Start the transaction
Student student1 = findStudentByPropertyValue(manager, name, "name");// Retrieve the student by name
student1.setName("NewName");// Change the name
// Retrieve the same student again (by id)
Student student2 = findStudentByPropertyValue(manager, "" + id, "studentId");
student2.setAge(50);// Change the age
manager.persist(student2);// Persist and commit
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
if (transaction != null) {
transaction.rollback();
}
} finally {
if (entityManagerFactory != null) {
entityManagerFactory.close();
}
}
}
public static Student create(int id, String name, int age) {
EntityManager manager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = null;
try {
transaction = manager.getTransaction();
transaction.begin();
Student stu = new Student(id, name, age);
manager.persist(stu);
transaction.commit();
return stu;
} catch (Exception ex) {
if (transaction != null) {
transaction.rollback();
}
ex.printStackTrace();
throw ex;
} finally {
manager.close();
}
}
private static Student findStudentByPropertyValue(EntityManager manager, String value, String property) {
CriteriaBuilder criteriaBuilder = manager.getCriteriaBuilder();
CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class);
Root<Student> itemRoot = criteriaQuery.from(Student.class);
Predicate predicate = criteriaBuilder.equal(itemRoot.get(property), value);
criteriaQuery.where(predicate);
criteriaQuery.orderBy(criteriaBuilder.asc(itemRoot.get("id")));
List<Student> items = manager.createQuery(criteriaQuery).getResultList();
return items.size() == 0 ? null : items.get(0);
}
}
实体:Student.java
package se.navod.platform.test.hibernate;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import org.hibernate.envers.Audited;
@Entity
@Table(name = "student")
@Audited
public class Student implements Serializable {
private static final long serialVersionUID = -834531085311709309L;
@Column(name = "student_id", unique = true)
private int studentId;
@Id
@Column(name = "primaryKey")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "student_name", nullable = false)
private String name;
@Column(name = "student_age", nullable = false)
private int age;
@Version
private long version;
public Student() {
super();
}
public Student(int studentId, String name, int age) {
super();
this.studentId = studentId;
this.name = name;
this.age = age;
}
public void setId(int id) {
this.id = id;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public int getStudentId() {
return studentId;
}
public void setStudentId(int studentId) {
this.studentId = studentId;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return id + "\t" + name + "\t" + age;
}
}
Presistance.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="PlatformTest"
transaction-type="RESOURCE_LOCAL">
<!-- Persistence provider -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- Entity classes -->
<class>se.navod.platform.test.hibernate.Student</class>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:jtds:sqlserver://localhost:1433/Test;prepareSQL=0;" />
<!-- The database username -->
<property name="javax.persistence.jdbc.user" value="spider3" />
<!-- The database password -->
<property name="javax.persistence.jdbc.password"
value="spider3" />
<property name="hibernate.dialect"
value="org.hibernate.dialect.SQLServer2012Dialect" />
<property name="org.hibernate.envers.audit_table_suffix"
value="_History" />
<property
name="org.hibernate.envers.do_not_audit_optimistic_locking_field"
value="false" />
<property name="org.hibernate.envers.revision_field_name"
value="revision" />
<property
name="org.hibernate.envers.revision_type_field_name"
value="revisionType" />
</properties>
</persistence-unit>
</persistence>
DB table 脚本
USE [Test]
GO
/****** Object: Table [dbo].[student] Script Date: 9/13/2021 12:07:13 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[student](
[student_id] [int] NULL,
[student_name] [nchar](1000) NULL,
[student_age] [int] NULL,
[primaryKey] [bigint] IDENTITY(1,1) NOT NULL,
[version] [bigint] NOT NULL,
CONSTRAINT [PK_persionId] PRIMARY KEY NONCLUSTERED
(
[primaryKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[student_History](
[student_id] [int] NULL,
[student_name] [nchar](1000) NULL,
[student_age] [int] NULL,
[primaryKey] [bigint] NOT NULL,
[version] [bigint] NOT NULL,
[revision] [int] NOT NULL,
[revisionType] [tinyint] NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[REVINFO](
[id] [int] NULL,
[REVTSTMP] [bigint] NOT NULL
) ON [PRIMARY]
GO
这是意料之中的。如果您想创建多个版本,请查看以下讨论:https://discourse.hibernate.org/t/envers-create-multiple-revisions-in-one-transaction/5138/2