Etiket arşivi: Hibernate Envers Configuration

Hibernate Envers

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,

IDADACIKLAMA

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

IDADACIKLAMA
1ÖğretmenMilli Eğitim Bakanlığı X Lisesi

olarak bir insert işlemi gerçekleştirildiğinde. Hibernate Envers,

MESLEK_AUD Tablosu

IDREVISIONREVTYPEADACIKLAMA
110ÖğretmenMilli Eğitim Bakanlığı X Lisesi

REVINFO Tablosu
REVREVTSTMP
11337872346190

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

IDADACIKLAMA
1ÖğretmenMEB Kadıköy İlköğretim Okulu

MESLEK_AUD Tablosu
IDREVISIONREVTYPEADACIKLAMA
110ÖğretmenMilli Eğitim Bakanlığı X Lisesi
1 21ÖğretmenMEB Kadıköy İlköğretim Okulu

REVINFO Tablosu
REVREVTSTMP
11337872346190
21337873103035

ş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

IDADACIKLAMA

MESLEK_AUD Tablosu
IDREVISIONREVTYPEADACIKLAMA
110ÖğretmenMilli Eğitim Bakanlığı X Lisesi
1 21ÖğretmenMEB Kadıköy İlköğretim Okulu
132nullnull

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

REVREVTSTMP
11337872346190
21337873103035
31337935377652

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.

Hibenrate Envers 3.6 Referans Dökümantasyonu