#java #hibernate #hibernate-envers
Вопрос:
Проблема Я использую hibernate envers (@проверено) с JPA и веду себя следующим образом. И, похоже, он пропускает версию в таблице истории, если между двумя обновлениями выполняется поиск сущности.
Использование Hibernate 5.5.7 ,MSSQL Сервер 15,Java 1.8
Вот шаги
- Создайте сущность. (очистка и фиксация) (у моей сущности есть столбец версии с @Version)
- Начните транзакцию
- Извлеките сущность и обновите свойство (например, измените имя). Не смывайте
- Извлеките объект снова и обновите другое свойство (например, измените идентификатор).
- Зафиксируйте транзакцию.
Ожидаемые результаты В моей таблице истории должно быть равное количество записей, соответствующих номеру в столбце «Версия». (например, если версия 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