当有两个更新并且在两者之间进行搜索时,使用 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

步骤如下

  1. 创建实体。 (刷新并提交)(我的实体有一个带@Version 的版本列)
  2. 开始交易
  3. 检索实体并更新 属性(例如更改名称)。不要冲洗
  4. 再次检索实体并更新另一个 属性(例如更改 id)
  5. 提交交易。

预期结果 在我的历史记录 table 中,应该有相同数量的记录与我的版本列中的数字相匹配。 (例如,如果版本为 2,则 0,1 和 2 应该有 3 条记录)

实际结果 历史记录中只有两行 table 版本 =0,另一行版本 = 2 所以审计中缺少版本 1 table.

更多想法 这似乎正在发生,因为在第 3 步中,当我们搜索时,Hibernate 会自动刷新。当有自动刷新时,我的实体版本会更新,因为它有变化。 但是对于那个版本没有生成历史记录。

是否有一些变通方法可以在我的历史 table 中获取 'missing' 版本?或者在设置我的@Audit tables.

时可能缺少一些步骤

这是link to the project:

这是我的代码 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