舉例來說,如果我們想要記錄每筆資料是誰在什麼時間產生,又是誰在什麼時間做了修改,如果在每個DAO中去處理這件事情就需要花相當的力氣,而Hibernate Interceptor會是個不錯的選擇。
以下我們就實作這樣的例子,程式分為三大部分:
- 一組攔截generic property的Hibernate Interceptor API
- 記錄誰在什麼時間更新資料的API
- 設定與程式套用範例
Part I
EmbeddablePropertyInterceptor
package com.gss.gmo.cao.hibernate.interceptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.springframework.util.ReflectionUtils;
/**
* @author linus_chien
*
* @param <Embeddable>
*/
@EqualsAndHashCode(of = "embeddableClass", callSuper = false)
@ToString(of = "embeddableClass")
public abstract class EmbeddablePropertyInterceptor<Embeddable> extends EmptyInterceptor {
private static final long serialVersionUID = 1L;
private Class<Embeddable> embeddableClass;
@SuppressWarnings("unchecked")
public EmbeddablePropertyInterceptor() {
java.lang.reflect.Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
embeddableClass = (Class<Embeddable>) pt.getActualTypeArguments()[0];
}
/**
* @return new Embeddable instance.
*/
@SneakyThrows
private Embeddable newInstance() {
return embeddableClass.newInstance();
}
@SuppressWarnings("unchecked")
private Embeddable findEmbeddableProperty(Object entity, Object[] state, String[] propertyNames) {
for (int i = 0; i < propertyNames.length; i++) {
String propertyName = propertyNames[i];
Field auditInfoField = ReflectionUtils.findField(entity.getClass(), propertyName, embeddableClass);
if (auditInfoField != null) {
if (state[i] == null) {
state[i] = newInstance();
}
return (Embeddable) state[i];
}
}
return null;
}
@Override
public final boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
Embeddable embeddable = findEmbeddableProperty(entity, state, propertyNames);
if (embeddable != null) {
takeCreateEvent(embeddable);
return true;
} else {
return false;
}
}
protected abstract void takeCreateEvent(Embeddable embeddable);
@Override
public final boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
Embeddable embeddable = findEmbeddableProperty(entity, currentState, propertyNames);
if (embeddable != null) {
takeModifiedEvent(embeddable);
return true;
} else {
return false;
}
}
protected abstract void takeModifiedEvent(Embeddable embeddable);
}
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.springframework.util.ReflectionUtils;
/**
* @author linus_chien
*
* @param <Embeddable>
*/
@EqualsAndHashCode(of = "embeddableClass", callSuper = false)
@ToString(of = "embeddableClass")
public abstract class EmbeddablePropertyInterceptor<Embeddable> extends EmptyInterceptor {
private static final long serialVersionUID = 1L;
private Class<Embeddable> embeddableClass;
@SuppressWarnings("unchecked")
public EmbeddablePropertyInterceptor() {
java.lang.reflect.Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
embeddableClass = (Class<Embeddable>) pt.getActualTypeArguments()[0];
}
/**
* @return new Embeddable instance.
*/
@SneakyThrows
private Embeddable newInstance() {
return embeddableClass.newInstance();
}
@SuppressWarnings("unchecked")
private Embeddable findEmbeddableProperty(Object entity, Object[] state, String[] propertyNames) {
for (int i = 0; i < propertyNames.length; i++) {
String propertyName = propertyNames[i];
Field auditInfoField = ReflectionUtils.findField(entity.getClass(), propertyName, embeddableClass);
if (auditInfoField != null) {
if (state[i] == null) {
state[i] = newInstance();
}
return (Embeddable) state[i];
}
}
return null;
}
@Override
public final boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
Embeddable embeddable = findEmbeddableProperty(entity, state, propertyNames);
if (embeddable != null) {
takeCreateEvent(embeddable);
return true;
} else {
return false;
}
}
protected abstract void takeCreateEvent(Embeddable embeddable);
@Override
public final boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
Embeddable embeddable = findEmbeddableProperty(entity, currentState, propertyNames);
if (embeddable != null) {
takeModifiedEvent(embeddable);
return true;
} else {
return false;
}
}
protected abstract void takeModifiedEvent(Embeddable embeddable);
}
EmbeddablePropertyInterceptorChainProxy
package com.gss.gmo.cao.hibernate.interceptor;
import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;
import lombok.extern.apachecommons.CommonsLog;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
@CommonsLog
public class EmbeddablePropertyInterceptorChainProxy extends EmptyInterceptor {
private static final long serialVersionUID = 5566719359520070978L;
private Set<EmbeddablePropertyInterceptor<?>> interceptorChain = new LinkedHashSet<EmbeddablePropertyInterceptor<?>>();
public void setInterceptorChain(Set<EmbeddablePropertyInterceptor<?>> interceptorChain) {
this.interceptorChain.addAll(interceptorChain);
}
public void addInterceptorChain(EmbeddablePropertyInterceptor<?> interceptor) {
interceptorChain.add(interceptor);
}
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
boolean isStateChanged = false;
for (EmbeddablePropertyInterceptor<?> interceptor : interceptorChain) {
log.debug("invoke onFlushDirty of " + interceptor.toString());
if (interceptor.onFlushDirty(entity, id, currentState, previousState, propertyNames, types)) {
isStateChanged = true;
}
}
return isStateChanged;
}
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
boolean isStateChanged = false;
for (EmbeddablePropertyInterceptor<?> interceptor : interceptorChain) {
log.debug("invoke onSave of " + interceptor.toString());
if (interceptor.onSave(entity, id, state, propertyNames, types)) {
isStateChanged = true;
}
}
return isStateChanged;
}
}
小結:import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;
import lombok.extern.apachecommons.CommonsLog;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
@CommonsLog
public class EmbeddablePropertyInterceptorChainProxy extends EmptyInterceptor {
private static final long serialVersionUID = 5566719359520070978L;
private Set<EmbeddablePropertyInterceptor<?>> interceptorChain = new LinkedHashSet<EmbeddablePropertyInterceptor<?>>();
public void setInterceptorChain(Set<EmbeddablePropertyInterceptor<?>> interceptorChain) {
this.interceptorChain.addAll(interceptorChain);
}
public void addInterceptorChain(EmbeddablePropertyInterceptor<?> interceptor) {
interceptorChain.add(interceptor);
}
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
boolean isStateChanged = false;
for (EmbeddablePropertyInterceptor<?> interceptor : interceptorChain) {
log.debug("invoke onFlushDirty of " + interceptor.toString());
if (interceptor.onFlushDirty(entity, id, currentState, previousState, propertyNames, types)) {
isStateChanged = true;
}
}
return isStateChanged;
}
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
boolean isStateChanged = false;
for (EmbeddablePropertyInterceptor<?> interceptor : interceptorChain) {
log.debug("invoke onSave of " + interceptor.toString());
if (interceptor.onSave(entity, id, state, propertyNames, types)) {
isStateChanged = true;
}
}
return isStateChanged;
}
}
在EmbeddablePropertyInterceptor裡面我們留了兩個abstract method,讓developer各自實作。
EmbeddablePropertyInterceptorChainProxy是為了可以攔截不同的property,但限制是type不能重複。
Part II
AuditInterceptor
package com.gss.gmo.cao.hibernate.audit;
import java.util.Date;
import org.springframework.beans.factory.annotation.Required;
import com.gss.gmo.cao.hibernate.interceptor.EmbeddablePropertyInterceptor;
/**
* Auto-set values of createUserId, createDate, lastModifiedUserId and
* lastModifiedDate.
*
* @author linus_chien
*
*/
public class AuditInterceptor extends EmbeddablePropertyInterceptor<AuditInfo> {
private static final long serialVersionUID = 1L;
/**
* Provide audit user id.
*/
private AuditUserIdAware auditUserIdAware;
@Required
public void setAuditUserIdAware(AuditUserIdAware auditUserIdAware) {
this.auditUserIdAware = auditUserIdAware;
}
@Override
protected void takeCreateEvent(AuditInfo auditInfo) {
auditInfo.setCreateUserId(auditUserIdAware.getAuditUserId());
auditInfo.setCreateUserName(auditUserIdAware.getAuditUserName());
auditInfo.setCreateUserEnglishName(auditUserIdAware.getAuditUserEnglishName());
auditInfo.setCreateDate(new Date());
}
@Override
protected void takeModifiedEvent(AuditInfo auditInfo) {
auditInfo.setLastModifiedUserId(auditUserIdAware.getAuditUserId());
auditInfo.setLastModifiedUserName(auditUserIdAware.getAuditUserName());
auditInfo.setLastModifiedUserEnglishName(auditUserIdAware.getAuditUserEnglishName());
auditInfo.setLastModifiedDate(new Date());
}
}
import java.util.Date;
import org.springframework.beans.factory.annotation.Required;
import com.gss.gmo.cao.hibernate.interceptor.EmbeddablePropertyInterceptor;
/**
* Auto-set values of createUserId, createDate, lastModifiedUserId and
* lastModifiedDate.
*
* @author linus_chien
*
*/
public class AuditInterceptor extends EmbeddablePropertyInterceptor<AuditInfo> {
private static final long serialVersionUID = 1L;
/**
* Provide audit user id.
*/
private AuditUserIdAware auditUserIdAware;
@Required
public void setAuditUserIdAware(AuditUserIdAware auditUserIdAware) {
this.auditUserIdAware = auditUserIdAware;
}
@Override
protected void takeCreateEvent(AuditInfo auditInfo) {
auditInfo.setCreateUserId(auditUserIdAware.getAuditUserId());
auditInfo.setCreateUserName(auditUserIdAware.getAuditUserName());
auditInfo.setCreateUserEnglishName(auditUserIdAware.getAuditUserEnglishName());
auditInfo.setCreateDate(new Date());
}
@Override
protected void takeModifiedEvent(AuditInfo auditInfo) {
auditInfo.setLastModifiedUserId(auditUserIdAware.getAuditUserId());
auditInfo.setLastModifiedUserName(auditUserIdAware.getAuditUserName());
auditInfo.setLastModifiedUserEnglishName(auditUserIdAware.getAuditUserEnglishName());
auditInfo.setLastModifiedDate(new Date());
}
}
AuditUserIdAware
package com.gss.gmo.cao.hibernate.audit;
import java.io.Serializable;
/**
* @author linus_chien
*
*/
public interface AuditUserIdAware extends Serializable {
/**
* @return user id for auditing.
*/
String getAuditUserId();
/**
* @return user name for auditing.
*/
String getAuditUserName();
/**
* @return user English name for auditing.
*/
String getAuditUserEnglishName();
}
import java.io.Serializable;
/**
* @author linus_chien
*
*/
public interface AuditUserIdAware extends Serializable {
/**
* @return user id for auditing.
*/
String getAuditUserId();
/**
* @return user name for auditing.
*/
String getAuditUserName();
/**
* @return user English name for auditing.
*/
String getAuditUserEnglishName();
}
AuditInfo
package com.gss.gmo.cao.hibernate.audit;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author linus_chien
*
*/
@Getter
@Setter
@ToString
@Embeddable
public class AuditInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* column length.
*/
private static final int LENGTH = 50;
/**
* create user id.
*/
@Column(name = "CREATE_USER_ID", length = LENGTH)
private String createUserId;
/**
* create user name.
*/
@Column(name = "CREATE_USER_NAME", length = LENGTH)
private String createUserName;
/**
* create user English name.
*/
@Column(name = "CREATE_USER_ENGLISH_NAME", length = LENGTH)
private String createUserEnglishName;
/**
* create date.
*/
@Column(name = "CREATE_DATE")
private Date createDate;
/**
* last modified user id.
*/
@Column(name = "LAST_MODIFIED_USER_ID", length = LENGTH)
private String lastModifiedUserId;
/**
* last modified user name.
*/
@Column(name = "LAST_MODIFIED_USER_NAME", length = LENGTH)
private String lastModifiedUserName;
/**
* last modified English name.
*/
@Column(name = "LAST_MODIFIED_USER_ENGLISH_NAME", length = LENGTH)
private String lastModifiedUserEnglishName;
/**
* last modified date.
*/
@Column(name = "LAST_MODIFIED_DATE")
private Date lastModifiedDate;
}
小結:import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author linus_chien
*
*/
@Getter
@Setter
@ToString
@Embeddable
public class AuditInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* column length.
*/
private static final int LENGTH = 50;
/**
* create user id.
*/
@Column(name = "CREATE_USER_ID", length = LENGTH)
private String createUserId;
/**
* create user name.
*/
@Column(name = "CREATE_USER_NAME", length = LENGTH)
private String createUserName;
/**
* create user English name.
*/
@Column(name = "CREATE_USER_ENGLISH_NAME", length = LENGTH)
private String createUserEnglishName;
/**
* create date.
*/
@Column(name = "CREATE_DATE")
private Date createDate;
/**
* last modified user id.
*/
@Column(name = "LAST_MODIFIED_USER_ID", length = LENGTH)
private String lastModifiedUserId;
/**
* last modified user name.
*/
@Column(name = "LAST_MODIFIED_USER_NAME", length = LENGTH)
private String lastModifiedUserName;
/**
* last modified English name.
*/
@Column(name = "LAST_MODIFIED_USER_ENGLISH_NAME", length = LENGTH)
private String lastModifiedUserEnglishName;
/**
* last modified date.
*/
@Column(name = "LAST_MODIFIED_DATE")
private Date lastModifiedDate;
}
讓Hibernate Interceptor幫我們攔截AuditInfo;AuditUserIdAware是為了loose coupling,不讓取得使用者資訊的程式碼出現在AuditInterceptor裡面。
Part III
Person
package com.gss.gmo.cao.hibernate.impl;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Type;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.hibernate.audit.AuditInfo;
/**
* @author linus_chien
*
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = { "id" })
@ToString
@Entity
@Filter(name = "personFilter", condition = "name like :name and address is null")
public class Person extends BaseDto {
private static final long serialVersionUID = 1L;
@Id
private String id;
@Column
private String name;
@Column
private String address;
@Embedded
private AuditInfo auditInfo;
@Type(type = "listType")
private List<String> emails;
}
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Type;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.hibernate.audit.AuditInfo;
/**
* @author linus_chien
*
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = { "id" })
@ToString
@Entity
@Filter(name = "personFilter", condition = "name like :name and address is null")
public class Person extends BaseDto {
private static final long serialVersionUID = 1L;
@Id
private String id;
@Column
private String name;
@Column
private String address;
@Embedded
private AuditInfo auditInfo;
@Type(type = "listType")
private List<String> emails;
}
LoginUserIdProvider
package com.gss.gmo.cao.hibernate.impl;
import com.gss.gmo.cao.hibernate.audit.AuditUserIdAware;
public class LoginUserIdProvider implements AuditUserIdAware {
private static final long serialVersionUID = -5133132384619280103L;
@Override
public String getAuditUserId() {
return "linus_chien";
}
@Override
public String getAuditUserName() {
return "Cheng-Ming Chien";
}
@Override
public String getAuditUserEnglishName() {
return "Linus";
}
}
import com.gss.gmo.cao.hibernate.audit.AuditUserIdAware;
public class LoginUserIdProvider implements AuditUserIdAware {
private static final long serialVersionUID = -5133132384619280103L;
@Override
public String getAuditUserId() {
return "linus_chien";
}
@Override
public String getAuditUserName() {
return "Cheng-Ming Chien";
}
@Override
public String getAuditUserEnglishName() {
return "Linus";
}
}
SpringConfig
package com.gss.gmo.cao.hibernate.impl;
import org.hibernate.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.gss.gmo.cao.hibernate.audit.AuditInterceptor;
import com.gss.gmo.cao.hibernate.interceptor.EmbeddablePropertyInterceptorChainProxy;
@Configuration
public class SpringConfig {
@Bean
public Interceptor embeddablePropertyInterceptorChainProxy() {
EmbeddablePropertyInterceptorChainProxy interceptor = new EmbeddablePropertyInterceptorChainProxy();
AuditInterceptor auditInterceptor = new AuditInterceptor();
auditInterceptor.setAuditUserIdAware(new LoginUserIdProvider());
interceptor.addInterceptorChain(auditInterceptor);
return interceptor;
}
}
import org.hibernate.Interceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.gss.gmo.cao.hibernate.audit.AuditInterceptor;
import com.gss.gmo.cao.hibernate.interceptor.EmbeddablePropertyInterceptorChainProxy;
@Configuration
public class SpringConfig {
@Bean
public Interceptor embeddablePropertyInterceptorChainProxy() {
EmbeddablePropertyInterceptorChainProxy interceptor = new EmbeddablePropertyInterceptorChainProxy();
AuditInterceptor auditInterceptor = new AuditInterceptor();
auditInterceptor.setAuditUserIdAware(new LoginUserIdProvider());
interceptor.addInterceptorChain(auditInterceptor);
return interceptor;
}
}
Spring XML
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="packagesToScan" value="com.gss.gmo.cao.hibernate.impl" />
<property name="annotatedPackages" value="com.gss.gmo.cao.hibernate.impl" />
<property name="entityInterceptor" ref="embeddablePropertyInterceptorChainProxy" />
</bean>
小結:<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="packagesToScan" value="com.gss.gmo.cao.hibernate.impl" />
<property name="annotatedPackages" value="com.gss.gmo.cao.hibernate.impl" />
<property name="entityInterceptor" ref="embeddablePropertyInterceptorChainProxy" />
</bean>
LoginUserIdProvider裡面可以和Spring Security整合,取得目前登入的使用者資訊。