MyBatis中动态SQL语句@Provider的用法
作者:大饭盒
前言
Mybatis里的动态SQL,估计用到的同学不是很多,毕竟在xml文件中定义sql语句的方式,已经可以满足绝大部分的开发需求,方便又简单。没有痛点,也就少了动力。这一章就来聊聊这块,对于有代码洁癖的人来说,还是很赏心悦目的。
四个注解
@Provider系列的注解有四个:
- @SelectProvider,被定义用来提供查询方法的SQL;
- @UpdateProvider,被定义用来提供更新方法的SQL;
- @DeleteProvider,被定义用来提供删除方法的SQL;
- @InsertProvider,被定义用来提供保存方法的SQL;
官方例子
public interface UserMapper { // 保存用户数据 @InsertProvider(type = SqlProvider.class, method = "insert") void insert(User user); public static class SqlProvider { // 对应@InsertProvider注解里的method,返回对应sql public static String insert() { return "INSERT INTO users (id, name) VALUES(#{id}, #{name})"; } } }
序列图
此章节主要源码都在ProviderSqlSource里。
源码
这里介绍的源码不多,主要是两个部分
sql拼接;
- 反射执行provider方法;
sql拼接
sql拼接,主要依赖AbstractSQL里的静态方法,下面讲一下update语句。
List<String> sets = new ArrayList<>(); List<String> tables = new ArrayList<>(); List<String> where = new ArrayList<>(); public T UPDATE(String table) { // 指定类型为update sql().statementType = SQLStatement.StatementType.UPDATE; // 设定表名,tables集合添加元素 sql().tables.add(table); // 返回当前sqlBuilder对象 return getSelf(); } private String updateSQL(SafeAppendable builder) { // 拼接 UPDATE [table_name] sqlClause(builder, "UPDATE", tables, "", "", ""); joins(builder); // 拼接sets集合 sqlClause(builder, "SET", sets, "", "", ", "); // 拼接where条件集合 sqlClause(builder, "WHERE", where, "(", ")", " AND "); // 拼接限定条件 limitingRowsStrategy.appendClause(builder, null, limit); return builder.toString(); }
注意:sql拼接是按照一定顺序的,tables -> sets -> where,就算是我们在代码里,刻意打乱顺序,也没有影响,比如:
WHERE("md5 = #{md5}"); SET("update_time = NOW()"); UPDATE(tableName);
其实是可以的,毕竟Provider本质上,就是提供了待执行的sql预处理语句。看官方的例子,其实就没有使用拼接,在后面的例子里,如果不进行参数判空,可以写成这样:
public String updateStatus() { return "UPDATE tb_image SET update_time = NOW(), status = ? WHERE (md5 = ?) AND (status = ?)"; }
反射执行provider方法
private String invokeProviderMethod(Object... args) throws Exception { Object targetObject = null; if (!Modifier.isStatic(providerMethod.getModifiers())) { // 如果是非静态方法,则需要一个类实例 targetObject = providerType.getDeclaredConstructor().newInstance(); } // 反射执行@Provider里指定的方法 CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args); // 返回sql语句 return sql != null ? sql.toString() : null; }
这里返回的sql语句,是基于JDBC预处理语法的字符串,例如UPDATE tb_image SET update_time = NOW(), status = ? WHERE (md5 = ?) AND (status = ?)
参数是怎么处理的
CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args);
这一行代码里,指定了方法参数【args】,首先说明,provider方法里的参数,都来自于Mapper里的方法参数值,从params里获取对应参数名称的值,写入到args;
private Object[] extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames) { Object[] args = new Object[argumentNames.length]; for (int i = 0; i < args.length; i++) { if (providerContextIndex != null && providerContextIndex == i) { args[i] = providerContext; } else { // 关键就是这一句,从params里获取对应参数名称的值,写入到args; args[i] = params.get(argumentNames[i]); } } return args; }
方法参数介绍
- params:Mapper里对应方法的所有参数;
- argumentNames:Provider里方法的参数名称;
这是下面例子里的参数处理结果
我的例子
provider
注意provider里方法的参数,可以和mapper参数一致,也可以缺失几个。引用mapper的参数,主要是为了进行逻辑分支判定。
public class ImageDynamicProvider { /** * 图片更新 * * @return sql */ public String updateStatus(Integer newStatus, Integer oldStatus) { Table table = ImageInfo.class.getAnnotation(Table.class); String tableName = table.name(); return new SQL() { { UPDATE(tableName); SET("update_time = NOW()"); // 判定是否需要更新状态 if (newStatus != null) { SET("status = #{newStatus}"); } WHERE("md5 = #{md5}"); // 判定是否需要此条件 if (oldStatus != null) { AND().WHERE("status = #{oldStatus}"); } } }.toString(); } }
引用Provider
在Mapper对应的方法上面,根据具体类型,选择注解。此处是更新语句,所以使用@UpdateProvider,参数提供了具体的类和方法,供后续执行反射方法。
/** * 更新状态 * * @param md5 图片摘要信息 */ @UpdateProvider(value = ImageDynamicProvider.class, method = "updateStatus") int updateStatusByProvider(@Param(value = "md5") String md5, @Param(value = "oldStatus") int oldStatus, @Param(value = "newStatus") int newStatus);
测试用例
@Test public void provider() { imageInfoMapper.updateStatusByProvider( "6e705a7733ac5gbwopmp02", 50, 199); }
输出
==> Preparing: UPDATE tb_image SET update_time = NOW(), status = ? WHERE (md5 = ?) AND (status = ?)
==> Parameters: 199(Integer), 6e705a7733ac5gbwopmp02(String), 50(Integer)
<== Updates: 1
小问题
如果既有Provider,也有xml方法映射。就是说我们定义了@Provider注解,又在xml中写了mapper方法的映射sql语句,这种场景,Mybatis在启动时就会报错。
nested exception is java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.essay.dao.ImageInfoMapper.updateStatusProvider
到此这篇关于MyBatis中动态SQL语句@Provider的用法的文章就介绍到这了,更多相关MyBatis 动态SQL @Provider内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!