但這樣還不夠好,原因是這樣的使用方式會讓DAO裡面充滿enable filter的程式碼,developer必須了解這些filter的規範才能正確使用,如果我們會依照使用者的身份決定condition的值,那麼與security有關的API就會汙染DAO,為此,我們利用Spring AOP的功能來完成enable filter dynamically的機制,讓interceptor來決定目前被攔截的DAO是否需要enable filter並傳入condition變數。
以下的程式碼分為兩大部分,interceptor and test case。
Interceptor:
Interceptor:
EnableFilterInterceptor
package com.gss.gmo.cao.hibernate.filter;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import lombok.SneakyThrows;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.util.ReflectionUtils;
import com.gss.gmo.cao.hibernate.HibernateGenericDao;
import com.gss.gmo.cao.hibernate.HibernateGenericPagingDao;
/**
* @author linus_chien
*
*/
public class EnableFilterInterceptor implements MethodInterceptor {
/**
* Provide filter name and parameters to enable.
*/
private EnableFilterInfoAware enableFilterInfoAware;
/**
* @param enableFilterInfoAware
*/
@Required
public void setEnableFilterInfoAware(EnableFilterInfoAware enableFilterInfoAware) {
this.enableFilterInfoAware = enableFilterInfoAware;
}
@Override
@SneakyThrows
public Object invoke(MethodInvocation invocation) {
Object target = invocation.getThis();
if (target instanceof HibernateGenericDao || target instanceof HibernateGenericPagingDao) {
SessionFactory sessionFactory = getSessionFactory(target);
Session session = sessionFactory.getCurrentSession();
@SuppressWarnings("unchecked")
Set<String> filterNames = sessionFactory.getDefinedFilterNames();
Map<String, Map<String, Object>> filterInfo = enableFilterInfoAware.getEnableFilterInfo();
for (String filterName : filterNames) {
if (filterInfo.containsKey(filterName)) {
Filter filter = session.getEnabledFilter(filterName);
if (filter == null) {
filter = session.enableFilter(filterName);
Map<String, Object> parameters = filterInfo.get(filterName);
for (String parameterName : parameters.keySet()) {
Object parameterValue = parameters.get(parameterName);
filter.setParameter(parameterName, parameterValue);
}
filter.validate();
}
}
}
}
Object result = invocation.proceed();
return result;
}
/**
* @param target
* @return SessionFactory
*/
private SessionFactory getSessionFactory(Object target) {
Field sessionFactoryField = ReflectionUtils.findField(target.getClass(), "sessionFactory", SessionFactory.class);
ReflectionUtils.makeAccessible(sessionFactoryField);
return (SessionFactory) ReflectionUtils.getField(sessionFactoryField, target);
}
}
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
import lombok.SneakyThrows;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.util.ReflectionUtils;
import com.gss.gmo.cao.hibernate.HibernateGenericDao;
import com.gss.gmo.cao.hibernate.HibernateGenericPagingDao;
/**
* @author linus_chien
*
*/
public class EnableFilterInterceptor implements MethodInterceptor {
/**
* Provide filter name and parameters to enable.
*/
private EnableFilterInfoAware enableFilterInfoAware;
/**
* @param enableFilterInfoAware
*/
@Required
public void setEnableFilterInfoAware(EnableFilterInfoAware enableFilterInfoAware) {
this.enableFilterInfoAware = enableFilterInfoAware;
}
@Override
@SneakyThrows
public Object invoke(MethodInvocation invocation) {
Object target = invocation.getThis();
if (target instanceof HibernateGenericDao || target instanceof HibernateGenericPagingDao) {
SessionFactory sessionFactory = getSessionFactory(target);
Session session = sessionFactory.getCurrentSession();
@SuppressWarnings("unchecked")
Set<String> filterNames = sessionFactory.getDefinedFilterNames();
Map<String, Map<String, Object>> filterInfo = enableFilterInfoAware.getEnableFilterInfo();
for (String filterName : filterNames) {
if (filterInfo.containsKey(filterName)) {
Filter filter = session.getEnabledFilter(filterName);
if (filter == null) {
filter = session.enableFilter(filterName);
Map<String, Object> parameters = filterInfo.get(filterName);
for (String parameterName : parameters.keySet()) {
Object parameterValue = parameters.get(parameterName);
filter.setParameter(parameterName, parameterValue);
}
filter.validate();
}
}
}
}
Object result = invocation.proceed();
return result;
}
/**
* @param target
* @return SessionFactory
*/
private SessionFactory getSessionFactory(Object target) {
Field sessionFactoryField = ReflectionUtils.findField(target.getClass(), "sessionFactory", SessionFactory.class);
ReflectionUtils.makeAccessible(sessionFactoryField);
return (SessionFactory) ReflectionUtils.getField(sessionFactoryField, target);
}
}
EnableFilterInfoAware
package com.gss.gmo.cao.hibernate.filter;
import java.util.Map;
/**
* @author linus_chien
*
*/
public interface EnableFilterInfoAware {
/**
* Key: filter name, Value: parameters (Key: parameter name, Value: value).
*
* @return
*/
Map<String, Map<String, Object>> getEnableFilterInfo();
}
EnableFilterInfoAware的用意事實上就是Strategy Pattern的一種應用,再一次將怎麼enable filter這件事交給strategy object負責。import java.util.Map;
/**
* @author linus_chien
*
*/
public interface EnableFilterInfoAware {
/**
* Key: filter name, Value: parameters (Key: parameter name, Value: value).
*
* @return
*/
Map<String, Map<String, Object>> getEnableFilterInfo();
}
Test Case包含四小部分:
- Filter Definition:package-info.java
- Filter:Person
- Implement Strategy:EnableFilterInfoProvider
- Setup AOP:Spring XML
package-info.java
/**
* @author linus_chien
*
*/
@FilterDef(name = "personFilter", parameters = { @ParamDef(name = "name", type = "string"), @ParamDef(name = "address", type = "string") })
@TypeDef(name = "listType", typeClass = ListJsonType.class)
package com.gss.gmo.cao.hibernate.impl;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;
import org.hibernate.annotations.TypeDef;
* @author linus_chien
*
*/
@FilterDef(name = "personFilter", parameters = { @ParamDef(name = "name", type = "string"), @ParamDef(name = "address", type = "string") })
@TypeDef(name = "listType", typeClass = ListJsonType.class)
package com.gss.gmo.cao.hibernate.impl;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;
import org.hibernate.annotations.TypeDef;
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;
}
EnableFilterInfoProvider
package com.gss.gmo.cao.hibernate.impl;
import java.util.HashMap;
import java.util.Map;
import com.gss.gmo.cao.hibernate.filter.EnableFilterInfoAware;
public class EnableFilterInfoProvider implements EnableFilterInfoAware {
@Override
public Map<String, Map<String, Object>> getEnableFilterInfo() {
Map<String, Map<String, Object>> enableFilterInfo = new HashMap<String, Map<String, Object>>();
Map<String, Object> parameter = new HashMap<String, Object>();
parameter.put("name", "%Linus%");
parameter.put("address", "abc");
enableFilterInfo.put("personFilter", parameter);
return enableFilterInfo;
}
}
import java.util.HashMap;
import java.util.Map;
import com.gss.gmo.cao.hibernate.filter.EnableFilterInfoAware;
public class EnableFilterInfoProvider implements EnableFilterInfoAware {
@Override
public Map<String, Map<String, Object>> getEnableFilterInfo() {
Map<String, Map<String, Object>> enableFilterInfo = new HashMap<String, Map<String, Object>>();
Map<String, Object> parameter = new HashMap<String, Object>();
parameter.put("name", "%Linus%");
parameter.put("address", "abc");
enableFilterInfo.put("personFilter", parameter);
return enableFilterInfo;
}
}
Spring XML
<aop:config>
<aop:pointcut id="daoPointcut" expression="execution(* com.gss.gmo.cao.hibernate.*Dao.*(..))" />
<aop:advisor advice-ref="daoAdvice" pointcut-ref="daoPointcut" />
</aop:config>
<bean id="daoAdvice" class="com.gss.gmo.cao.hibernate.filter.EnableFilterInterceptor">
<property name="enableFilterInfoAware">
<bean class="com.gss.gmo.cao.hibernate.impl.EnableFilterInfoProvider" />
</property>
</bean>
這樣只要是被攔截到的DAO就會enable filter,然後只要查詢Person就會補上額外的condition,而相同的filter definition套在每個entity都能有不同的condition。想像一下,如果今天要做到個人只能看個人的資料,而主管可以看到部門內所有人的資料,利用這個方式就不需要在DAO裡面implement這樣的邏輯,而是交給Hibernate Filter和Strategy處理了。<aop:pointcut id="daoPointcut" expression="execution(* com.gss.gmo.cao.hibernate.*Dao.*(..))" />
<aop:advisor advice-ref="daoAdvice" pointcut-ref="daoPointcut" />
</aop:config>
<bean id="daoAdvice" class="com.gss.gmo.cao.hibernate.filter.EnableFilterInterceptor">
<property name="enableFilterInfoAware">
<bean class="com.gss.gmo.cao.hibernate.impl.EnableFilterInfoProvider" />
</property>
</bean>
最後我們看一下Hibernate實際下的SQL statement:
Show SQL
Hibernate:
select
count(*) as y0_
from
Person this_
where
this_.name like ?
and this_.address is null
and (
this_.name=?
)
select
count(*) as y0_
from
Person this_
where
this_.name like ?
and this_.address is null
and (
this_.name=?
)