mybatis作为一种灵活的orm框架,优点有目共睹:灵活!不管多复杂的查询,联表,多对多,联表count。。。只要能把sql语句写出来,就能查出来转成PO。然鹅,在使用的时候也充分暴露了,越是灵活的东西,越容易出bug。比如买个面包机,我只需要按照说明书,往里加面粉,酵母,糖等配料就行了,做出来的东西起码符合食物的最低标准——能吃!但是如果给你一个烤箱做面包,理论上你可以做各种形状各种口味的:牛角面包,吐司,法棍。。然而事实上,可能做出来的东西并不能称之为“食物”。mybatis就是一个烤箱,能做任何事情,又不能做任何事情。
1. 搜索条件传入参数类型是Integer,Long 例如枚举类型,前端传入的是枚举的名称,而数据库存的是枚举值对应的数字,我们的做法是,先找到枚举值对应的数字,然后用数字去数据库进行搜索。
例:
接口Mapper.java:
1
List<TaskApplicationBO> findTaskApplicationsByCondition (@Param("taskApplicationStatus" ) Integer taskApplicationStatus) ;
对应的Mapper.xml的搜索:
1
2
3
4
5
6
select ta.id id, ta.task_application_status task_application_status
from task_application ta
where ta.delete_flag = 1
<if test ="taskApplicationStatus != null and taskApplicationStatus != ''" >
and ta.task_application_status = #{taskApplicationStatus}
</if >
这个时候,如果传入的taskApplicationStatus值是0,则会搜索出全部。
sql打印如下:
1
SELECT ta.id id , ta.task_application_status task_application_status FROM task_application ta WHERE ta.delete_flag = 1
传入的taskApplicationStatus值是其他值,则会拼接上搜索条件进行搜索。
1
2
SELECT ta.id id , ta.task_application_status task_application_status FROM task_application ta WHERE ta.delete_flag = 1 and ta.task_application_status = ?
Parameters : 1 (Integer )
由打印出的sql可以看出,当taskApplicationStatus为0的时候,没有进入if条件里。
通过Debug MyBatis源找到了IfSqlNode类,该类用来处理动态SQL的节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class IfSqlNode implements SqlNode {
private ExpressionEvaluator evaluator;
private String test;
private SqlNode contents;
public IfSqlNode (SqlNode contents, String test) {
this .test = test;
this .contents = contents;
this .evaluator = new ExpressionEvaluator();
}
public boolean apply (DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true ;
}
return false ;
}
}
其中方法public boolean apply(DynamicContext context)用来构造节点内的SQL语句。if (evaluator.evaluateBoolean(test, context.getBindings())该代码便是解析中test内表达式的关键,如果表达式为true则拼接SQL,否则忽略。
ExpressionEvaluator 类,通过OgnlCache.getValue(expression, parameterObject);可以看到表达式的值是从缓存中获取的,由此可知MyBatis竟然对表达式做了缓存,以提高性能。1
2
3
4
5
6
7
public class ExpressionEvaluator {
public boolean evaluateBoolean (String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) return (Boolean) value;
if (value instanceof Number) return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
return value != null ;
}
解析表达式的方法private static Object parseExpression(String expression),该方法会先从缓存取值,如果没有便进行解析并放入缓存中,然后调用Ognl.getValue(parseExpression(expression), root)获得表达式的值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class OgnlCache {
private static final Map<String, ognl.Node> expressionCache = new ConcurrentHashMap<String, ognl.Node>();
public static Object getValue (String expression, Object root) throws OgnlException {
return Ognl.getValue(parseExpression(expression), root);
}
private static Object parseExpression (String expression) throws OgnlException {
try {
Node node = expressionCache.get(expression);
if (node == null ) {
node = new OgnlParser(new StringReader(expression)).topLevelExpression();
expressionCache.put(expression, node);
}
return node;
} catch (ParseException e) {
throw new ExpressionSyntaxException(expression, e);
} catch (TokenMgrError e) {
throw new ExpressionSyntaxException(expression, e);
}
}
MyBatis的表达式是用OGNL 处理的,OGNL的官网中对表达式返回Boolean类型有以下解释:
如果对象是一个Number类型,值为0时将被解析为false,否则为true,浮点型0.00也是如此。
and ta.task_application_status = #{taskApplicationStatus} 时出现的问题了, taskApplicationStatus != ‘’被解析为false,而’’被解析为false,因此taskApplicationStatus!=’’是不成立的,表达式的值为false,导致无法拼接搜索条件。一般只有传入为String类型的时候才需要判断是否为String != ‘’。
解决方法
去掉test表达式中的taskApplicationStatus != ‘’内容
增加一个判断条件 or taskApplicationStatus == 0
另外如果你有类似于String str =”A”; 这样的写法时,因为单引号内如果为单个字符时,OGNL将会识别为Java 中的 char类型,显然String 类型与char类型做==运算会返回false,从而导致表达式不成立。解决方法很简单,修改为即可;也可以做toString的转化
2. 动态拼接sql时的特殊字符 严格来说这是mysql和url编码(通过http发起请求时参数识别)的问题,需要提前做个字符转化,且几乎无法穷举特殊字符,只能转化常见的。
比如“#”和“&”,在发起http请求时,“#”以后的内容会被忽略,而“&”作为参数拼接的符号,其后跟的内容会被认为是传入的参数,而本身作为参数传入时讲无法辨认。需要在前端进行转化。
mysql的特殊字符,比如“%”和“/”等。则是mysql的特殊字符,需要在拼接sql前进行转义。
1
2
3
4
value = value.replaceAll("/" , ESCAPE_CHAR + "/" );
value = value.replaceAll("%" , ESCAPE_CHAR + "%" );
value = value.replaceAll("_" , ESCAPE_CHAR + "_" );
value = value.replaceAll("'" , "''" );
3. 传入List组in语句 其实这也是mysql的问题,传入List,拼接in语句,如果传入的List集合是数字类型(如Integer,Long),查询返回的集合会根据数字大小进行升序排序(如果传入的List是String则不会)。因此如果需要原始顺序,需要对查询字段进行排序,具体如下:
1
2
3
4
5
6
7
<foreach collection="ids" item="item" open="(" separator="," close=")">
#{item}
</foreach>
ORDER BY FIELD
<foreach collection="ids" item="item" open="(id," separator="," close=")">
#{item}
</foreach>
4. 其他 未完待续。。。。。。