基于HIBERNATE的全自动查询框架(2)

基于HIBERNATE的全自动查询框架(二)
这个框架最重要的代码是自动拼装条件部份,本篇主要对拼装条件工具类入口类的代码进行讲解,首先是本框架的方法调用时序图
基于HIBERNATE的全自动查询框架(2)

下面是自动拼装条件工具类的入口
public List findByAutoConditionByLimit(Pagin pagin,Class<? extends BaseModel> pojoClass,String startChar,String[] columName,String[] excludeParameters,List<Criterion> customConditions, String alias)
	{
                  //框架的核心在这里,自动生成HIBERNATE的查询对象
		DetachedCriteria criteria = DetachedCriteriaUtil.createDetachedCriteria(pojoClass,startChar,excludeParameters,alias);
		.......
		//这里开始查询,根据查询对象封装的分页查询
		return findByDetachedCriteria(....);
	}


下面来看看DetachedCriteriaUtil工具类,这是自动封装查询条件的总入口,此工具首先根据POJO类和别名创建出DetachedCriteria对象,然后从请求中循环地分析出需要的查询参数,并格式化成字符串,然后通过“标装条件构造器”拼每个查询条件,最后拼装DetachedCriteria查询器中,本类主要的逻辑在于createDetachedCriteria和getCriterion方法,其它方法只要知道他们是干什么用的就行,代码:
package com.esc.common.util;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.hibernate.FetchMode;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Expression;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.transform.Transformers;

import com.esc.common.baseclass.BaseModel;
import com.esc.common.hibernate.resulttranformer.EscAliasToBean;
import com.esc.common.util.conditionbuilder.ConditionBuilder;
import com.esc.common.util.conditionbuilder.StandardConditionBuilder;

/**
 * 拼装Hibernate条件
 */
public class DetachedCriteriaUtil {

	/** 小写字母X */
	public static final String MIDDLE_SEPRATOR_CHAR = "x";
	/** 两个空格 */
	public static final String SEPARATOR_TWO_SPACE = "  ";// 两个空格,避免和"日期
															// 时间"中的空格混淆
	/** 此字符串内容为" <= x <= ",不包括引号 */
	public static final String COMPLETE_SEPARATOR = SEPARATOR_TWO_SPACE
			+ Operator.LE + SEPARATOR_TWO_SPACE + MIDDLE_SEPRATOR_CHAR
			+ SEPARATOR_TWO_SPACE + Operator.LT + SEPARATOR_TWO_SPACE;

	private DetachedCriteriaUtil() {
		// do not allow to create Object
	}

	/** 标准条件构造器,核心中的核心 */
	private static final ConditionBuilder cb = new StandardConditionBuilder();

	/**
	 * 自动拼装条件
	 * 
	 * @param pojoClazz
	 *            实体类名
	 * @param startChar
	 *            参数统一的开始字符
	 * @param alias
	 *            别名
	 * @return
	 */
	public static DetachedCriteria createDetachedCriteria(Class pojoClazz,
			String startChar, String alias) {
		return createDetachedCriteria(pojoClazz, startChar, null, alias);
	}

	/**
	 * 自动拼装条件 2008-11-3
	 * 
	 * @param pojoClazz
	 *            实体类名
	 * @param startChar
	 *            参数前辍
	 * @param excludeParameters
	 *            不作为查询条件的参数
	 * @param alias
	 *            别名
	 * @return
	 */
	public static DetachedCriteria createDetachedCriteria(Class pojoClazz,
			String startChar, String[] excludeParameters, String alias) {
		// 需要回传的参数,前台写成了name="userBean.sysUser.name|userBean.sysUser.realName"的参数,
		// 通过处理,可出使用第一个名字取回值
		Map<String, String> postBackParameters = null;
		// 这个字符串很重要,它将决定那些以它开头的参数被处理,其它不被处理
		String newStartChar = new StringBuilder(startChar).append(POINT)
				.toString();
		// 创建DetachedCriteria
		DetachedCriteria criteria = DetachedCriteria.forClass(pojoClazz, alias);
		// 取得当前请求
		HttpServletRequest request = SysContext.getRequest();
		// 取得所有请求参数
		Enumeration parameterNames = request.getParameterNames();
		// 创建alias集合,主要不为了不重复创建alias
		Set<String> aliases = getAliasesFromRequest();
		// 查询条件是否包括当前正在处理的参数,如果检测到当前参数被排除了,设为FALSE
		boolean includeThisParameter = true;
		// 循环所有参数,拼装条件
		while (parameterNames != null && parameterNames.hasMoreElements()) {
			// 按顺序取得参数名
			String propName = parameterNames.nextElement().toString();
			// 取得参数值
			String[] propValue = request.getParameterValues(propName);
			// 只处理newStartChar开头的参数
			if (propName.startsWith(newStartChar)
					&& !CollectionUtils.isStringArrayEmpty(propValue)) {
				// 让下面的方法返回Criterion对象,在这里进行拼装
				if (propName.contains("|")) {// 组装很多OR
					String[] keys = propName.split("\\|");
					List<Criterion> criterions = new ArrayList<Criterion>(
							keys.length);
					for (String key : keys) {
						// getCriterion返回单个的Criterion对象
						criterions.add(getCriterion(key, startChar, propValue,
								criteria, cb, alias, pojoClazz, aliases));
					}
					addAndToCriteria(criteria, assembleOr(criterions));
					if (postBackParameters == null) {
						// 这里把请求中pojo.bb|pojo.cc参数换个EL表达式能接受的名,传回到REQUEST中,用于回显,AJAX查询的话这里就不需要了
						postBackParameters = new HashMap<String, String>(5);
					}
					postBackParameters.put(keys[0], StringUtil
							.getLinkString(propValue));
				} else {// 组装单个and
					// 要先判断此参数是否被用户排除在外了,
					includeThisParameter = true;
					if (excludeParameters != null
							&& excludeParameters.length > 0) {
						for (int i = 0; i < excludeParameters.length; i++) {
							if (propName != null
									&& propName.equals(excludeParameters[i])) {
								includeThisParameter = false;// 确实排除了
							}
						}
					}
					if (includeThisParameter)
						// 不被排除的参数名
						addAndToCriteria(criteria, getCriterion(propName,
								startChar, propValue, criteria, cb, alias,
								pojoClazz, aliases));
				}
			}
		}
		// 设置回传参数
		setPostBackParameter(postBackParameters);
		return criteria;
	}
	
	/**
	 * 组装条件
	 * 
	 * @param propName
	 *            带前辍的属性名
	 * @param startChar
	 *            属性名的前辍
	 * @param propValue
	 *            属性值
	 * @param criteria
	 *            HIBERNATE条件对象
	 * @param cb
	 *            条件生成器
	 * @param alias
	 *            别名
	 * @param pojoClazz
	 *            POJO类
	 * @return Criterion 条件对象
	 */
	private static Criterion getCriterion(String propName, String startChar,
			String[] propValue, DetachedCriteria criteria, ConditionBuilder cb,
			String alias, Class pojoClazz, Set<String> aliases) {
		//从pojo.aaa.bb参数名中,取出非前辍部份,即aaa.bb
		String key = propName.substring(startChar.length() + 1, propName .length());
		if (key.contains(POINT)) {// 如果还包含点号,即当前pojo属性中的属性,即级联了其它表进行查询
			//找个地方记住本次查询不是单表查询,而是级连了其它表的查询,最好的地方是放入请求
			setHasJoinTatleToRequest(SysContext.getRequest(), true);
			//选把别名创建好,HIBERNATE级联其它表进行查询时,对象属性要创建别名才行
			String[] keys = key.split("\\.");
			createAlias(criteria, alias, aliases, keys, 0);
		}
		if (propValue.length > 1) {// 一个属性有两个值,即JSP页面有两个输入框采用了同一个名字,如通过创建时间查询时,就需要输入区间了
			StringBuilder sb = new StringBuilder(30);
			//把这两个值拼成 aaa  <=  x  <=  bbb的字符串
			sb.append(propValue[0].trim()).append(COMPLETE_SEPARATOR).append(propValue[1]);
			//通过标准条件构造器拼装
			return cb.parseToCriterion(key, sb.toString(), pojoClazz, alias);
		} else {// 一个属性单个值
			//通过标准条件构造器拼装
			return cb.parseToCriterion(key, propValue[0], pojoClazz, alias);
		}
	}

	private static final String ALIAS_KEY_IN_REQUEST = "ALIAS_KEY_IN_REQUEST";
	private static final String HAS_JOIN_TABLE_KEY_IN_REQUEST = "HAS_JOIN_TABLE_KEY_IN_REQUEST";

	private static void setAliasToRequest(HttpServletRequest request,
			Set<String> aliases) {
		request.setAttribute(ALIAS_KEY_IN_REQUEST, aliases);
	}

	public static Set<String> getAliasesFromRequest() {
		Set<String> aliases = (Set<String>) SysContext.getRequest()
				.getAttribute(ALIAS_KEY_IN_REQUEST);
		if (aliases == null) {
			aliases = new HashSet<String>(5);
			setAliasToRequest(SysContext.getRequest(), aliases);
		}
		return aliases;
	}

	/**
	 * 为类似于name="userBean.sysUser.name|userBean.sysUser.realName"的参数设置回传值
	 * 
	 * @param parameters
	 */
	private static void setPostBackParameter(Map<String, String> parameters) {
		if (parameters != null) {
			Set<String> keySet = parameters.keySet();
			HttpServletRequest request = SysContext.getRequest();
			for (String key : keySet) {
				String[] keys = key.split("\\.");
				request
						.setAttribute(keys[keys.length - 1], parameters
								.get(key));
			}
		}
	}

	private static void setHasJoinTatleToRequest(HttpServletRequest request,
			boolean hasJoin) {
		request.setAttribute(HAS_JOIN_TABLE_KEY_IN_REQUEST, hasJoin);
	}

	private static boolean getHasJoinTatleFromRequest() {
		Boolean hasJoin = (Boolean) SysContext.getRequest().getAttribute(
				HAS_JOIN_TABLE_KEY_IN_REQUEST);
		return hasJoin == null ? false : hasJoin;
	}


	/**
	 * 把集合里所有条件组装OR
	 * 
	 * @param criterions
	 *            待组装成OR的条件集合
	 * @return 组装好的条件
	 */
	public static Criterion assembleOr(List<Criterion> criterions) {
		if (criterions == null || criterions.size() == 0)
			return null;
		Criterion result = null;
		for (Criterion criterion : criterions) {
			if (criterion == null)
				continue;
			if (result == null) {
				result = criterion;
			} else {
				result = Expression.or(result, criterion);
			}
		}
		return result;

	}

	/**
	 * 增加与条件
	 * 
	 * @param criteria
	 * @param criterion
	 */
	public static void addAndToCriteria(DetachedCriteria criteria,
			Criterion criterion) {
		if (criterion != null)
			criteria.add(criterion);
	}

	/**
	 * 该方法提供DetachedCriteria对查询字段的封装可支持无限级联取部分字段,如取如下字段
	 * user.organization.parentOrganization.parentOrganization.orgName 但请注意1点
	 * ,连接采用内联,级联越多,结果集可能就越少;
	 * 
	 * @param columnNames
	 *            字符串数组,以数据的形式接收要查询的字段属性,如String[] column={"属性1","属性2","属性3"};
	 * @param pojoClass
	 *            实体类的Class,如Mobile.class;
	 * @param aials
	 *            为要查询的POJO对象指定一个别名
	 * @return DetachedCriteria 的一个对象,如果需要查询条件,在些对象后追加查询条件。
	 * 
	 * @param forJoinTable
	 *            是否多表连接查询
	 */
	public static void selectColumn(DetachedCriteria criteria,
			String[] columnNames, Class<? extends BaseModel> pojoClass,
			String rootAlias, boolean forJoinTable) {
		if (null == columnNames) {
			return;
		}

		// 使用这个临时变量集合,是因为dinstinct关键字要放在最前面,而distinct关键字要在下面才决定放不放,
		List<Projection> tempProjectionList = new ArrayList<Projection>();

		Set<String> aliases = getAliasesFromRequest();
		boolean hasJoniTable = false;
		for (String property : columnNames) {
			if (property.contains(POINT)) {
				String[] propertyChain = property.split("\\.");
				createAlias(criteria, rootAlias, aliases, propertyChain, 0);
				tempProjectionList
						.add(Projections.property(
								getAliasFromPropertyChainString(property)).as(
								property));
				hasJoniTable = true;
			} else {
				tempProjectionList.add(Projections.property(
						rootAlias + POINT + property).as(property));
			}
		}

		ProjectionList projectionList = Projections.projectionList();
		if (hasJoniTable || forJoinTable || getHasJoinTatleFromRequest()) {// 这个一定要放在tempProjectionList的前面,因为distinct要在最前面
			projectionList.add(Projections.distinct(Projections.id()));
		}

		for (Projection proj : tempProjectionList) {
			projectionList.add(proj);
		}

		criteria.setProjection(projectionList);

		if (!hasJoniTable) {
			criteria.setResultTransformer(Transformers.aliasToBean(pojoClass));
		} else {
			criteria.setResultTransformer(new EscAliasToBean(pojoClass));
		}
	}

	private static final String POINT = ".";

	/**
	 * 创建别名
	 * 
	 * @param criteria
	 * @param rootAlais
	 * @param aliases
	 * @param columns
	 * @param currentStep
	 */
	public static void createAlias(DetachedCriteria criteria, String rootAlais,
			Set<String> aliases, String[] columns) {
		if (columns == null) {
			return;
		}
		for (String column : columns) {
			createAlias(criteria, rootAlais, aliases, column.split("\\."), 0);
		}
	}

	/**
	 * 创建别名
	 * 
	 * @param criteria
	 * @param rootAlais
	 * @param aliases
	 * @param columns
	 * @param currentStep
	 */
	private static void createAlias(DetachedCriteria criteria,
			String rootAlais, Set<String> aliases, String[] columns,
			int currentStep) {
		if (currentStep < columns.length - 1) {
			if (!aliases.contains(converArrayToAlias(columns, currentStep))) {
				if (currentStep > 0) {
					criteria.createAlias(
							converArrayToAlias(columns, currentStep - 1)
									+ POINT + columns[currentStep],
							converArrayToAlias(columns, currentStep))
							.setFetchMode(columns[currentStep], FetchMode.JOIN);
				} else {
					criteria.createAlias(
							rootAlais + POINT + columns[currentStep],
							converArrayToAlias(columns, currentStep))
							.setFetchMode(columns[currentStep], FetchMode.JOIN);
				}
				aliases.add(converArrayToAlias(columns, currentStep));
			}
			currentStep++;
			createAlias(criteria, rootAlais, aliases, columns, currentStep);
		}
	}

	/**
	 * 从"organization.parentOrganization.parentOrganization.parentOrganization.id"
	 * 得到
	 * "organization_parentOrganization_parentOrganization_parentOrganization.id"
	 * 
	 * @param property
	 * @return
	 */
	public static String getAliasFromPropertyChainString(String property) {
		if (property.contains(".")) {
			return property.substring(0, property.lastIndexOf(POINT))
					.replaceAll("\\.", "_")
					+ property.substring(property.lastIndexOf(POINT));
		}
		return property;
	}

	/**
	 * 从数组中创建ALIAS
	 * 
	 * @param columns
	 * @param currentStep
	 * @return
	 */
	private static String converArrayToAlias(String[] columns, int currentStep) {
		StringBuilder alias = new StringBuilder();
		for (int i = 0; i <= currentStep; i++) {
			if (alias.length() > 0) {
				alias.append("_");
			}
			alias.append(columns[i]);
		}
		return alias.toString();
	}
}



下一篇分析标准条件构造器