hello~各位读者好,我是鸭血粉丝(大家可以称呼我为「阿粉」)。今天,阿粉带着大家来了解一下获取 mapper.xml 配置文件中的sql

上期回顾

首先,我们还是回顾一下上篇文件的内容。先看下这个测试类,大家还有印象吗:

public class MybatisTest {
    @Test
    public void testSelect() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            FruitMapper mapper = session.getMapper(FruitMapper.class);
            Fruit fruit = mapper.findById(1L);
            System.out.println(fruit);
        } finally {
            session.close();
        }
    }
}

上篇源码分析讲了 获取mybatismapper 接口,它是一个代理类。这次,我们来了解下 获取 mapper.xml 配置文件中的sql。

初始化做了什么事

阿粉其实第一篇的时候就已经讲过了 mybatis 的初始化,但是已经有一段时间了。这篇文章又正好要讲怎么获取 mapper.xml 里面的 sql,所有这里阿粉再简单说下mapper.xml的初始化。

2.1 mapper.xml

首先看下阿粉测试的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.cl.mybatis.mapper.FruitMapper">
    
    <sql id="fruit">id,name </sql>
    
    <select id="findById" resultType="fruit">
        select <include refid="fruit"/> from fruit where id =#{id}
    </select>
</mapper>
2.2  Configuration -> mappedStatements

然后,所有解析的 xml 都会放到Configuration这个类里面。那么跟着阿粉来看下mapper.xml解析之后是个什么东西。

这里阿粉就直接给出结果,就不再次去走下初始化流程了。

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

mappedStatementsConfiguration类里面的一个属性,这个就是用来放我们的 mapper.xml里面的信息。我们来看下具体是怎么放的。

首先在 SqlSession session = sqlSessionFactory.openSession(); 这段代码打个断点,sqlSessionFactory 已经构建了,里面有一个  Configuration 属性。看下阿粉截的图。

mappedStatements 是一个map,key 等于com.cl.mybatis.mapper.FruitMapper.findById,这个是什么?是不是就是mapper.xml 类的全路径,加上 <select>标签的 id 。然后 value 是一个 MappedStatement 对象,每个 <select | update | insert | delete> 会解析成一个 MappedStatement  对象。MappedStatement  对象里面有一个 sqlSource  属性,这个放的就是我们自己写的 sql 。

现在是不是很清楚了,只要我们获取到com.cl.mybatis.mapper.FruitMapper.findById 这个 key,是不是就可以获取到对应的 sql,那怎么获取这个key呢?

获取对应的key

3.1 代理类 -> h

还记得阿粉上一篇文章吗?获取 mapper 接口,返回的是一个代理对象,使用的是JDK的动态代理。看下创建代理类的方法。

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

mapperProxy 这个是我们的 h 类,h 类里面肯定有一个 invoke 方法。首先什么是 h 类?我们看下 newProxyInstance 方法。

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        。。。
    }

实现 InvocationHandler 接口的类就是 h类。还是不懂的可以去看下 JDK 实现的动态代理模式,阿粉这里就不继续深入了。

3.2 获取 key

现在们去看下 h类里面的 invoke 方法。MapperProxy -> invoke

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

我们看下倒数第二行的 cachedMapperMethod 方法,ctrl + 左键点进去。

private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

看下这句代码 MapperMethod(mapperInterface,method,sqlSession.getConfiguration())) 我们来猜测一下,mapperInterface  是不是我们的 mapper 接口,这个能不能获取到 mapper 接口的全路径,method 是不是我们当前调用的方法 ,看下阿粉的测试方法:Fruit fruit = mapper.findById(1L) ,获取 findById() 这个方法的名字,就是 "findById",一拼接就获取到了key,对不对?我们带正式一下,ctrl + 左键 MapperMethod。

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

在倒数第一行代码上打个断点。我们看下 command 这个成员变量。

看,我们的 key 是不是拿到了,那么 sql 是不是也拿到了。

总结

最后,阿粉在来梳理下这个流程:

  • mybatis 初始化的时候,会将 mapper.xml 解析放到 Configuration 类的mappedStatements 属性上。

  • mappedStatements 是一个map ,它的 value 是一个对象,对象里面的一个属性存了我们写的 sql 语句。要想获取 value 必须找到对应的 key。

  • 获取 key 是通过 JDK代理模式实现的,代理 mapper 接口,在执行这个接口的方法的时候,就会获取到当前接口的全路径和当前方法的名字。

好了,今天阿粉就讲到这里,下次阿粉会带着到家去了解一下,获取到 sql 之后,mybatis是怎么去执行 sql 的。我们下次 mybatis 源码分析再见。

最后说两句(求关注)

最近大家应该发现微信公众号信息流改版了吧,再也不是按照时间顺序展示了。这就对阿粉这样的坚持的原创小号主,可以说非常打击,阅读量直线下降,正反馈持续减弱。

所以看完文章,哥哥姐姐们给阿粉来个在看吧,让阿粉拥有更加大的动力,写出更好的文章,拒绝白嫖,来点正反馈呗~。

如果想在第一时间收到阿粉的文章,不被公号的信息流影响,那么可以给Java极客技术设为一个星标

最后感谢各位的阅读,才疏学浅,难免存在纰漏,如果你发现错误的地方,由于本号没有留言功能,还请你在后台留言指出,我对其加以修改。

最后谢谢大家支持~

最最后,重要的事再说一篇~

快来关注我呀~
快来关注我呀~
快来关注我呀~

< END >

如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐