Есть приложение spring+jpa+envers(hibernate) envers необходимо сохранять историю сущностей в специальной таблице.
После того, как я несколько раз сохранил свой объект, я ожидал увидеть заполненное поле версии в таблице USER и заполненное поле версии в USER_AUT. Но фактический результат - это правильное значение в таблице USER, но добавлены столбцы REV_TYPE, REV (в поле только счетчики для всех строк) и нуль в столбцах версии.
Я использую 4.0.1.Final hibernate
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>4.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.0.1.Final</version>
</dependency>
Но когда я смотрю в таблицу, все значения в поле «Версия» равны нулю.
Моя сущность
import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.envers.Audited;
import javax.persistence.*;
@Entity
@Audited
@Table(name = "User", uniqueConstraints = {
@UniqueConstraint(columnNames = { "prKey"})})
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@AllArgsConstructor
@Getter
@Setter
public class User {
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
@Column(name = "PR_KEY", unique = true)
private String prKey;
@Column(name = "name", length = 100, unique = false)
private String name;
@Version
private int version;
public User(String name){
this.name = name;
}
}
И когда я получаю объекты с помощью аудита:
public List<User> getHistory(String id) {
AuditReader auditReader = AuditReaderFactory.get(entityManagerFactory.createEntityManager());
List<Number> auditVersions = auditReader.getRevisions(User.class, id);
List<User> users = auditVersions.stream().map(item -> auditReader.find(User.class, id, item.intValue())).collect(Collectors.toList());
return extractRiskMetrics(riskMetricRecords);
}
Итак, моя настойчивость - конфиг
@Configuration
@EnableTransactionManagement
@EnableJpaAuditing
@EnableJpaRepositories(basePackages = {"persistence"})
@ComponentScan(basePackages = {"persistence", "model"})
public class PersistenceConfig {
private static final String PACKAGE_WITH_JPA_ENTITIES = "persistence";
private final Logger log = Logger.getLogger(getClass());
@Bean
@Resource(type = DataSource.class, lookup = "jdbc/MyDatasource", name = "jdbc/MyDatasource")
public DataSource dataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
DataSource dataSource = dsLookup.getDataSource("java:comp/env/jdbc/MyDatasource");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(dataSource());
entityManager.setPackagesToScan(PACKAGE_WITH_JPA_ENTITIES);
entityManager.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManager.setJpaProperties(getHibernateProperties());
log.info("Entity Manager configured.");
return entityManager;
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
//Set properties hibernate
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.OracleDialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("org.hibernate.envers.do_not_audit_optimistic_locking_field", false);
properties.put("verifyServerCertificate", false);
properties.put("useSSL", false);
properties.put("requireSSL", false);
properties.put("useLegacyDatetimeCode", false);
properties.put("useUnicode", "yes");
properties.put("characterEncoding", "UTF-8");
properties.put("serverTimezone", "UTC");
properties.put("useJDBCCompliantTimezoneShift", true);
return properties;
}
}
Обновления:
Для поля org.hibernate.envers.do_not_audit_optimistic_locking_field задано значение false, но поля версии по-прежнему пусты.
Может быть, это связано с конфликтом Spring Data Jpa и Hibernate - envers?
Фактически, выполненный запрос (измененный и f.c.)
[1/22/19 14:04:51:996 MSK] 00000096 SystemOut O Hibernate: update UserRecord set User=?, version=? where PR_KEY=? and version=?
[1/22/19 14:04:51:998 MSK] 00000096 SystemOut O Hibernate: select hibernate_sequence.nextval from dual
[1/22/19 14:04:52:000 MSK] 00000096 SystemOut O Hibernate: insert into REVINFO (REVTSTMP, REV) values (?, ?)
[1/22/19 14:04:52:002 MSK] 00000096 SystemOut O Hibernate: insert into UserRecord_AUD (REVTYPE, busId, User, UserType, someInfo, PR_KEY, REV) values (?, ?, ?, ?, ?, ?, ?)
Итак, в таблице AUD нет версии where=?
@Ralph Я смотрю в таблицу user_AUD, потому что только версия пользовательской таблицы в порядке (увеличивается)
Столбцы версии не должны подвергаться аудиту по умолчанию. Почему вы хотите проводить аудит столбцов «Версия»? если ваша сущность аннотирована с помощью Audited, при изменении любого поля в таблице *_AUD будет запись аудита. У вас также будет запись в таблице REVINFO, где столбец REV будет в основном представлять «версии» вашего объекта (каждый «моментальный снимок» вашего аудируемого объекта — это отдельная версия).
@VladislavOsipenkov также вы говорите в заголовке, что все столбцы «Версия» пусты, но затем вы упоминаете, что в таблице «ПОЛЬЗОВАТЕЛЬ» столбец «Версия» фактически заполняется, как и ожидалось, но есть проблема со столбцом «Версия» в таблице AUD (где вам на самом деле не нужна версия). столбец).
@hovanessyan спасибо, обновленный заголовок, проблема только в таблице AUD. Мне нужна заполненная версия в AUD, потому что я предоставляю операцию getHistrory и возвращаю все изменения объекта с заполненным столбцом версии.
Вы можете использовать номер версии из таблицы REVINFO (столбец REV). Версия — это деталь реализации оптимистической блокировки. История аудита, представленная в отчетах/интерфейсе для некоторых пользователей, не должна включать столбец «Версия». В общем случае REV и Version должны иметь одинаковые значения (например, для REV 1 версия равна 1 и т. д.). Я хочу сказать, что столбец REV является фактическим столбцом, который нужно использовать, чтобы показать, что изменилось в Entity при использовании Envers.
@hovanessyan в моем случае, если я добавил 3 раза первый объект (изменил значение) и 2 раза второй объект. REV будет 5 для второго объекта, но должно быть всего 2. Они не равны
@VladislavOsipenkov да, я понимаю вашу точку зрения. Я добавил пример кода в ответ на это. Тем не менее, я думаю, что вы не должны выставлять поле «Версия» - возможно, выставляете REV как есть или переназначаете на последовательность 1,2,3.




Посмотрите на настройку конфигурации org.hibernate.envers.do_not_audit_optimistic_locking_field.
Этот параметр конфигурации определяет, будет ли Hibernate Envers включать аннотированное поле @Version в схему аудита или нет. По умолчанию для параметра установлено значение true, что означает, что поле оптимистичной блокировки не будет проверяться. Установив значение false, вы проведете аудит значения столбца.
Я хочу предостеречь вас от установки в этом поле значения false.
Если ваше приложение выполняет явную функцию приращения оптимистической блокировки, это приведет к добавлению дополнительных строк в таблицу журнала аудита, даже если ни один из других столбцов базы данных не будет изменен в рамках вашего бизнес-процесса. Это связано с тем, что после включения отслеживания полей @Version Hibernate Envers просто обрабатывает их как любой другой базовый атрибут объекта. Следовательно, принудительное увеличение оптимистической блокировки вызовет изменение аудита.
обновленная проблема с этим параметром, но проблема все та же
Поскольку ваша конфигурация все еще использует hibernate.hbm2ddl.auto=none. Вы в основном говорите, что если есть изменения схемы, игнорируйте их. Пока вы разрабатываете (особенно с Envers), вы должны оставить это как минимум как update, пока вы не будете довольны тем, что у вас есть, и что оно стабильно; в противном случае вам нужно будет вручную внести необходимые изменения. В Hibernate 6 мы представляем новую функцию, в которой вы можете сказать, что Envers должен быть update, а сама ORM, возможно, должна быть none, чтобы лучше контролировать, как применяются изменения схемы.
Как вы упомянули, REVINFO — это централизованная таблица для всех проверяемых объектов.
Основная идея, представленная ниже, состоит в том, чтобы переназначить номера ревизий на целочисленную последовательность, поэтому RevNumber(2,5,31,125) будет переназначен на customVersion(1,2,3,4)
Допустим, у вас есть EntityA, и вы хотите получить для него все ревизии (и сопоставить данные каждой ревизии с пользовательским классом RevisionEntityDto).
Используя AuditReader от Envers, вы можете сделать что-то вроде:
AuditReader auditReader = AuditReaderFactory.get(entityManager);
//getRevisions() returns revisions sorted in ascending order (older revisions come first)
List<Number> entityARevisions = auditReader.getRevisions(EntityA.class, primaryKeyOfEntityA);
//entityARevisions is already sorted;
for (int customVersion = 0; customVersion < entityARevisions.size(); customVersion++) {
createRevisionEntityDto(primaryKeyOfEntityA, auditReader, revision, customVersion);
}
private RevisionEntityDto createRevisionEntityDto(Long primaryKeyOfEntityA, AuditReader, Number revision) {
EntityA revisionOfEntityA = auditReader.find(EntityA.class, primaryKey, revision);
Date revDate = auditReader.getRevisionDate(revision);
// at this point you have a single revision of EntityA
return toRevisionEntityDto(revision, revisionOfEntityA, revDate);
}
private RevisionEntityDto toRevisionEntityDto(Number revision, EntityA revisionOfEntityA, Date revisionDate, int customVersion) {
//here you do the mapping logic;
RevisionEntityDto revEntityDto = new RevisionEntityDto();
revEntityDto.setFieldA(revisionOfEntityA.getFieldA);
revEntityDto.setDate(revisionDate); // you can use the date to sort if you want at a later stage;
revEntityDto.setCustomVersion(customVersion);
return revEntityDto;
}
Это не имеет отношения к поставленному вопросу.
На какой стол смотреть?
userилиuser_AUD?