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中数组的定义与使用小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2021-10-10
  • 使用JAVA实现高并发无锁数据库操作步骤分享

    使用JAVA实现高并发无锁数据库操作步骤分享

    一个在线2k的游戏,每秒钟并发都吓死人。传统的hibernate直接插库基本上是不可行的。我就一步步推导出一个无锁的数据库操作,详情看下文
    2013-11-11
  • SpringBoot+随机盐值+双重MD5实现加密登录

    SpringBoot+随机盐值+双重MD5实现加密登录

    数据加密在很多项目上都可以用到,大部分都会采用MD5进行加密,本文主要介绍了SpringBoot+随机盐值+双重MD5实现加密登录,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • 详解java中的6种单例写法及优缺点

    详解java中的6种单例写法及优缺点

    在java中,单例有很多种写法,面试时,手写代码环节,除了写算法题,有时候也会让手写单例模式,这里记录一下单例的几种写法和优缺点。需要的朋友可以参考下
    2018-11-11
  • SpringBoot之webflux全面解析

    SpringBoot之webflux全面解析

    这篇文章主要介绍了SpringBoot之webflux全面解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • java基础的详细了解第六天

    java基础的详细了解第六天

    这篇文章对Java编程语言的基础知识作了一个较为全面的汇总,在这里给大家分享一下。需要的朋友可以参考,希望能给你带来帮助
    2021-08-08
  • Jmeter内置变量vars和props的使用详解

    Jmeter内置变量vars和props的使用详解

    JMeter是一个功能强大的负载测试工具,它提供了许多有用的内置变量来支持测试过程,其中最常用的变量是 vars 和 props,本文通过代码示例详细给大家介绍了Jmeter内置变量vars和props的使用,需要的朋友可以参考下
    2024-08-08
  • Springboot 整合maven插口调用maven release plugin实现一键打包功能

    Springboot 整合maven插口调用maven release plugin实现一键打包功能

    这篇文章主要介绍了Springboot 整合maven插口调用maven release plugin实现一键打包功能,整合maven-invoker使程序去执行mvn命令,结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • Jdk中没有jre文件夹怎么办?如何解决?

    Jdk中没有jre文件夹怎么办?如何解决?

    这篇文章主要介绍了Jdk中没有jre文件夹怎么办?如何解决的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • Java Web项目部署在Tomcat运行出错与解决方法示例

    Java Web项目部署在Tomcat运行出错与解决方法示例

    这篇文章主要介绍了Java Web项目部署在Tomcat运行出错与解决方法,结合具体实例形式分析了Java Web项目部署在Tomcat过程中由于xml配置文件导致的错误问题常见提示与解决方法,需要的朋友可以参考下
    2017-03-03

最新评论