我们在实现的时候采取了一种非常简洁又功能强大的方式-模板技术!对,就是利用freemarker把sql/hql中的动态拼接条件判断语法都交给freemarker语法去处理,这样既能复用freemarker框架,又保持了我们框架设计的简洁性-不需要自己写过多的处理逻辑,以下是我们需要进行动态处理的sql/hql语句的样例
and t.type=1
<#else>
and t.type=0
#if>
and t.type = ${type}
and t.status = ${status}
start with t.code = '${code}'
connect by nocycle prior t.id = t.parent_id
]]>
这个文件看起来非常类似ibatis的语句了,只是没用ibatis的哪些标签-改成了freemarker语法,没错,我们就是复用freemarker来帮我们解决这些烦杂的判断操作的
这样我们的动态sql程序就可以总结成以下流程
a.系统加载阶段
这个阶段程序负责将指定路径下的动态sql文件加载到内存中,一次性缓存起来,没错,这些东西只需要加载一次,以后直接读取就行了,没必要每次去查找,缓存也非常简单,一个Map
b.程序调用查询阶段
调用程序通过sql/hql语句的name属性和传入查询参数来得到最终解析出来的语句
我们期望的方法可能是这样的:
public List findByNamedQuery(final String queryName, final Map parameters)
通过queryName从缓存中查找出其对应的sql/hql语句(最原始的,里面带有freemarker语法)
然后通过freemarker模板和传递进去的parameters参数对模板进行解析,得到最终的语句(纯sql/hql)
最后将解析后的sql/hql传递给底层api,返回查询结果
3.实现
上面介绍了大致的思路,这里介绍具体的代码实现
3.1DTD定义
我们是把动态的sql/hql语句放在单独的xml配置文件里的,为了规范xml文档,我们给文档定义了dtd文件,这里我们只定义了两个元素
然后将其保存为dynamic-hibernate-statement-1.0.dtd,放在classpath下
编写DTD校验器
/**
* hibernate动态sql dtd解析器
* @author WangXuzheng
*
*/
public class DynamicStatementDTDEntityResolver implements EntityResolver, Serializable{
private static final long serialVersionUID = 8123799007554762965L;
private static final Logger LOGGER = LoggerFactory.getLogger( DynamicStatementDTDEntityResolver.class );
private static final String HOP_DYNAMIC_STATEMENT = "http://www.haier.com/dtd/";
public InputSource resolveEntity(String publicId, String systemId) {
InputSource source = null; // returning null triggers default behavior
if ( systemId != null ) {
LOGGER.debug( "trying to resolve system-id [" + systemId + "]" );
if ( systemId.startsWith( HOP_DYNAMIC_STATEMENT ) ) {
LOGGER.debug( "recognized hop dyanmic statement namespace; attempting to resolve on classpath under com/haier/openplatform/dao/hibernate/" );
source = resolveOnClassPath( publicId, systemId, HOP_DYNAMIC_STATEMENT );
}
}
return source;
}
private InputSource resolveOnClassPath(String publicId, String systemId, String namespace) {
InputSource source = null;
String path = "com/haier/openplatform/dao/hibernate/" + systemId.substring( namespace.length() );
InputStream dtdStream = resolveInHibernateNamespace( path );
if ( dtdStream == null ) {
LOGGER.debug( "unable to locate [" + systemId + "] on classpath" );
if ( systemId.substring( namespace.length() ).indexOf( "2.0" ) > -1 ) {
LOGGER.error( "Don't use old DTDs, read the Hibernate 3.x Migration Guide!" );
}
}
else {
LOGGER.debug( "located [" + systemId + "] in classpath" );
source = new InputSource( dtdStream );
source.setPublicId( publicId );
source.setSystemId( systemId );
}
return source;
}
protected InputStream resolveInHibernateNamespace(String path) {
return this.getClass().getClassLoader().getResourceAsStream( path );
}
protected InputStream resolveInLocalNamespace(String path) {
try {
return ConfigHelper.getUserResourceAsStream( path );
}
catch ( Throwable t ) {
return null;
}
}
}
3.2编写sql文件
and t.type=1
<#else>
and t.type=0
#if>
and t.type = ${type}
and t.status = ${status}
start with t.code = '${code}'
connect by nocycle prior t.id = t.parent_id
]]>
3.3加载动态sql文件
这里我们将加载sql/hql语句的程序独立到一个单独的类中,以便独立扩展
这里一共3个方法,分表标示获取系统中sql/hql语句的map(key:语句名称,value:具体的)
/**
* 动态sql/hql语句组装器
* @author WangXuzheng
*
*/
public interface DynamicHibernateStatementBuilder {
/**
* hql语句map
* @return
*/
public Map getNamedHQLQueries();
/**
* sql语句map
* @return
*/
public Map getNamedSQLQueries();
/**
* 初始化
* @throws IOException
*/
public void init() throws IOException;
}
默认的加载器-将指定配置文件中的sql/hql语句加载到内存中
/**
* @author WangXuzheng
*
*/
public class DefaultDynamicHibernateStatementBuilder implements DynamicHibernateStatementBuilder, ResourceLoaderAware {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDynamicHibernateStatementBuilder.class);
private Map namedHQLQueries;
private Map namedSQLQueries;
private String[] fileNames = new String[0];
private ResourceLoader resourceLoader;
private EntityResolver entityResolver = new DynamicStatementDTDEntityResolver();
/**
* 查询语句名称缓存,不允许重复
*/
private Set nameCache = new HashSet();
public void setFileNames(String[] fileNames) {
this.fileNames = fileNames;
}
@Override
public Map getNamedHQLQueries() {
return namedHQLQueries;
}
@Override
public Map getNamedSQLQueries() {
return namedSQLQueries;
}
@Override
public void init() throws IOException {
namedHQLQueries = new HashMap();
namedSQLQueries = new HashMap();
boolean flag = this.resourceLoader instanceof ResourcePatternResolver;
for (String file : fileNames) {
if (flag) {
Resource[] resources = ((ResourcePatternResolver) this.resourceLoader).getResources(file);
buildMap(resources);
} else {
Resource resource = resourceLoader.getResource(file);
buildMap(resource);
}
}
//clear name cache
nameCache.clear();
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
private void buildMap(Resource[] resources) throws IOException {
if (resources == null) {
return;
}
for (Resource resource : resources) {
buildMap(resource);
}
}
@SuppressWarnings({ "rawtypes" })
private void buildMap(Resource resource) {
InputSource inputSource = null;
try {
inputSource = new InputSource(resource.getInputStream());
XmlDocument metadataXml = MappingReader.INSTANCE.readMappingDocument(entityResolver, inputSource,
new OriginImpl("file", resource.getFilename()));
if (isDynamicStatementXml(metadataXml)) {
final Document doc = metadataXml.getDocumentTree();
final Element dynamicHibernateStatement = doc.getRootElement();
Iterator rootChildren = dynamicHibernateStatement.elementIterator();
while (rootChildren.hasNext()) {
final Element element = (Element) rootChildren.next();
final String elementName = element.getName();
if ("sql-query".equals(elementName)) {
putStatementToCacheMap(resource, element, namedSQLQueries);
} else if ("hql-query".equals(elementName)) {
putStatementToCacheMap(resource, element, namedHQLQueries);
}
}
}
} catch (Exception e) {
LOGGER.error(e.toString());
throw new SysException(e);
} finally {
if (inputSource != null && inputSource.getByteStream() != null) {
try {
inputSource.getByteStream().close();
} catch (IOException e) {
LOGGER.error(e.toString());
throw new SysException(e);
}
}
}
}
private void putStatementToCacheMap(Resource resource, final Element element, Map statementMap)
throws IOException {
String sqlQueryName = element.attribute("name").getText();
Validate.notEmpty(sqlQueryName);
if (nameCache.contains(sqlQueryName)) {
throw new SysException("重复的sql-query/hql-query语句定义在文件:" + resource.getURI() + "中,必须保证name的唯一.");
}
nameCache.add(sqlQueryName);
String queryText = element.getText();
statementMap.put(sqlQueryName, queryText);
}
private static boolean isDynamicStatementXml(XmlDocument xmlDocument) {
return "dynamic-hibernate-statement".equals(xmlDocument.getDocumentTree().getRootElement().getName());
}
}
配置一下
classpath*:/**/*-dynamic.xml
dao层代码
/**
* Hibernate实现的DAO层
* @param DAO操作的对象类型
* @param 主键类型
* @author WangXuzheng
*
*/
public class SimpleHibernateDAO implements BaseDAO,InitializingBean{
private static final Logger LOGER = LoggerFactory.getLogger(SimpleHibernateDAO.class);
protected SessionFactory sessionFactory;
protected Class entityClass;
/**
* 模板缓存
*/
protected Map templateCache;
protected DynamicHibernateStatementBuilder dynamicStatementBuilder;
/**
* 通过子类的泛型定义取得对象类型Class.
* eg.
* public class UserDao extends SimpleHibernateDao
*/
public SimpleHibernateDAO() {
this.entityClass = Reflections.getSuperClassGenricType(getClass());
}
/**
* 取得sessionFactory.
*/
public SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* 采用@Autowired按类型注入SessionFactory, 当有多个SesionFactory的时候在子类重载本函数.
*/
public void setSessionFactory(final SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void setDynamicStatementBuilder(DynamicHibernateStatementBuilder dynamicStatementBuilder) {
this.dynamicStatementBuilder = dynamicStatementBuilder;
}
/**
* 取得当前Session.
*/
public Session getSession() {
return sessionFactory.getCurrentSession();
}
/**
* 保存新增或修改的对象.
*/
@Override
public void save(final T entity) {
Validate.notNull(entity, "entity不能为空");
getSession().save(entity);
LOGER.debug("save entity: {}", entity);
}
/**
* 删除对象.
*
* @param entity 对象必须是session中的对象或含id属性的transient对象.
*/
@Override
public void delete(final T entity) {
if(entity == null){
return;
}
getSession().delete(entity);
LOGER.debug("delete entity: {}", entity);
}
/**
* 按id删除对象.
*/
@Override
public void delete(final ID id) {
Validate.notNull(id, "id不能为空");
delete(get(id));
LOGER.debug("delete entity {},id is {}", entityClass.getSimpleName(), id);
}
/**
* 按id获取对象.
*/
@SuppressWarnings("unchecked")
@Override
public T get(final ID id) {
Validate.notNull(id, "id不能为空");
return (T) getSession().get(entityClass, id);
}
/**
* 按id列表获取对象列表.
*/
public List get(final Collection ids) {
return find(Restrictions.in(getIdName(), ids));
}
/**
* 获取全部对象.
*/
@Override
public List getAll() {
return find();
}
/**
* 获取全部对象, 支持按属性行序.
*/
@SuppressWarnings("unchecked")
public List getAll(String orderByProperty, boolean isAsc) {
Criteria c = createCriteria();
if (isAsc) {
c.addOrder(Order.asc(orderByProperty));
} else {
c.addOrder(Order.desc(orderByProperty));
}
return c.list();
}
/**
* 按属性查找对象列表, 匹配方式为相等.
*/
public List findBy(final String propertyName, final Object value) {
Criterion criterion = Restrictions.eq(propertyName, value);
return find(criterion);
}
/**
* 按属性查找唯一对象, 匹配方式为相等.
*/
@SuppressWarnings("unchecked")
@Override
public T findUniqueBy(final String propertyName, final Object value) {
Criterion criterion = Restrictions.eq(propertyName, value);
return ((T) createCriteria(criterion).uniqueResult());
}
/**
* 按HQL查询对象列表.
*
* @param values 数量可变的参数,按顺序绑定.
*/
@SuppressWarnings("unchecked")
public List findByHQL(final String hql, final Object... values) {
return createHQLQuery(hql, values).list();
}
/**
* 按HQL查询对象列表,并将对象封装成指定的对象
*
* @param values 数量可变的参数,按顺序绑定.
*/
@SuppressWarnings("unchecked")
public List findByHQLRowMapper(RowMapper rowMapper,final String hql, final Object... values) {
Validate.notNull(rowMapper, "rowMapper不能为空!");
List
我们的SimpleHibernateDAO实现了InitializingBean,在其afterProperties方法中我们将调用DynamicHibernateStatementBuilder把语句缓存起来
public List getDescendants(Long userId,String code) {
Map values = new HashMap();
values.put("userId", String.valueOf(userId));
values.put("code", code);
values.put("type", String.valueOf(ResourceTypeEnum.URL_RESOURCE.getType()));
values.put("status", String.valueOf(ResourceStatusEnum.ACTIVE.getStatus()));
return this.findByNamedQuery(new ResourceRowMapper(),"resource.getDescendants", values);
}