Hibernate Envers 記錄實體對象的歷史版本,主要用於審計數據,如意外丟失數據的找回、審計數據合法性等。java
Hibernate Envers 使用 @Audited
註解實體對象,Hibernate 爲每個註解對象映射的數據表建立一個對應的審計數據存儲表,藉助 Listener 機制將數據更新操做(增刪改)記錄到審計表中。mysql
如下展現 Hibernate Envers 開發的完整步驟:sql
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>lesson</groupId> <artifactId>hibernate</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> <version>${hibernate.version}</version> </dependency> </dependencies> <properties> <hibernate.version>5.2.10.Final</hibernate.version> </properties> </project>
persistence.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="hibernate.envers"> <description> Persistence unit for the JPA tutorial of the Hibernate Getting Started Guide </description> <class>hibernate.Person</class> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/test" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="123456" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.hbm2ddl.auto" value="create" /> </properties> </persistence-unit> </persistence>
package hibernate; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.hibernate.annotations.GenericGenerator; import org.hibernate.envers.Audited; @Entity @Table(name = "person") @Audited public class Person { private int id; private String account; private String name; private Date birth; public Person() {} public Person(String account, String name, Date birth) { this.account = account; this.name = name; this.birth = birth; } @Id // 實體惟一標識 @GeneratedValue(generator = "increment") // 使用名爲「increment」的生成器 @GenericGenerator(name = "increment", strategy = "increment") // 定義名爲「increment」的生成器,使用Hibernate的"increment"生成策略,即自增 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Temporal(TemporalType.TIMESTAMP) public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } @Override public String toString() { return "Person [id=" + id + ", account=" + account + ", name=" + name + ", birth=" + birth + "]"; } }
org.hibernate.envers.AuditReader
查詢實體對象歷史記錄package hibernate; import java.util.Date; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.hibernate.envers.AuditReader; import org.hibernate.envers.AuditReaderFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; public class EnversIllustrationTest { private EntityManagerFactory entityManagerFactory; @Before public void setUp() throws Exception { entityManagerFactory = Persistence.createEntityManagerFactory("hibernate.envers"); } @After public void tearDown() throws Exception { entityManagerFactory.close(); } @Test public void test() { EntityManager entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); entityManager.persist(new Person("tony", "Tony Stark", new Date())); entityManager.persist(new Person("hulk", "Banner", new Date())); entityManager.getTransaction().commit(); entityManager.close(); entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); List<Person> persons = entityManager.createQuery("FROM Person", Person.class).getResultList(); System.out.println("------------------------------------------------------------------------------------------------------------------"); System.out.println("INITIAL DATA : "); for (Person person : persons) { System.out.println(person); } System.out.println("------------------------------------------------------------------------------------------------------------------"); entityManager.getTransaction().commit(); entityManager.close(); entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); Person person = entityManager.find(Person.class, 2); person.setBirth(new Date()); person.setName("Bruce Banner"); entityManager.getTransaction().commit(); entityManager.close(); entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); person = entityManager.find(Person.class, 2); System.out.println("------------------------------------------------------------------------------------------------------------------"); System.out.print("UPDATED DATA : "); System.out.println(person); System.out.println("------------------------------------------------------------------------------------------------------------------"); AuditReader reader = AuditReaderFactory.get(entityManager); Person original = reader.find(Person.class, 2, 1); Person current = reader.find(Person.class, 2, 2); System.out.println("------------------------------------------------------------------------------------------------------------------"); System.out.print("First Version : "); System.out.println(original); System.out.print("Second Version : "); System.out.println(current); System.out.println("------------------------------------------------------------------------------------------------------------------"); entityManager.getTransaction().commit(); entityManager.close(); } }
單元測試日誌:數據庫
六月 26, 2017 10:14:16 下午 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation INFO: HHH000204: Processing PersistenceUnitInfo [ name: hibernate.envers ...] 六月 26, 2017 10:14:16 下午 org.hibernate.Version logVersion INFO: HHH000412: Hibernate Core {5.2.10.Final} 六月 26, 2017 10:14:16 下午 org.hibernate.cfg.Environment <clinit> INFO: HHH000206: hibernate.properties not found 六月 26, 2017 10:14:16 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit> INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final} 六月 26, 2017 10:14:16 下午 org.hibernate.boot.jaxb.internal.stax.LocalXmlResourceResolver resolveEntity WARN: HHH90000012: Recognized obsolete hibernate namespace http://hibernate.sourceforge.net/hibernate-mapping. Use namespace http://www.hibernate.org/dtd/hibernate-mapping instead. Support for obsolete DTD/XSD namespaces may be removed at any time. 六月 26, 2017 10:14:19 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!) 六月 26, 2017 10:14:19 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/test] 六月 26, 2017 10:14:19 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator INFO: HHH10001001: Connection properties: {user=root, password=****} 六月 26, 2017 10:14:19 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator INFO: HHH10001003: Autocommit mode: false 六月 26, 2017 10:14:19 下午 org.hibernate.engine.jdbc.connections.internal.PooledConnections <init> INFO: HHH000115: Hibernate connection pool size: 20 (min=1) Mon Jun 26 22:14:19 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification. 六月 26, 2017 10:14:20 下午 org.hibernate.dialect.Dialect <init> INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect 六月 26, 2017 10:14:20 下午 org.hibernate.envers.boot.internal.EnversServiceImpl configure INFO: Envers integration enabled? : true Hibernate: drop table if exists PERSON 六月 26, 2017 10:14:23 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@425d5d46] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. Hibernate: drop table if exists PERSON_AUD Hibernate: drop table if exists REVINFO Hibernate: create table PERSON (ID integer not null auto_increment, ACCOUNT varchar(255), NAME varchar(255), BIRTH datetime, primary key (ID)) engine=MyISAM 六月 26, 2017 10:14:23 下午 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@47f08b81] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode. Hibernate: create table PERSON_AUD (ID integer not null, REV integer not null, REVTYPE tinyint, ACCOUNT varchar(255), NAME varchar(255), BIRTH datetime, primary key (ID, REV)) engine=MyISAM Hibernate: create table REVINFO (REV integer not null auto_increment, REVTSTMP bigint, primary key (REV)) engine=MyISAM Hibernate: alter table PERSON_AUD add constraint FK3lr1agcm4oxkno1t09jp2m936 foreign key (REV) references REVINFO (REV) 六月 26, 2017 10:14:23 下午 org.hibernate.tool.schema.internal.SchemaCreatorImpl applyImportSources INFO: HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@56ba8773' Hibernate: insert into PERSON (ACCOUNT, NAME, BIRTH) values (?, ?, ?) Hibernate: insert into PERSON (ACCOUNT, NAME, BIRTH) values (?, ?, ?) Hibernate: insert into REVINFO (REVTSTMP) values (?) Hibernate: insert into PERSON_AUD (REVTYPE, ACCOUNT, NAME, BIRTH, ID, REV) values (?, ?, ?, ?, ?, ?) Hibernate: insert into PERSON_AUD (REVTYPE, ACCOUNT, NAME, BIRTH, ID, REV) values (?, ?, ?, ?, ?, ?) 六月 26, 2017 10:14:23 下午 org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService INFO: HHH000397: Using ASTQueryTranslatorFactory Hibernate: select person0_.ID as ID1_0_, person0_.ACCOUNT as ACCOUNT2_0_, person0_.NAME as NAME3_0_, person0_.BIRTH as BIRTH4_0_ from PERSON person0_ ------------------------------------------------------------------------------------------------------------------ INITIAL DATA : Person [id=1, account=tony, name=Tony Stark, birth=2017-06-26 22:14:23.0] Person [id=2, account=hulk, name=Banner, birth=2017-06-26 22:14:24.0] ------------------------------------------------------------------------------------------------------------------ Hibernate: select person0_.ID as ID1_0_0_, person0_.ACCOUNT as ACCOUNT2_0_0_, person0_.NAME as NAME3_0_0_, person0_.BIRTH as BIRTH4_0_0_ from PERSON person0_ where person0_.ID=? Hibernate: update PERSON set ACCOUNT=?, NAME=?, BIRTH=? where ID=? Hibernate: insert into REVINFO (REVTSTMP) values (?) Hibernate: insert into PERSON_AUD (REVTYPE, ACCOUNT, NAME, BIRTH, ID, REV) values (?, ?, ?, ?, ?, ?) Hibernate: select person0_.ID as ID1_0_0_, person0_.ACCOUNT as ACCOUNT2_0_0_, person0_.NAME as NAME3_0_0_, person0_.BIRTH as BIRTH4_0_0_ from PERSON person0_ where person0_.ID=? ------------------------------------------------------------------------------------------------------------------ UPDATED DATA : Person [id=2, account=hulk, name=Bruce Banner, birth=2017-06-26 22:14:24.0] ------------------------------------------------------------------------------------------------------------------ Hibernate: select person_aud0_.ID as ID1_1_, person_aud0_.REV as REV2_1_, person_aud0_.REVTYPE as REVTYPE3_1_, person_aud0_.ACCOUNT as ACCOUNT4_1_, person_aud0_.NAME as NAME5_1_, person_aud0_.BIRTH as BIRTH6_1_ from PERSON_AUD person_aud0_ where person_aud0_.REV=(select max(person_aud1_.REV) from PERSON_AUD person_aud1_ where person_aud1_.REV<=? and person_aud0_.ID=person_aud1_.ID) and person_aud0_.REVTYPE<>? and person_aud0_.ID=? Hibernate: select person_aud0_.ID as ID1_1_, person_aud0_.REV as REV2_1_, person_aud0_.REVTYPE as REVTYPE3_1_, person_aud0_.ACCOUNT as ACCOUNT4_1_, person_aud0_.NAME as NAME5_1_, person_aud0_.BIRTH as BIRTH6_1_ from PERSON_AUD person_aud0_ where person_aud0_.REV=(select max(person_aud1_.REV) from PERSON_AUD person_aud1_ where person_aud1_.REV<=? and person_aud0_.ID=person_aud1_.ID) and person_aud0_.REVTYPE<>? and person_aud0_.ID=? ------------------------------------------------------------------------------------------------------------------ First Version : Person [id=2, account=hulk, name=Banner, birth=2017-06-26 22:14:24.0] Second Version : Person [id=2, account=hulk, name=Bruce Banner, birth=2017-06-26 22:14:24.0] ------------------------------------------------------------------------------------------------------------------ 六月 26, 2017 10:14:24 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost:3306/test]
org.hibernate.envers.AuditReader
的 find
方法用於查詢實體對象的歷史數據,第一個參數是實體類,第二個參數爲實體類對象的惟一標識,第三個參數是版本號,從1開始。apache
Envers 自動生成的數據庫審計表及記錄以下:app