Аудит гибернации с использованием Envers пропускает версию, когда есть два обновления и поиск между ними

#java #hibernate #hibernate-envers

Вопрос:

Проблема Я использую hibernate envers (@проверено) с JPA и веду себя следующим образом. И, похоже, он пропускает версию в таблице истории, если между двумя обновлениями выполняется поиск сущности.

Использование Hibernate 5.5.7 ,MSSQL Сервер 15,Java 1.8

Вот шаги

  1. Создайте сущность. (очистка и фиксация) (у моей сущности есть столбец версии с @Version)
  2. Начните транзакцию
  3. Извлеките сущность и обновите свойство (например, измените имя). Не смывайте
  4. Извлеките объект снова и обновите другое свойство (например, измените идентификатор).
  5. Зафиксируйте транзакцию.

Ожидаемые результаты В моей таблице истории должно быть равное количество записей, соответствующих номеру в столбце «Версия». (например, если версия 2, должно быть 3 записи для 0,1 и 2)

Фактические результаты В таблице истории есть только две строки: одна с версией =0, а другая с версией= 2, поэтому версия 1 отсутствует в таблице аудита.

Больше мыслей, похоже, это происходит, потому что на шаге 3 Hibernate выполняет автоматическую очистку при поиске. Когда происходит автоматическая очистка, моя версия сущности обновляется, потому что в ней есть изменения. Но для этой версии не создается запись истории.

Есть ли какой-то обходной путь, чтобы получить «отсутствующую» версию в моей таблице истории ? Или, может быть, есть некоторые шаги, которые я пропускаю при настройке таблиц @Audit.

Вот ссылка на проект:

Вот мой код 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);
    }

}
 

Entity: 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>
 

Сценарии таблиц БД

     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
 

Ответ №1:

Это ожидаемо. Если вы хотите создать несколько версий, ознакомьтесь со следующим обсуждением: https://discourse.hibernate.org/t/envers-create-multiple-revisions-in-one-transaction/5138/2