比springside PropertyFilter增强灵活的FieldFilter,

 完整代码附件内

 

 SpringSide PropertyFilter 的缺点:

 

1,不能A or B or C

 

2, 不能( A or B) and c

 

3, 不能包含between

 

4,不能属性间比较 field2 >field2

 

5,很难看的下划线 EQS_compId EQS_userName GES_recTime

 

6,即使已经知道具体类型,但也要转换成字符串

 

if (recForm.getBeginTime() != null && recForm.getEndTime() != null) {
			filters.add(new PropertyFilter("GES_recTime", DateFormatUtils
					.format(recForm.getBeginTime(), "HH:mm:ss")));
			filters.add(new PropertyFilter("LES_recTime", DateFormatUtils
					.format(recForm.getEndTime(), "HH:mm:ss")));
		}

7,功能有限的like

 

增强后的PropertyFilter

 

 

package com.sun4love.common.orm;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.beanutils.ConvertUtils;
import org.springframework.util.Assert;

/**
 * 属性过滤器
 * 
 * @author sun4love
 */
public class PropertyFilter {
	private String fieldName;
	private String otherField;
	private MatchType matchType;
	private boolean or;
	private boolean and;
	private boolean roundOr;
	private boolean roundAnd;
	private Object[] values;
	private List<PropertyFilter> filters = new ArrayList<PropertyFilter>();

	/**
	 * values为具体类型值的构造函数
	 * 
	 * @param fieldName
	 *            属性名
	 * @param matchType
	 *            匹配类型 {@link MatchType}
	 * @param values
	 *            值数组,MatchType为BETWEEN类型时,长度必须是2,其他为1,值必须是具体类型的值,
	 *            如果是字符串需要转换类型,见另一个构造函数
	 *            {@link #PropertyFilter(String, MatchType, FieldType, Object...)}
	 */
	public PropertyFilter(final String fieldName, MatchType matchType,
			Object... values) {
		this.fieldName = fieldName;
		this.matchType = matchType;
		if (this.matchType == MatchType.BETWEEN
				&& (values == null || values.length != 2)) {
			throw new IllegalArgumentException(String.format(
					"%s属性选择MatchType.BETWEEN类型时,values参数长度必须为2", fieldName));
		}
		this.values = values;
		filters.add(this);
	}

	/**
	 * 
	 * values值需要转换类型的构造函数
	 * 
	 * @param fieldName
	 *            属性名
	 * @param matchType
	 *            匹配类型 {@link MatchType}
	 * @param fieldType
	 *            属性的类型,value将被转换到此类型
	 * @param values
	 *            值数组,BETWEEN类型时,长度必须是2,其他为1,值必须是具体类型的值, 如果是字符串需要转换类型,见另一个构造函数
	 *            {@link #FieldFilter(String, MatchType, FieldType, Object...)}
	 */
	public PropertyFilter(final String fieldName, MatchType matchType,
			FieldType fieldType, Object... values) {
		this.fieldName = fieldName;
		this.matchType = matchType;
		Assert.notEmpty(values);
		if (this.matchType == MatchType.BETWEEN
				&& (values == null || values.length != 2)) {
			throw new IllegalArgumentException(String.format(
					"%s属性选择MatchType.BETWEEN类型时,values参数长度必须为2", fieldName));
		}
		for (int i = 0; i < values.length; i++) {
			this.values[i] = ConvertUtils.convert(values[i],
					fieldType.getValue());
		}
		filters.add(this);
	}

	/**
	 * 属性比较构造函数
	 * 
	 * @param fieldName
	 *            属性名
	 * @param matchType
	 *            条件类型
	 * @param otherField
	 *            其他属性
	 */
	public PropertyFilter(final String fieldName, String otherField,
			MatchType matchType) {
		this.fieldName = fieldName;
		this.matchType = matchType;
		this.otherField = otherField;
		filters.add(this);
	}

	/**
	 * 获取属性名
	 * 
	 * @return
	 */
	public String getFieldName() {
		return fieldName;
	}

	/**
	 * 向当前filter添加一个or联合过滤条件
	 * 
	 * @param filter
	 * @return
	 */
	public PropertyFilter addOrFilter(PropertyFilter filter) {
		filter.or = true;
		filters.add(filter);
		return this;
	}

	/**
	 * 向当前filter添加一个or联合过滤条件,
	 * <p>
	 * 过滤条件将作为一个整体,即将所有条件放入括号内
	 * 
	 * @param filter
	 * @return
	 */
	public PropertyFilter addRoundOrFilter(PropertyFilter filter) {
		Assert.isTrue(filter == this, "PropertyFilter不允许添加自身");
		filter.roundOr = true;
		filters.add(filter);
		return this;
	}

	/**
	 * 向当前filter添加一个and联合过滤条件,
	 * 
	 * @param filter
	 * @return
	 */
	public PropertyFilter addAndFilter(PropertyFilter filter) {
		Assert.isTrue(filter == this, "PropertyFilter不允许添加自身");
		filter.and = true;
		filters.add(filter);
		return this;
	}

	/**
	 * 
	 * 向当前filter添加一个and联合过滤条件,
	 * <p>
	 * 过滤条件将作为一个整体,即将所有条件放入括号内
	 * 
	 * @param filter
	 * @return
	 */
	public PropertyFilter addRoundAndFilter(PropertyFilter filter) {
		Assert.isTrue(filter == this, "PropertyFilter不允许添加自身");
		filter.roundAnd = true;
		filters.add(filter);
		return this;
	}

	/**
	 * 判断该filter是否是一个or联合过滤,见{@link #addOrFilter(PropertyFilter)}
	 * 
	 * @return
	 */
	public boolean isOr() {
		return or;
	}

	/**
	 * 判断该filter是否是一个and联合过滤,见{@link #addAndFilter(PropertyFilter)}
	 * 
	 * @return
	 */
	public boolean isAnd() {
		return and;
	}

	/**
	 * 判断该filter是否是一个or联合过滤, 见 {@link #addRoundOrFilter(PropertyFilter)}
	 * 
	 * @return
	 */
	public boolean isRoundOr() {
		return roundOr;
	}

	/**
	 * 判断该filter是否是一个and联合过滤, 见 {@link #addRoundAndFilter(PropertyFilter)}
	 * 
	 * @return
	 */
	public boolean isRoundAnd() {
		return roundAnd;
	}
	/**
	 * 判断该filter是否是一个联合过滤
	 * @return
	 */
	public boolean isMulti() {
		return !filters.isEmpty();
	}
	/**
	 * 获取属性的比较类型
	 * @return
	 */
	public MatchType getMatchType() {
		return matchType;
	}
	/**
	 * 获取属性比较参数值集合
	 * @return
	 */
	public Object[] getValues() {
		return values;
	}

	/**
	 * 联合filter迭代器
	 * <p>
	 * 不支持删除操作
	 * 
	 * @return
	 */
	public Iterator<PropertyFilter> iterator() {
		return new Iterator<PropertyFilter>() {
			private Iterator<PropertyFilter> it = filters.iterator();

			public boolean hasNext() {
				return it.hasNext();
			}

			public PropertyFilter next() {
				return it.next();
			}

			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	/**
	 * 联合filter作为一个过滤条件
	 * 
	 * @param filter
	 * @return
	 */
	public PropertyFilter joinFilter(PropertyFilter filter) {
		Assert.isTrue(filter == this, "PropertyFilter不允许添加自身");
		filters.add(filter);
		return this;
	}
	/**
	 * 其他field,两个属性比较时
	 * 
	 * @return
	 */
	public String getOtherField() {
		return otherField;
	}
	/**
	 * 属性类型
	 * @author sun4love
	 *
	 */
	public enum FieldType {
		String(String.class), Date(Date.class), Integer(Integer.class), Double(
				Double.class), Long(Long.class), Boolean(Boolean.class);
		private Class<?> clazz;

		private FieldType(Class<?> clazz) {
			this.clazz = clazz;
		}

		public Class<?> getValue() {
			return clazz;
		}
	}

	/** 属性比较类型. */
	public enum MatchType {
		/**
		 * 等于
		 * */
		EQ,
		/**
		 * 等于另一属性
		 * */
		EQF,
		/**
		 * like 'value'
		 * */
		LIKE,
		/**
		 * like '%value'
		 * */
		LIKE_START,
		/**
		 * like 'value%'
		 * */
		LIKE_END,
		/**
		 * like '%value%'
		 * */
		LIKE_ANYWHERE,
		/**
		 * 小于
		 * */
		LT,
		/**
		 * 小于另一属性
		 * */
		LTF,
		/**
		 * 大于
		 * */
		GT,
		/**
		 * 大于另一属性
		 * */
		GTF,
		/**
		 * 小于等于
		 * */
		LE,
		/**
		 * 小于等于另一属性
		 * */
		LEF,
		/**
		 * 大于等于
		 */
		GE,
		/**
		 * 大于等于另一属性
		 */
		GEF,
		/**
		 * 
		 * 在两者之间
		 * 
		 */
		BETWEEN,
		/**
		 * 
		 * 不等于
		 * 
		 */
		NE,
		/**
		 * 
		 * 不等于另一属性
		 * 
		 */
		NEF
	}

}
 

 

 

 当然HibernateDao也需要修改

 

package com.sun4love.common.orm.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.springframework.util.Assert;

import com.sun4love.common.reflection.ReflectionUtils;
import com.sun4love.common.orm.Page;
import com.sun4love.common.orm.PropertyFilter;
import com.sun4love.common.orm.PropertyFilter.MatchType;

/**
 * 封装SpringSide扩展功能的Hibernat DAO泛型基类.
 * 
 * 扩展功能包括分页查询,按属性过滤条件列表查询. 可在Service层直接使用,也可以扩展泛型DAO子类使用,见两个构造函数的注释.
 * 
 * @param <T>
 *            DAO操作的对象类型
 * @param <PK>
 *            主键类型
 * 
 * @author calvin
 */
public class HibernateDao<T, PK extends Serializable> extends
		SimpleHibernateDao<T, PK> {
	/**
	 * 用于Dao层子类使用的构造函数. 通过子类的泛型定义取得对象类型Class. eg. public class UserDao extends
	 * HibernateDao<User, Long>{ }
	 */
	public HibernateDao() {
		super();
	}

	/**
	 * 用于省略Dao层, Service层直接使用通用HibernateDao的构造函数. 在构造函数中定义对象类型Class. eg.
	 * HibernateDao<User, Long> userDao = new HibernateDao<User,
	 * Long>(sessionFactory, User.class);
	 */
	public HibernateDao(final SessionFactory sessionFactory,
			final Class<T> entityClass) {
		super(sessionFactory, entityClass);
	}

	// -- 分页查询函数 --//
	/**
	 * 分页获取全部对象.
	 */
	public Page<T> getAll(final Page<T> page) {
		return findPage(page);
	}

	/**
	 * 按HQL分页查询.
	 * 
	 * @param page
	 *            分页参数.不支持其中的orderBy参数.
	 * @param hql
	 *            hql语句.
	 * @param values
	 *            数量可变的查询参数,按顺序绑定.
	 * 
	 * @return 分页查询结果, 附带结果列表及所有查询时的参数.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Page<T> findPage(final Page<T> page, final String hql,
			final Object... values) {
		Assert.notNull(page, "page不能为空");

		Query q = createQuery(hql, values);

		if (page.isAutoCount()) {
			long totalCount = countHqlResult(hql, values);
			page.setTotalCount(totalCount);
		}

		setPageParameter(q, page);
		List result = q.list();
		page.setResult(result);
		return page;
	}

	/**
	 * 按HQL分页查询.
	 * 
	 * @param page
	 *            分页参数.
	 * @param hql
	 *            hql语句.
	 * @param values
	 *            命名参数,按名称绑定.
	 * 
	 * @return 分页查询结果, 附带结果列表及所有查询时的参数.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Page<T> findPage(final Page<T> page, final String hql,
			final Map<String, ?> values) {
		Assert.notNull(page, "page不能为空");

		Query q = createQuery(hql, values);

		if (page.isAutoCount()) {
			long totalCount = countHqlResult(hql, values);
			page.setTotalCount(totalCount);
		}

		setPageParameter(q, page);

		List result = q.list();
		page.setResult(result);
		return page;
	}

	/**
	 * 按Criteria分页查询.
	 * 
	 * @param page
	 *            分页参数.
	 * @param criterions
	 *            数量可变的Criterion.
	 * 
	 * @return 分页查询结果.附带结果列表及所有查询时的参数.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Page<T> findPage(final Page<T> page, final Criterion... criterions) {
		Assert.notNull(page, "page不能为空");

		Criteria c = createCriteria(criterions);

		if (page.isAutoCount()) {
			int totalCount = countCriteriaResult(c);
			page.setTotalCount(totalCount);
		}

		setPageParameter(c, page);
		List result = c.list();
		page.setResult(result);
		return page;
	}

	/**
	 * 设置分页参数到Query对象,辅助函数.
	 */
	protected Query setPageParameter(final Query q, final Page<T> page) {
		// hibernate的firstResult的序号从0开始
		q.setFirstResult(page.getFirst() - 1);
		q.setMaxResults(page.getPageSize());
		return q;
	}

	/**
	 * 设置分页参数到Criteria对象,辅助函数.
	 */
	protected Criteria setPageParameter(final Criteria c, final Page<T> page) {
		// hibernate的firstResult的序号从0开始
		c.setFirstResult(page.getFirst() - 1);
		c.setMaxResults(page.getPageSize());

		if (page.isOrderBySetted()) {
			String[] orderByArray = StringUtils.split(page.getOrderBy(), ',');
			String[] orderArray = StringUtils.split(page.getOrder(), ',');

			Assert.isTrue(orderByArray.length == orderArray.length,
					"分页多重排序参数中,排序字段与排序方向的个数不相等");

			for (int i = 0; i < orderByArray.length; i++) {
				if (Page.ASC.equals(orderArray[i])) {
					c.addOrder(Order.asc(orderByArray[i]));
				} else {
					c.addOrder(Order.desc(orderByArray[i]));
				}
			}
		}
		return c;
	}

	/**
	 * 执行count查询获得本次Hql查询所能获得的对象总数.
	 * 
	 * 本函数只能自动处理简单的hql语句,复杂的hql查询请另行编写count语句查询.
	 */
	protected long countHqlResult(final String hql, final Object... values) {
		String fromHql = hql;
		// select子句与order by子句会影响count查询,进行简单的排除.
		fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
		fromHql = StringUtils.substringBefore(fromHql, "order by");

		String countHql = "select count(*) " + fromHql;

		try {
			Long count = findUnique(countHql, values);
			return count;
		} catch (Exception e) {
			throw new RuntimeException("hql can't be auto count, hql is:"
					+ countHql, e);
		}
	}

	/**
	 * 执行count查询获得本次Hql查询所能获得的对象总数.
	 * 
	 * 本函数只能自动处理简单的hql语句,复杂的hql查询请另行编写count语句查询.
	 */
	protected long countHqlResult(final String hql, final Map<String, ?> values) {
		String fromHql = hql;
		// select子句与order by子句会影响count查询,进行简单的排除.
		fromHql = "from " + StringUtils.substringAfter(fromHql, "from");
		fromHql = StringUtils.substringBefore(fromHql, "order by");

		String countHql = "select count(*) " + fromHql;

		try {
			Long count = findUnique(countHql, values);
			return count;
		} catch (Exception e) {
			throw new RuntimeException("hql can't be auto count, hql is:"
					+ countHql, e);
		}
	}

	/**
	 * 执行count查询获得本次Criteria查询所能获得的对象总数.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected int countCriteriaResult(final Criteria c) {
		CriteriaImpl impl = (CriteriaImpl) c;

		// 先把Projection、ResultTransformer、OrderBy取出来,清空三者后再执行Count操作
		Projection projection = impl.getProjection();
		ResultTransformer transformer = impl.getResultTransformer();

		List<CriteriaImpl.OrderEntry> orderEntries = null;
		try {
			orderEntries = (List) ReflectionUtils.getFieldValue(impl,
					"orderEntries");
			ReflectionUtils
					.setFieldValue(impl, "orderEntries", new ArrayList());
		} catch (Exception e) {
			logger.error("不可能抛出的异常:{}", e.getMessage());
		}

		// 执行Count查询
		int totalCount = ((Long) c.setProjection(Projections.rowCount())
				.uniqueResult()).intValue();

		// 将之前的Projection,ResultTransformer和OrderBy条件重新设回去
		c.setProjection(projection);

		if (projection == null) {
			c.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		}
		if (transformer != null) {
			c.setResultTransformer(transformer);
		}
		try {
			ReflectionUtils.setFieldValue(impl, "orderEntries", orderEntries);
		} catch (Exception e) {
			logger.error("不可能抛出的异常:{}", e.getMessage());
		}

		return totalCount;
	}

	// -- 属性过滤条件(FieldFilter)查询函数 --//

	/**
	 * 按属性查找对象列表,支持多种匹配方式.
	 * 
	 * @param matchType
	 *            匹配方式,目前支持的取值见FieldFilter的MatcheType enum.
	 */
	public List<T> findBy(final String fieldName, final Object value,
			final MatchType matchType) {
		Criterion criterion = buildFieldFilterCriterion(fieldName,
				new Object[] { value }, null, matchType);
		return find(criterion);
	}

	/**
	 * 按属性过滤条件列表查找对象列表.
	 */
	public List<T> find(List<PropertyFilter> filters) {
		Criterion[] criterions = buildFieldFilterCriterions(filters);
		return find(criterions);
	}

	/**
	 * 按属性过滤条件列表分页查找对象.
	 */
	public Page<T> findPage(final Page<T> page,
			final List<PropertyFilter> filters) {
		Criterion[] criterions = buildFieldFilterCriterions(filters);
		return findPage(page, criterions);
	}

	/**
	 * 按属性条件列表创建Criterion数组,辅助函数.
	 */
	protected Criterion[] buildFieldFilterCriterions(
			final List<PropertyFilter> filters) {
		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (PropertyFilter filter : filters) {
			if (!filter.isMulti()) {
				criterionList.add(buildFieldFilterCriterion(
						filter.getFieldName(), filter.getValues(),
						filter.getOtherField(), filter.getMatchType()));
			} else {
				criterionList.add(buildMultiFieldFilter(filter));
			}
		}
		return criterionList.toArray(new Criterion[criterionList.size()]);
	}

	protected Criterion buildMultiFieldFilter(PropertyFilter filter) {
		Criterion last = null;
		for (Iterator<PropertyFilter> it = filter.iterator(); it.hasNext();) {
			PropertyFilter ff = it.next();
			Criterion c = buildFieldFilterCriterion(ff);
			if (ff.isAnd()) {
				last = new SimpleLogicalExpression(last, c, "and");
			} else if (ff.isOr()) {
				last = new SimpleLogicalExpression(last, c, "or");
			} else if (ff.isRoundAnd()) {
				last = Restrictions.and(last, c);
			} else if (ff.isRoundOr()) {
				last = Restrictions.or(last, c);
			} else {
				last = c;
			}
		}
		return last;
	}

	/**
	 * 按属性条件参数创建Criterion,辅助函数.
	 */
	protected Criterion buildFieldFilterCriterion(final String fieldName,
			final Object[] fieldValues, String otherField,
			final MatchType matchType) {
		Assert.hasText(fieldName, "fieldName不能为空");
		Criterion criterion = null;
		try {
			// 根据MatchType构造criterion
			if (MatchType.EQ.equals(matchType)) {
				criterion = Restrictions.eq(fieldName, fieldValues[0]);
			} else if (MatchType.LE.equals(matchType)) {
				criterion = Restrictions.le(fieldName, fieldValues[0]);
			} else if (MatchType.LT.equals(matchType)) {
				criterion = Restrictions.lt(fieldName, fieldValues[0]);
			} else if (MatchType.GE.equals(matchType)) {
				criterion = Restrictions.ge(fieldName, fieldValues[0]);
			} else if (MatchType.GT.equals(matchType)) {
				criterion = Restrictions.gt(fieldName, fieldValues[0]);
			} else if (MatchType.NE.equals(matchType)) {
				criterion = Restrictions.ne(fieldName, fieldValues[0]);
			} else if (MatchType.EQF.equals(matchType)) {
				criterion = Restrictions.eqProperty(fieldName, otherField);
			} else if (MatchType.LEF.equals(matchType)) {
				criterion = Restrictions.leProperty(fieldName, otherField);
			} else if (MatchType.LTF.equals(matchType)) {
				criterion = Restrictions.ltProperty(fieldName, otherField);
			} else if (MatchType.GEF.equals(matchType)) {
				criterion = Restrictions.geProperty(fieldName, otherField);
			} else if (MatchType.GTF.equals(matchType)) {
				criterion = Restrictions.gtProperty(fieldName, otherField);
			} else if (MatchType.NEF.equals(matchType)) {
				criterion = Restrictions.neProperty(fieldName, otherField);
			} else if (MatchType.LIKE.equals(matchType)) {
				criterion = Restrictions.like(fieldName,
						(String) fieldValues[0], MatchMode.EXACT);
			} else if (MatchType.LIKE_START.equals(matchType)) {
				criterion = Restrictions.like(fieldName,
						(String) fieldValues[0], MatchMode.START);
			} else if (MatchType.LIKE_END.equals(matchType)) {
				criterion = Restrictions.like(fieldName,
						(String) fieldValues[0], MatchMode.END);
			} else if (MatchType.LIKE_ANYWHERE.equals(matchType)) {
				criterion = Restrictions.like(fieldName,
						(String) fieldValues[0], MatchMode.ANYWHERE);
			} else if (MatchType.BETWEEN.equals(matchType)) {
				criterion = Restrictions.between(fieldName, fieldValues[0],
						fieldValues[1]);
			}
		} catch (Exception e) {
			throw ReflectionUtils.convertReflectionExceptionToUnchecked(e);
		}
		return criterion;
	}

	/**
	 * 按属性条件参数创建Criterion,辅助函数.
	 */
	protected Criterion buildFieldFilterCriterion(PropertyFilter filter) {
		return buildFieldFilterCriterion(filter.getFieldName(),
				filter.getValues(), filter.getOtherField(),
				filter.getMatchType());
	}

	/**
	 * 判断对象的属性值在数据库内是否唯一.
	 * 
	 * 在修改对象的情景下,如果属性新修改的值(value)等于属性原来的值(orgValue)则不作比较.
	 */
	public boolean isFieldUnique(final String fieldName, final Object newValue,
			final Object oldValue) {
		if (newValue == null || newValue.equals(oldValue)) {
			return true;
		}
		Object object = findUniqueBy(fieldName, newValue);
		return (object == null);
	}
}
 

 

 事例:

 

public Setting getSetting(String key) {
		List<FieldFilter> filters = new ArrayList<FieldFilter>();
		FieldFilter ff1 = new FieldFilter("keyField", "data", MatchType.EQF);
		FieldFilter ff2 = new FieldFilter("data", MatchType.BETWEEN, "3", "6");
		FieldFilter ff3 = new FieldFilter("data", MatchType.BETWEEN, "4", "8");
		ff1.addRoundAndFilter(ff2);
		ff2.addOrFilter(ff3);
		filters.add(ff1);
		settingDao.find(filters);
		return null;
	}

 

 

 

 

 生成sql

 

   select
        this_.keyField as keyField0_0_,
        this_.version as version0_0_,
        this_.data as data0_0_,
        this_.description as descript4_0_0_,
        this_.orderby as orderby0_0_
    from
        setting this_
    where
        (
            this_.keyField=this_.data
            and this_.data between ? and ?
        )
        or this_.data between ? and ?

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(apache,sql,orm)