Mybaits源码学习(七):动态参数

一、前文回顾

在前文中我们分析了Mybatis是如何替我们实现了我们所编写的Mapper接口(采用JDK动态代理,在获取Mapper的时候创建Mapper的实现类)。本文将继续学习Mybatis中其他的知识,今天索要研究的是Mybatis是如何处理动态SQL的。

二、动态SQL

在说动态SQL之前,我们先说下静态SQL,所谓静态SQL就是SQL在运行期间不会改变,例如要查询用户表的所有用户我们会这样写 select * from user (这里不讨select * 是否规范);与之相对的就是动态SQL,即在运行期间SQL会经常改变,例如通过用户名查询用户可能是 select * from user where name like ‘%张三%’;或者是select * from user where name like ‘%李四%’;在Mybatis之前我们通常会使用PrepareStatement配合占位符?来实现。之前也有说过Mybatis几乎屏蔽了所有的JDBC操作,我们不在需求这么麻烦的去操作动态SQL,那么Mybatis中是如何实现的呢?

三、Mybatis动态SQL分析

1、前置代码

1、实体类

@Data
public class Company {

    private Long id;

    private String companyName;

    private String companyNo;

    private String address;
}

2、Mapper接口

public interface CompanyMapper {

    List<Company> list(@Param("company") Company company);
}

3、Mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pax.mapper.CompanyMapper">


    <select id="list" resultType="com.pax.entity.Company">
        select * from company
        <where>
            <if test="company.companyNo != null and company.companyNo != ''">
                company_no like concat('%',#{company.companyNo},'%')
            </if>

            <if test="company.companyName != null and company.companyName != ''">
                company_name like concat('%',#{companyName},'%')
            </if>

        </where>

    </select>
</mapper>

4、Main函数

    public static void main(String[] args) throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //创建SqlSession工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory创建SqlSession (try-with-resource,会自动关闭连接)
        try (SqlSession session = sqlSessionFactory.openSession()) {
            CompanyMapper companyMapper = session.getMapper(CompanyMapper.class);
            Company condition = new Company();
            condition.setCompanyName("Test Company Name");
            condition.setCompanyNo("123");
            companyMapper.list(condition);
        }
    }

2、流程Debug

2.1、参数是如何转化的

我们直接在companyMapper.list(condition);这一行打下断点进行Debug。前面的流程不再赘述(如果不是很清楚的可以看之前的文章Mybatis流程Debug ),我们直接来到MapperMethod这个类,
image.png
可以看到当执行完 method.convertArgsToSqlCommandParam(args); 方法之后,我们传递的Company对象就转为成一个类似Map的类型的参数。从方法名上也可以看出该方法的作用就是将我们传递的参数,转化成 命令参数(即SQL参数)
(关注到key为“company”,这个值就是我们再Mapper接口中定义的
List list(@Param(“company”) Company company);

继续Debug该方法,下面代码是转化的核心方法
1659505244011.jpg
源码分析:
1、首先会判断是否有参数,如果没有参数则返回null(静态SQL没有参数)
2、通过反射获取方法的参数中是否有 “@Param”注解,如果没有注解且只有一个参数直接返回传递的参数(结合到本案例中就是直接返回 company对象)
3、否则会封装为Map<String, Object>对象,如果我们自定义了key则使用我们自定义key,同时也会添加名为 param1,param2…的key,如下图
image.png

2.2、参数是如何拼接到SQL

分析完了参数是如何转化的,接下来颜探究参数是如何拼接到SQL中的,继续Debug
result = sqlSession.selectList(command.getName(), param);,一路Debug我们来到了BaseExecutor,
image.png
可以看到当执行完ms.getBoundSql(parameter);之后我们的SQL已经将我们所写的等标签转化为了对应的SQL,同时也将动态参数用占位符?来代替。(至于如何转化的放到后面将,这里讲参数是如何拼接到sql中的)。既然已经拿到了含有占位符的SQL,那么接下来就是要用实际的参数去替代占位符(?),继续Debug。
一路Debug我们停在了SimpleExecutor的prepareStatement方法,方法名中也可以看出该方法返回的是一个预编译的Statment对象,进入该方法
image.png
源码分析:
1、获取数据库连接
2、创建PrepareStatment(预编译的Statment对象)
3、处理参数
继续Debug处理参数的方法

  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            //核心代码
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

源码分析:这部分源码相对来说很长,但是并不是非常的复杂。核心得逻辑就是通过之前封装的Map对象取到我们传递的参数,并且根据类型判断是否有对应的TypeHandler(类型处理器,用于自定义处理参数),接着根据不同类型的参数去调用不同类型的处理器,再根据配置文件里JDBC类型和Java类型的映射关系最终给SQL动态赋值。(了解即可)

四、总结

本文讲述了Mybatis中是如何动态的为我们的SQL添加参数,下一篇文章将会学习在Mybatis中我们所写的标签是如何被解析的,例如 等等,希望对你有所帮助。

未完待续

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值