前言
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