前言

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里。

水煮MyBatis(二一)- 动态SQL:@Provider

源码

这儿介绍的源码不多,首要是两个部分

  • 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里办法的参数称号;

这是下面比如里的参数处理结果

水煮MyBatis(二一)- 动态SQL:@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