MyBatis中动态SQL语句@Provider的用法

 更新时间:2023年06月22日 09:54:26   作者:大饭盒  
本文主要介绍了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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java环境变量如何配置

    java环境变量如何配置

    这篇文章主要为大家详细介绍了java环境变量配置教程,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • Java的List集合框架之LinkedList详细解析

    Java的List集合框架之LinkedList详细解析

    这篇文章主要介绍了Java的List集合框架之LinkedList详细解析,LinkedList底层是内部Node类的存储,prev、next、item值,同时最外层还有first、last节点,需要的朋友可以参考下
    2023-11-11
  • java中HashMap的原理分析

    java中HashMap的原理分析

    HashMap在Java开发中有着非常重要的角色地位,每一个Java程序员都应该了解HashMap。详细地阐述HashMap中的几个概念,并深入探讨HashMap的内部结构和实现细节,讨论HashMap的性能问题
    2016-03-03
  • java如何动态执行while循环

    java如何动态执行while循环

    这篇文章主要介绍了java如何动态执行while循环问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Java Swing GridBagLayout网格袋布局的实现

    Java Swing GridBagLayout网格袋布局的实现

    这篇文章主要介绍了Java Swing GridBagLayout网格袋布局的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • Java IO字符流缓冲区实现原理解析

    Java IO字符流缓冲区实现原理解析

    这篇文章主要介绍了Java IO字符流缓冲区实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Java判断字符串是否为IP地址的方法

    Java判断字符串是否为IP地址的方法

    这篇文章主要为大家详细介绍了Java判断字符串是否为IP地址的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-08-08
  • MyBatisPlus唯一索引批量新增或修改的实现方法

    MyBatisPlus唯一索引批量新增或修改的实现方法

    本文主要介绍了MyBatisPlus唯一索引批量新增或修改的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • Spring boot 处理大文件上传完整代码

    Spring boot 处理大文件上传完整代码

    这篇文章主要介绍了Spring boot 处理大文件上传,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • IDEA连接mysql数据库报错的解决方法

    IDEA连接mysql数据库报错的解决方法

    这篇文章主要介绍了IDEA连接mysql数据库报错的解决方法,文中有非常详细的图文示例,对出现Server returns invalid timezone. Go to ‘Advanced‘ tab and set ‘serverTimezone‘ prope报错的小伙伴们很有帮助哟,需要的朋友可以参考下
    2021-05-05

最新评论