Hibernate Envers
Hibernate Envers entitylerinizin versiyonlanmasını sağlayan Hibernate ekibi tarafından geliştirilmiş bir projedir. Tek yapmanız gereken Hibernate Envers için gerekli configurasyonu yapmak ve entitylerinizi @Audited annotationı ile işaretlemektir.
Hibernate Envers Nasıl Kullanılır?
1)Spring Entegrasyonu
Hibernate Envers ile Spring 3 entegrasyonunu application-config.xml dosyasında gerçekleştiriyoruz.
application-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- Modules' Configuration Files --> <import resource="meslek-config.xml"/> <context:annotation-config /> <tx:annotation-driven transaction-manager="transactionManager" /> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- HIBERNATE CONFIGURATION START POINT --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"> <value>WEB-INF/database.properties</value> </property> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="sample.hibernateenvers.model" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9iDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> <property name="eventListeners"> <map key-type="java.lang.String" value-type="org.hibernate.event.EventListeners"> <entry key="post-insert" value-ref="auditEventListener"/> <entry key="post-update" value-ref="auditEventListener"/> <entry key="post-delete" value-ref="auditEventListener"/> <entry key="pre-collection-update" value-ref="auditEventListener"/> <entry key="pre-collection-remove" value-ref="auditEventListener"/> <entry key="post-collection-recreate" value-ref="auditEventListener"/> </map> </property> </bean> <bean id="auditEventListener" class="org.hibernate.envers.event.AuditEventListener"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${driverClassName}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </bean> <!-- HIBERNATE CONFIGURATION END POINT --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
Application-config.xml dosyasında Hibernate Envers ile ilgili olan configurasyon alanları,
<property name="eventListeners"> <map key-type="java.lang.String" value-type="org.hibernate.event.EventListeners"> <entry key="post-insert" value-ref="auditEventListener"/> <entry key="post-update" value-ref="auditEventListener"/> <entry key="post-delete" value-ref="auditEventListener"/> <entry key="pre-collection-update" value-ref="auditEventListener"/> <entry key="pre-collection-remove" value-ref="auditEventListener"/> <entry key="post-collection-recreate" value-ref="auditEventListener"/> </map> </property>
Hangi işlemlerin yapılacağı bildiriliyor. Örnegin satırı insert isleminden sonra versiyonlama işlemi yapılacağını bildiriyor. Versiyonlama işleminin nasıl yapılacağınada auditEventListener belirliyor. Eğer isterseniz loglamanın nasıl yapılacağına müdehale edebilirsiniz. Bunu Hibernate Envers’in püf noktalarında detaylıca inceleyeceğiz.
<bean id="auditEventListener" class="org.hibernate.envers.event.AuditEventListener"/>
Versiyonlama işleminin nasıl olacağını belirten eventListener classıdır. Burada kullanılan Hibernate Envers’in default org.hibernate.envers.event.AuditEventListener classıdır.
Configurasyon işleminden sonra Hibernate Envers’in entitylerinizin versiyonlarını tutabilmesi için yapmanız gereken tek işlem versiyonları tutulacak olan entity classını @Audited annotationı ile işaretlemektir.
Meslek.java classını inceleyelim
package sample.hibernateenvers.model; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.envers.Audited; /** * * Meslek bilgilerinin tutulduğu classtır. * * @version 1.0 23.05.2012 * * @author Musa YUVACI * */ @Entity @Table(name = "MESLEK") @Audited public class Meslek implements Serializable { /** * */ private static final long serialVersionUID = -1570951154709484999L; private Long id; private String ad; private String aciklama; @Id @GeneratedValue @Column(name = "ID", nullable = false, precision = 15, scale = 0) public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } @Column(name = "AD", length = 255, unique = true, nullable = false) public String getAd() { return this.ad; } public void setAd(String ad) { this.ad = ad; } @Column(name = "ACIKLAMA", length = 255) public String getAciklama() { return aciklama; } public void setAciklama(String aciklama) { this.aciklama = aciklama; } @Override public String toString() { return "sample.hibernateenvers.model.Meslek[id=" + id + "]"; } @Override public boolean equals(Object obj) { if(obj != null && ((Meslek) obj).getId() != null) { if ((obj instanceof Meslek) && (this.getId().longValue() == ((Meslek) obj).getId().longValue())) { return true; } } return false; } }
Veri Tabanındaki MESLEK tablosu ise,
ID | AD | ACIKLAMA |
---|---|---|
yapısındadır. Hibernate Envers Meslek entitysini versiyonlamak için veri tabanına MESLEK_AUD ve REVINFO adinda iki yeni tablo ekliyor (tablo isimleri ve alanlari default olarak belirleniyor, istenilirse üzerinde değişiklik yapılabilir).
Meslek Entitysi İçin Versiyonlama(Log Bilgilerinin Tutulması) Örneği;
1.Adım (İnsert İşlemi)
MESLEK Tablosu
ID | AD | ACIKLAMA |
---|---|---|
1 | Öğretmen | Milli Eğitim Bakanlığı X Lisesi |
olarak bir insert işlemi gerçekleştirildiğinde. Hibernate Envers,
MESLEK_AUD Tablosu
ID | REVISION | REVTYPE | AD | ACIKLAMA |
---|---|---|---|---|
1 | 1 | 0 | Öğretmen | Milli Eğitim Bakanlığı X Lisesi |
REVINFO Tablosu
REV | REVTSTMP |
---|---|
1 | 1337872346190 |
olarak versiyonlama islemini gerçekleştirir. MESLEK_AUD tablosunda ki REV alanı işlemin hangi revisionda yapıldığı bilgisini tutar, REVTYPE bilgisi ise ne işlemi yapıldığını bildirir (0:insert 1:update 2:delete). REVINFO tablosunda ise hangi revisionun ne zaman yapildigi bilgisi REVTSTMP alanında TIMESTAMP olarak tutulur (Burada Hibernate Envers tarafından yapılan versiyonlama işlemlerinin her noktasına müdehale edebilirsiniz, yazının ilerleyen kısımlarında tek tek ele alacağız).
2.Adım (Update İşlemi)
Birinci adımında veri tabanına kaydetmiş olduğumuz 1 idli veriyi update işlemi uyguladığımız da sonuç
MESLEK Tablosu
ID | AD | ACIKLAMA |
---|---|---|
1 | Öğretmen | MEB Kadıköy İlköğretim Okulu |
MESLEK_AUD Tablosu
ID | REVISION | REVTYPE | AD | ACIKLAMA |
---|---|---|---|---|
1 | 1 | 0 | Öğretmen | Milli Eğitim Bakanlığı X Lisesi |
1 | 2 | 1 | Öğretmen | MEB Kadıköy İlköğretim Okulu |
REVINFO Tablosu
REV | REVTSTMP |
---|---|
1 | 1337872346190 |
2 | 1337873103035 |
şeklinde olur. Hibernate Envers update işleminin logunu tutmuştur.
3.Adım (Delete İşlemi)
Veri tabanında bulunan 1 id li kaydı silelim.
MESLEK Tablosu
ID | AD | ACIKLAMA |
---|---|---|
MESLEK_AUD Tablosu
ID | REVISION | REVTYPE | AD | ACIKLAMA |
---|---|---|---|---|
1 | 1 | 0 | Öğretmen | Milli Eğitim Bakanlığı X Lisesi |
1 | 2 | 1 | Öğretmen | MEB Kadıköy İlköğretim Okulu |
1 | 3 | 2 | null | null |
Not: Silme işleminden sonra MESLEK_AUD tablosuna ID = 1, REV = 3, REVTYPE = 2 (silme işlemini ifade eder) bilgileri olan ve diğer bilgileri null olan bir kayıt eklendi. Hibernate Envers’in default configurasyonunda silinen entitynin sadece id si tutulur ama siz isterseniz bütün verilerini tutabilirsiniz, yazının ilerleyen kısımlarında işlemin nasıl yapılacağına detaylıca yer vereceğiz.
REVINFO Tablosu
REV | REVTSTMP |
---|---|
1 | 1337872346190 |
2 | 1337873103035 |
3 | 1337935377652 |
Buraya kadar yapmış olduğumuz işlemler, sadece Hibernate Envers ile Spring Configurasyonunun yapılıp, nesnenin @Audited annotationu ile işaretlenmesi sonucunda Hibernate Envers in default olarak gerçekleştirdiği işlemlerdir. Biraz daha detaya inelim.
DETAYLAR
1) Entity İçerisinde Herhangi Bir Variablenin Versiyon Bilgisi İçersinde Yer AlMAMAsının Sağlanması.
Versiyonu tutulmak istenen entitynin içerisindeki herhangi bir variablenin versiyon bilgisi tutulması istenmeyebilir veya geek olmayabilir. Bu gibi durumlarda ilgili variablenin @NotAudited annotationu ile işaretlenmesi yeterlidir.
2) Tutulan Entity Revision Bilgisinin İhtiyaçlar Doğrultunsuda Değiştirilmesi.
Hibenate Envers default olarak REVINFO tablosunda, yapılan işlemin revision numarasını ve yapılış zamanının bilgisini tutar. İhtiyaçlar doğrultusunda bu işleyiş modifiye edilebilir. Öncelikle iki adet class olusturulması gerekmektedir. Bunlar;
CustomRevisionListener.java
package sample.hibernateenvers.model; import org.hibernate.envers.RevisionListener; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.security.core.context.SecurityContextHolder; @Configurable public class CustomEnversListener implements RevisionListener { @Override public void newRevision(Object revisionEntity) { CustomRevisionEntity customRevisionEntity = (CustomRevisionEntity) revisionEntity; /* * authentication bilgisi aliniyor. Kullanilan authemntication sistemine * gore user bilgisi almak farklilik gosterebilir. */ String userName = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); customRevisionEntity.setUsername(userName); } }
CustomRevisionListener.java classı org.hibernate.envers.RevisionListener interface ini implement edip, newRevision metodunu implement ediyor. Bu sayede entity üzerinde işlem yapıldığı zaman newRevision metodunda belirtilen şekilde revisionu tutuluyor. Bunun içinde CustomRevsionEntity.java classını olusturmak gerekiyor.
CustomRevsionEntity.java
package sample.hibernateenvers.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.hibernate.envers.RevisionEntity; import org.hibernate.envers.RevisionNumber; import org.hibernate.envers.RevisionTimestamp; @Entity @RevisionEntity(CustomEnversListener.class) @Table(name="REVISION") public class CustomRevisionEntity { private int id; private long timestamp; private String username; @Id @GeneratedValue @RevisionNumber @Column(name="REVISION_ID") public int getId() { return id; } public void setId(int id) { this.id = id; } @RevisionTimestamp @Column(name="REVISION_TIME") public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } @Column(name="USER_NAME") public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
CustomRevisionEntity.java classında Hibernate Envers’in default olarak REVINFO olarak isimlendirdiği database tablosu modifiye edilir ve revision için hangi bilgilerin tutulacağı belirlenir. RevisionNumber ve RevisionTimestamp variablelerini koyup annotationlar ile işaretlediten sonra, tutmak istediğiniz diğer bilgiler için variable oluşturanbilirsiniz. CustomRevisionEntity.java revision için username bilgisi tutuyoruz. Yapmamız gereken sadece username variablesini tanımlayıp getter ve setter ını oluşturmak. İsterseniz CustomRevisionEntity.java classını @Table annontationı ile işaretleyerek istediğiniz bir ismi (Veri tabanında karşılık gelecek tablo ismi) verebilirsiniz. Burada @Table(name=”REVISION”) olarak kullanıldı. Ayrıca tablonun kolonlarını da isimlendirebilirsiniz, bunu da @Column(name = “REVISION_ID”) olarak CustomRevisionEntity.java classında görebilirsiniz.
Not: Burada Dikkat edilmesi gereken konu, oluşturulmuş olan classlar Spring Configurasyonunda belirtilen
<property name="packagesToScan" value="sample.hibernateenvers.model" />
packagenin altında olmalıdır.
Sonuç : CustomRevisionListener.java ve CustomRevisionEntity.java classları ile entitynin revision bilgisinin nasıl tutalmasını gerektiğini belirttik.
3) Silinen Entitylerin Silinmeden Önceki Verilerinin Saklanmasının Sağlanması.
Hibernate Envers default olarak silinen bir entity nin sadece id bilgisini tutar. Audit tablosuna (Örneğin MESLEK_AUD) entitynin sadece id sini, revison numarasını ve revision type bilgisini koyar, nesnenin diğer bilgilerini null olarak koyar. Eğer nesnenin silinmeden önceki bütün bilgilerine tutmak istiyorsanız. Hibernate Envers Configurasyonuna
<prop key="org.hibernate.envers.store_data_at_delete">true</prop>
satırını eklemeniz gerekmektedir. Bu sayede silinen entitynin en son bilgileride audit tablosunda yer alır. Aşağıdaki Spring Entegrasyonunda satırın nereye ekleneceği belirtilmiştir.
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="sample.hibernateenvers.model" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9iDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> <prop key="org.hibernate.envers.store_data_at_delete">true</prop> </props> </property> <property name="eventListeners"> <map key-type="java.lang.String" value-type="org.hibernate.event.EventListeners"> <entry key="post-insert" value-ref="auditEventListener"/> <entry key="post-update" value-ref="auditEventListener"/> <entry key="post-delete" value-ref="auditEventListener"/> <entry key="pre-collection-update" value-ref="auditEventListener"/> <entry key="pre-collection-remove" value-ref="auditEventListener"/> <entry key="post-collection-recreate" value-ref="auditEventListener"/> </map> </property> </bean>
4) Hibernate Envers’in Versiyonlama İşlemine Müdehale Etmek.
Hibernate Envers default olarak AuditEventListener.java classı ile versiyonlama işlemini gerçekleştirir. İhtiyaç durumunda kendi XEventListener.java classını yazıp Hibernate Envers e bu classı kullanmasını söyleyebilirsiniz.
Bunun için;
CustomAuditEventListener.java
package sample.hibernateenvers.util; import org.hibernate.envers.event.AuditEventListener; import org.hibernate.event.PostDeleteEvent; import org.hibernate.event.PostInsertEvent; import org.hibernate.event.PostUpdateEvent; public class CustomAuditEventListener extends AuditEventListener { /** * */ private static final long serialVersionUID = 8077145687034936513L; @Override public void onPostDelete(PostDeleteEvent arg0) { super.onPostDelete(arg0); } @Override public void onPostInsert(PostInsertEvent arg0) { super.onPostInsert(arg0); } @Override public void onPostUpdate(PostUpdateEvent arg0) { super.onPostUpdate(arg0); } }
CustomAuditEventListener.java classı Hibernate Envers’in org.hibernate.envers.event.AuditEventListener classını extend ediyor. Gerekli olan metodları override ediyor. Buradaki örnekte insert, update ve delete metodları override edilmiştir. Versiyonlama işlemine bu şekilde müdehale edilebilir. Bu şekilde daha detaylı işlemler yapablirsiniz örneğin versiyonlanacak olan entitynin bilgilerine müdehale edebilirsiniz. Hibenate Envers’in bu versiyonama işleminde CustomAuditEventListener.java classını kullanması için Spring configurasyonunda bulunan
<bean id="auditEventListener" class="org.hibenate.envers.event.AuditEventListener"/>
satırının
<bean id="auditEventListener" class="sample.hibernateenvers.util.CustomAuditEventListener"/>
olacak şekilde güncellenmesi gerekiyor.
Kaynak Kodlar ve Açıklama
Hibernate Envers projelerinin kaynak kodlarını aşağıdaki linklerden download edebilirsiniz. Kaynak kodları eclipse (SpringSourse Tool Suite) projesi olarak verilmiştir. Hibernate Envers Sample v1.0 Hibernate Envers’in default configurasyonlu halini içerir. Hibernate Envers Sample v2.0 ise yazıda “detaylar” başlığı altında yapılan configurasyon değişikliklerinin uygulamasına sahiptir. Uygulamaları çalıştırmak için öncelikle uygulama içerisindeki build.xml ant scriptini çalıştırmalısınız (hsqldb için veri tabanı start işlemini gerçekleştiriyor). Ardından test klasorunun altındaki sample.hibernateenvers.test.persistence.MeslekDAOTest.java classındaki yazılmış olan unit testleri kullanarak Hibernate Envers uygulamalarını test edebilirsiniz.
Hibernate Envers Sample v1.0
Hibernate Envers Sample v2.0
Sonuç
Hibernate Envers ile entitylerinizin otomatik olarak versiyonlarının tutulması(loglanması) işlemi gerçekleştirilmiş oldu. Tutulmuş olan entity versiyon bilgilerinin(Entity Loglarının) sorgulanmasını bir sonraki yazıda inceleyeceğiz. Hibernate Envers ile ilgili daha detaylı bilgi için referans dökümantasyonunu inceleyebilirsiniz.