问题
粗浅的使用过几种java的持久框架,感觉mybatis还是十分好用的,特别比较喜欢resultMapping这个feature,使得结果的封装很方便。
实际使用中,笔者遇到一个问题,就是针对不同的搜索,往往要使用很多个定义的检索sql,使得xml和DAO层繁琐复杂。
例如对于用户表 (User)
public class User {
private Long id;
private String name;
private String telphone;
private String email;
//getter and setters
}
存在使用id, name, telphone, email的搜索,所以对应的xml中,应该定于针对各种情况的搜索。
<select id="findById" resultType="User">
SELECT id, name, telphone, email FROM user where id = #{id}
</select>
<select id="findByName" resultType="User">
SELECT id, name, telphone, email FROM user where name = #{name}
</select>
<select id="findByPhone" resultType="User">
SELECT id, name, telphone, email FROM user where telphone = #{telphone}
</select>
<select id="findByEmail" resultType="User">
SELECT id, name, telphone, email FROM user where email = #{email}
</select>
如果存在更多子段的搜索,可能意味着书写更多xml,同时DAO层也需要提供更多的方法,获取相应的数据,这样会导致xml文件膨胀,DAO层接口也会随着业务增多不断增多。
我的解决方法
1. ${param} 和 #{param}的区别
试图解决这个问题之前,先对这两种用法加以区分。
大家应该对这样的sql很熟悉吧
String sql= "insert into user (name,pwd) values(?,?)";
ps=conn.prepareStatement(sql);
ps.setString(1, user.getName());
ps.setString(2, user.getPwd());
#{param}的用法就是参数,和上面的用法可以对等转化,如果打开mybatis的log,就可以发现每次执行的sql,而${param}的用法则相当于字符串拼接。
举个例子
<select id="findByEmail" resultType="User">
SELECT id, name, telphone, email FROM user where email = #{email}
</select>
执行的方式是
SELECT id, name, telphone, email FROM user where email = ?
然后通过参数的方式设置对应的值。
而
<select id="findByEmail" resultType="User">
SELECT id, name, telphone, email FROM user where email = ${email}
</select>
则等于
SELECT id, name, telphone, email FROM user where email = test@163.com
相当于字符串的直接拼接(假设传入的参数是 email: "test@163.com")
如果采用${param}的方式,则可能导致sql拼接错误,以及sql注入。但是它却可以为sql拼接提供解决方案。
我的通用Query框架
利用这些特性,我决定把所有的 条件拼接起来,运用$拼接sql,运用#设置需要的值,对于每个单独的条件,java类表述如下:
public class QueryItem implements Serializable{
private String fieldName; //sql的字段 采用$获取值,拼接在sql中
private Object value;//自断对应的值,采用#获取值
private String compareMethod; //比较的方法,可以支持 等于,大于,小于等等
}
当然,上面的定义,只是检索条件的一项我们还需要一个类,把条件项目用 list存起来,这样才能遍历所有的条件。
public class Query implements Serializable {
private List<QueryItem> queryItems = new ArrayList<QueryItem>();
private Integer limit;
private Integer offset;
private String order;
private String orderDir = "ASC";
}
同时对于检索,还有paging,以及排序的相关内容。
<sql id="commonQuery">
<if test="queryItems != null ">
<foreach collection="queryItems" item="item" index="index"
separator=" ">
<if test="item.compareMethod == 'equal' ">
AND ${item.fieldName} = #{item.value}
</if>
<if test="item.compareMethod == 'less' ">
<![CDATA[ AND ${item.fieldName} < #{item.value} ]]>
</if>
<if test="item.compareMethod == 'less_equal' ">
<![CDATA[ AND ${item.fieldName} <= #{item.value} ]]>
</if>
<if test="item.compareMethod == 'larger' ">
<![CDATA[ AND ${item.fieldName} > #{item.value} ]]>
</if>
<if test="item.compareMethod == 'larger_equal' ">
<![CDATA[ AND ${item.fieldName} >= #{item.value} ]]>
</if>
<if test="item.compareMethod == 'start_with' ">
<![CDATA[ AND ${item.fieldName} like '${item.value}%' ]]>
</if>
<if test="item.compareMethod == 'end_with' ">
<![CDATA[ AND ${item.fieldName} like '%${item.value}' ]]>
</if>
<if test="item.compareMethod == 'contains' ">
<![CDATA[ AND ${item.fieldName} like '%${item.value}%' ]]>
</if>
</foreach>
</if>
</sql>
<strong> </strong>
上面的代码,就是通用检索最核心的部分了,遍历queryItems中的项目,然后取出条件拼接成Where Clause
${item.fieldName} like '%${item.value}%'
注意这里的用法,前面的语句中使用#来设置值,但是对于字符串like,字符串内的内容属于拼接的部分,所以使用了$
当然还有一些关于paging和排序的设置
<sql id="orderBy">
<if test="order != null ">
ORDER BY ${order} ${orderDir}
</if>
</sql>
<sql id="limit">
<if test="limit != null ">
LIMIT ${offset}, ${limit}
</if>
</sql>
如果在query中设置了 orderBy和limit,则自动补全这部分在sql中
如何使用框架搜索
<select id="findUsers" resultType="User">
select <span style="font-family: Arial, Helvetica, sans-serif;">id, name, telphone, email</span>
from user
where 1=1
<include refid="commonQuery"></include>
<include refid="orderBy"></include>
<include refid="limit"></include>
</select>
而在java代码中,则可以采用代码形式,任意定义sql的条件 例如
Query query = new Query();
query.addQueryItem(new QueryItem("name", "liudong", CompareMethodDef.EQUAL));
query.addQueryItem(new QueryItem("phone", 15618010153L, CompareMethodDef.EQUAL));
List<User> users = userDao.findUsers(query);
当然同样可以增加paging,排序等规则。这样会使得xml定义和dao层非常简单,针对不同的检索,只要在java代码中,稍作修改就可以达到目的了