Mybatis学习笔记五之源码剖析

一、Mybatis底层工作原理

       上篇文章中我们讲到Mybatis和数据库交互有两种方式,一是使用传统的Mybatis提供的API,第二是Mapper的代理方式。项目中一般都是使用第二种方式,那这两种方式的初始化过程有什么不同呢。

      Mybatis在初始化的时候,会将Mybatis的配置信息全部加载到内存中,使用org.apache.ibatis.session.Configuration实例来维护。首先对Configuration对象介绍:
Configuration对象的结构和Mybatis核心配置文件sqlMapConfig.xml的对象几乎相同。回顾一下xml中的配置标签有哪些:properties(属性)、settings(设置)、typeAliases(类型别名)、typeHandlers(类型处理器)、pluins(拦截器)等。
Configuration也有对应的对象属性来封装它们。也就是说,初始化配置文件信息的本质就是创建Configuration对象并将解析的xml数据封装到Configuration内部属性中。

1、传统方式

sqlMapConfig.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--加载外部的properties文件-->
    <properties resource="jdbc.properties"></properties>

    <plugins>
        <plugin interceptor="com.lagou.plugin.MyPlugin">
            <property name="name" value="tom"/>
        </plugin>

        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

    <!--environments:运行环境-->
    <environments default="development">
        <environment id="development">
            <!--当前事务交由JDBC进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--当前使用mybatis提供的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射配置文件-->
    <mappers>
       <!-- <mapper class="com.lagou.mapper.IUserMapper"></mapper>-->
        <package name="com.lagou.mapper"/>
    </mappers>
</configuration>

我们先编写一个测试类:

public void test1() throws IOException {
    // 1. 读取配置文件,读成字节输入流,注意:现在还没解析
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    // 2. 解析配置文件,封装Configuration对象   创建DefaultSqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    // 3. 生产了DefaultSqlsession实例对象   设置了事务不自动提交  完成了executor对象的创建
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.(1)根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象
       //(2)将查询任务委派了executor执行器
    List<Object> objects = sqlSession.selectList("namespace.id");

    // 5.释放资源
    sqlSession.close();

  }

(1)第一步,读取配置文件到字节输入流InputStream中(初始化过程)

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

(2)第二步,解析配置文件,封装Configuration对象   创建DefaultSqlSessionFactory对象(初始化过程)

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

我们可以点进build方法看看,我们可以看到build方法中创建了一个xml解析器parser

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象
            return build(parser.parse());
}

我们再点进parser.parse()中看看

  public Configuration parse() {
        // 若已解析,抛出 BuilderException 异常
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 标记已解析
        parsed = true;
        ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
        // 解析 XML configuration 节点
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

继续看parseConfiguration(parser.evalNode("/configuration"))方法,该方法会解析sqlMapConfig.xml中支持配置的所有标签,并封装到Configration中

private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

我们重点看 mapperElement(root.evalNode("mappers")),该方法就是解析我们配置在sqlMapConfig中的映射文件并封装到Mybatis核心配置类Configration中

<!--引入映射配置文件-->
    <mappers>
       <!-- <mapper class="com.lagou.mapper.IUserMapper"></mapper>-->
        <package name="com.lagou.mapper"/>
    </mappers>

我们来重点看看Configration这个类中的


 介绍一下MappedStatement:MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的 标签都被封装到了此对象中。比如id,resultType、paramterType还有最重要的sql语句。

<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

 (3)第三步,生产了DefaultSqlsession实例对象   设置了事务不自动提交  完成了executor对象的创建(sql执行过程)

 SqlSession sqlSession = sqlSessionFactory.openSession();

先简单介绍下SqlSession:
SqlSession是一个接口,它有两个实现类:DefaultSqlSession(默认)和SqlSessionManager(弃用),SqlSessionManager是Mybatis中用于和数据库交互的顶层类,通常将它与ThreadLocal绑定,一次会话使用一个SqlSession,并且在使用完后需要close。

public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
}

SqlSession中最重要的两个参数,configuration与初始化时相同,Executor为执行器

Executor也是一个接口,他有三个常用的实现类:BatchExecutor(重用语句并执行批量更新)、ReuseExecutor(重用预处理语句preparedstatements)、SimpleExecutor(普通的执行器,默认)

我们点进openSession()方法中看看:获取了Executor类型、并设置了事务不自动提交

//6. 进入openSession方法
    @Override
    public SqlSession openSession() {
        //getDefaultExecutorType()传递的是SimpleExecutor
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
    }

继续看看openSessionFromDataSource()方法:我们可以看到方法中创建了执行器Executor和DefaultSqlSession对象

(4)第四步,根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象,将查询任务委派了executor执行器(Executor代码剖析)
     

 List<Object> objects = sqlSession.selectList("namespace.id");

我们点进selectList方法:

点进query()方法中我们可以看到,先创建了缓存key

继续看query()方法:缓存中没有数据则从数据库中查,调用queryFromDatabase()方法

再看queryFromDatabase()方法:缓存我们已经在之前文章讲过了,这里重点看sql执行操作

进doQuery()看看:这里创建了StatementHandlder对象,并将查询操作交给了StatementHandlder,这也验证了我们上篇文章中讲的Mybatis的层次结构(Executor又交给了StatementHandlder去执行)

我们先看prepareStatement()方法:parameterHandler完成对参数设值

我们再看handler.query()方法:由resultSetHandler处理返回结果

2,Mapper的代理方式

Mapper的代理方式和传统方式前三部相同,我们先编写个测试类

 /**
   * mapper代理方式
   */
  public void test2() throws IOException {

    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = factory.openSession();

    // 使用JDK动态代理对mapper接口产生代理对象
    IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);

    //代理对象调用接口中的任意方法,执行的都是动态代理中的invoke方法
    List<Object> allUser = mapper.findAllUser();

  }

(4)第4步,使用JDK动态代理对mapper接口产生代理对象

IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);

在看getMapper()方法之前,我们先回去看看Mybatis初始化解析文件方法中解析mappers标签这块

我们重点看这一段:当我们配置了package标签,即该包下的所有Mapper文件都会被扫描时,会调用configuration.addMappers()方法

点进addMappers()方法可以看到,初始化时会扫描包下所有Mapper接口(即Dao接口),并添加到mapperRegistry中

我们查看MapperRegistry这个类可以看到里面有个HashMap,该key值就是Mapper接口类型,MapperProxyFactory就是接口代理工厂,一个Mapper接口对应MapperProxyFactory代理工厂

我们回到sqlSession.getMapper方法中:

在继续往下看:我们可以看到根据传的Class类型(key)直接获取到了对应的接口代理工厂MapperProxyFactory

我们再看mapperProxyFactory.newInstance()方法:创建了MapperProxy类,该类实现了JDK动态代理的InvocationHandler接口

//MapperProxyFactory类中的newInstance方法
    public T newInstance(SqlSession sqlSession) {
        // 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // 调用了重载方法
        return newInstance(mapperProxy);
    }

我们继续看 newInstance(mapperProxy)方法:这里我们就证实了IUserMapper mapper = sqlSession.getMapper(IUserMapper.class)返回的是JDK动态代理对象

protected T newInstance(MapperProxy<T> mapperProxy) {

        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

(5)第五步,代理对象调用接口中的任意方法,执行的都是动态代理中的invoke方法

List<Object> allUser = mapper.findAllUser();

我们找到MapperProxy中的invoke方法:这里是由MapperMethod 对象调用了执行的方法

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果是 Object 定义的方法,直接调用
            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);
        }
        // 获得 MapperMethod 对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // 重点在这:MapperMethod最终调用了执行的方法
        return mapperMethod.execute(sqlSession, args);
    }

我们点进execute()方法中,发现该方法增删改查都有,并且实质在最终和传统方式一样还是由sqlSession执行了查询:sqlSession.selectList()。

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
        switch (command.getType()) {
            case INSERT: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 执行 INSERT 操作
                // 转换 rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // 转换参数
                Object param = method.convertArgsToSqlCommandParam(args);
                // 转换 rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // 无返回,并且有 ResultHandler 方法参数,则将查询的结果,提交给 ResultHandler 进行处理
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                // 执行查询,返回列表
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                // 执行查询,返回 Map
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                // 执行查询,返回 Cursor
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                // 执行查询,返回单个对象
                } else {
                    // 转换参数
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 查询单条
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                            (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值