Mybatis动态SQL生成
当前环境下,mybatis是使用很频繁的一个数据持久层框架。我们很多时候使用xml的方式来配置mybatis的sql,这种方式也称之为mybatis的动态SQL。but,本篇要说的是另一种方式。日常业务中我们可能会遇到很多有关于动态SQL的问题。我们就需要在代码中来编写SQL。
这个时候有的人可能就会想到直接 String sql = "select * from XXX where XXX";对于这种方式我是不推荐的,1、既然是代码,就要有代码的易读性。2、既然是动态sql就要有足够的扩展性。。。。
进入正题:mybatis针对上述问题,提供了一套API来解决我们这个尴尬的窘态。
3.2版本之前:
SqlBuilder 和 SelectBuilder
用法:
public String selectBlogsSql() { BEGIN(); // Clears ThreadLocal variable SELECT("*"); FROM("BLOG"); return SQL(); }
private String selectPersonSql() { BEGIN(); // Clears ThreadLocal variable SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME"); SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON"); FROM("PERSON P"); FROM("ACCOUNT A"); INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID"); INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID"); WHERE("P.ID = A.ID"); WHERE("P.FIRST_NAME like ?"); OR(); WHERE("P.LAST_NAME like ?"); GROUP_BY("P.ID"); HAVING("P.LAST_NAME like ?"); OR(); HAVING("P.FIRST_NAME like ?"); ORDER_BY("P.ID"); ORDER_BY("P.FULL_NAME"); return SQL(); }
具体相关文档:http://www.mybatis.org/mybatis-3/zh/statement-builders.html
在3.2版本之前,通过实现ThreadLocal变量来掩盖一些导致Java DSL麻烦的语言限制。但这种方式已经废弃了,现代的框架都欢迎人们使用构建器类型和匿名内部类的想法。因此,SelectBuilder 和 SqlBuilder 类都被废弃了。
3.2版本之后:
实例:
private String selectPersonSql() { return new SQL() {{ SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME"); SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON"); FROM("PERSON P"); FROM("ACCOUNT A"); INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID"); INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID"); WHERE("P.ID = A.ID"); WHERE("P.FIRST_NAME like ?"); OR(); WHERE("P.LAST_NAME like ?"); GROUP_BY("P.ID"); HAVING("P.LAST_NAME like ?"); OR(); HAVING("P.FIRST_NAME like ?"); ORDER_BY("P.ID"); ORDER_BY("P.FULL_NAME"); }}.toString(); }
————————————————————————————————————————————————————————————————————————————————————————
当然上述都是这些Mybatis本身提供的一些API,要达到动态SQL,我们还差一步,所以来个简单的demo。
public class MybatisSQLTemplate { /** * 插入数据 * @param params * @return */ public String insert(Map<String,Object> params){ return new SQL() .INSERT_INTO(getTableName(params)) .VALUES(buildFiled(params),buildValue(params)) .toString(); } /** * 更新数据 * @param params * @return */ public String update(Map<String,Object> params){ return new SQL() .UPDATE(getTableName(params)) .SET(buildSet(params)) .WHERE(buildWhere(params)) .toString(); } /** * 获取表名 * @param params * @return */ private String getTableName(Map<String,Object> params){ if(params == null || params.get(Condition.TABLE_NAME.name()) == null){ throw new IllegalArgumentException("Table name is required.."); }else { return params.get(Condition.TABLE_NAME.name()).toString(); } } /** * 构建插入字段 * @param params * @return */ private String buildFiled(Map<String,Object> params){ params.remove(Condition.TABLE_NAME.name()); if(params.isEmpty()){ throw new IllegalArgumentException("Parameter name is required.."); } String columns = params.keySet().toString(); return columns.substring(1,columns.length()-1); } /** * 构建插入value值 * @param params * @return */ private String buildValue(Map<String,Object> params){ params.remove(Condition.TABLE_NAME.name()); if(params.isEmpty()){ throw new IllegalArgumentException("Parameter value is required.."); } String columns = params.keySet().stream().map(i->"#{"+i+"}").collect(Collectors.toList()).toString(); return columns.substring(1,columns.length()-1); } /** * 构建更新set数据 * @param params * @return */ private String buildSet(Map<String,Object> params){ params.remove(Condition.TABLE_NAME.name()); if(params.isEmpty()){ throw new IllegalArgumentException("The value to be modified cannot be empty.."); } Object temp = params.remove(Condition.WHERE.name()); String sets = params.keySet().stream().map(i -> i + "=#{" + i + "}").collect(Collectors.toList()).toString(); params.put(Condition.WHERE.name(),temp); return sets.substring(1,sets.length()-1); } /** * 构建where条件(目前只支持单个条件) * @param params * @return */ private String buildWhere(Map<String,Object> params){ params.remove(Condition.TABLE_NAME.name()); if(params.isEmpty() || params.get(Condition.WHERE.name()) == null){ throw new IllegalArgumentException("Missing where condition.."); } Object where = params.get(Condition.WHERE.name()); //可以做多类型判断以兼容更复杂where条件 if(where instanceof String){ String temp = (String) where; return temp + "= #{" + where +"}"; }else { throw new IllegalArgumentException("Where argument is illegal.."); } } }
说明一下,上述的代码中使用的参数都是Map。一是为了测试方便,而是降低复杂度。当然你也可以传入自己的db对象。通过反射机制来读取属性,或者通过自定义注解来使整个代码结构更合理。
接下来说明一下,如何使用。
首先了解一下注解@SelectProvider、@InsertProvider、@UpdateProvide、@DeleteProvider,这四个注解分别用于查、插、改、删,都拥有一个type属性以及一个method属性。type属性用来指明生成SQL的类是那个,method用来表明该类下的那个方法。
public interface OrderInfoMapper {
@InsertProvider(type = MybatisSQLTemplate.class,method = "insert")
int insert(Map<String,Object> params);
@UpdateProvider(type = MybatisSQLTemplate.class,method = "update")
int update(Map<String,Object> params);
}
到这里动态SQL就算完成了。这里的Mapper与XML方式对应的Mapper无异。
写在后面:当然MyBatis不仅提供了API方式的SQL,结果集参数等的映射都是可以做到的。而且如果想要自定义Mybatis的一些行为,可以通过编写插件的方式来实现 @Intercepts。
2018-11-08 记