美文网首页
这可能是简书最白话的MyBatis源码解读

这可能是简书最白话的MyBatis源码解读

作者: 爪哇部落格 | 来源:发表于2019-07-17 17:58 被阅读0次

    本文是MyBatis使用以及源码浅析markdown重构版本,对原文的排版进行了重构,同时对原文内容进行了完善,欢迎大家一起来阅读。码字不易,欢迎大家转载,烦请注明出处;谢谢配合

    简介

    MyBatis是一个普遍应用并且十分优秀的持久层框架;本文将从MyBatis的使用和源码阅读两个方面展开;本文的演示环境如下:

    • JDK1.8
    • MySQL 8.0.15
    • MyBatis 3.4.6

    MyBatis使用指南

    step.1 构建SqlSessionFactory

    每个基于MyBatis的应用都是以SqlSessionFactory为核心的,SqlSessionFactory可以通过SqlSessionFactoryBuilder来构建;常用的有两种方式,一种是通过XML配置构建,而另一种则是通过Configuration实例对象来构建。

    • 通过XML构建
    @Before
    public void getSqlSessionFactory() throws IOException {
        String configPath = "mybatis.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(configPath);
        sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    }
    
    • mybatis.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 resource="mysql.properties"></properties>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="xin/sunce/mybatis/dao/CustomerDao.xml"/>
            <mapper resource="xin/sunce/mybatis/dao/StudentDao.xml"/>
            <mapper resource="xin/sunce/mybatis/dao/ClassDao.xml"/>
        </mappers>
    </configuration>
    
    • 通过Configuration实例对象来构建

    step.2 获取SqlSession

    获取到SqlSessionFactory以后,我们便可以获取到SqlSession

    SqlSession sqlSession = sessionFactory.openSession();
    

    step.3 执行SQL语句

    执行SQL语句的方法也并非只用一种,既可以通过Mapper来执行,亦可以直接通过SqlSession来执行

    • 通过Mapper执行
    @Test
    public void testQuery() {
        SqlSession sqlSession = sessionFactory.openSession();
        try {
            StudentDao mapper = sqlSession.getMapper(StudentDao.class);
            Student student = mapper.getStudentById(1);
            LOGGER.info("query table student result: " + student.toString());
        } finally {
            sqlSession.close();
        }
    }
    
    • 通过SqlSession执行

    step.4 映射的sql语句

    当然,映射的SQL语句也是不一定必须通过XML文件来完成的,也是可以通过注解来实现的

    • 通过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="xin.sunce.mybatis.dao.StudentDao">
      <cache/>
      <select id="getStudentById" parameterType="int" resultType="xin.sunce.mybatis.entity.Student">
        SELECT id,name,age FROM student WHERE id = #{id}
      </select>
    </mapper>
    
    • 通过注解映射
    public interface StudentDao {
      @Select("SELECT id,name,age FROM student WHERE id = #{id}")
      Student getStudentById(int id);
    }
    

    作用域以及生命周期

    依赖注入框架会创建线程安全的SqlSessionMapper并将它们注入到你的bean中,因此你可以直接忽略它们的生命周期;如何通过依赖注入框架来使用MyBatis,后面我们将会介绍;你也可以参看MyBatis-Spring

    SqlSessionFactoryBuilder

    这个类可以被实例化,创建以及销毁,一旦在其创建SqlSessionFactory,其实就不需要它了;所以它最好的作用域就是方法作用域(局部方法),当然你可以通过它创建多个SqlSessionFactory实例,当然最好不要让SqlSessionFactoryBuilder一直存在,以保证XML资源被用于更重要的事情。

    SqlSessionFactory

    SqlSessionFactory一旦被创建就应该在应用运行期间一直存在;它不应该被频繁的销毁创建,所以它的作用域应该是应用作用域;最好通过单例模式来使用。

    SqlSession

    每个线程都应该有自己的SqlSessionSqlSession不是线程安全的,所以它不能被线程共享,所以SqlSession不能被置于静态类,或者一个类的实例变量;所以它的作用域最好是请求或者方法。例如在HTTP请求中,应该每次收到一个请求,便打开一个SqlSession,响应之后,立即关闭SqlSession

    Mapper

    通过使用的Demo,我们也知道Mapper实例是通过SqlSession获得的,所以它的作用域也是方法作用域。

    以上MyBatis使用指南,主要参考MyBatis官方文档

    源码解读

    step.1 SqlSessionFactoryBuilder

    了解以上知识,让我们对MyBatis有了进一步的了解;便于我们捕捉源码的阅读方向;我们知道SqlSessionFactory是通过SqlSessionFactoryBuilder来构建的,接下来我们首先来看看它。

    SqlSessionFactoryBuilder

    我们看到各种SqlSessionFactoryBuilder#build方法,通过XMLConfigBuilderbuild方法:

    public SqlSessionFactory build(InputStream inputStream,
                       String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, 
                    environment, properties);
            //XMLConfigBuilder的实例调用parse(),最终返回Configuration实例
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
    
            try {
                inputStream.close();
            } catch (IOException var13) {
            }
    
        }
        return var5;
    }
    

    最终方法:通过 Configuration实例来构建

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
    

    以上代码也印证了我们主要介绍的两种方法来构建SqlSessionFactory,最终会返回一个DefaultSqlSessionFactory实例。

    step.2 SqlSessionFactory

    SqlSessionFactory

    我们可以看到SqlSessionFactory主要提供了开启SqlSession以及获取Configuration的方法;知道了接口作用,我们再来看看默认的实现类DefaultSqlSessionFactory

    step.3 SqlSession

    获取SqlSession最终都交给了两个私有的方法:openSessionFromDataSourceopenSessionFromConnection;顾名思义分别是通过数据源来获取,通过连接来获取;两个方法大同小异,我们来详细看看其中一个。

    openSessionFromDataSource

    以上便是创建SqlSeesion的过程,利用public DefaultSqlSession(Configuration configuration, Executor executor)此构造方法来获取SqlSession实例,而Executor也是在这个时期由configuration#newExecutor()方法创建的,这里需要记一下,下文介绍Executor会涉及。

    SqlSeesion

    我们发现SqlSession提供了所有对数据库的操作,各式各样的增删改查,以及获取映射Mapper的方法;接下来我们仔细研读一下默认的实现类DefaultSqlSessionselectList方法。

    selectList

    我们发现在DefaultSqlSession实现类中SqlSeesion封装的对数据库的操作最终都是有Executor来执行的;相当于SqlSeesion提供对数据库相应的操作,而具体的职责是有Executor来完成的,而Executor执行的MappedStatement是在configuration获取的。

    step.4 Executor

    Executor

    下图是Executor的继承实现关系

    Executor

    找到Executor以后,我们离真相又近了一步;Executor从设计上就考虑到了缓存,我们可以从createCacheKeyclearLocalCache等方法看到其设计的巧妙之处;这里我们先忽略缓存设计(后面会做详细说明),我们先来看看它的抽象类BaseExecutor

    BaseExecutor

    我们看到BaseExecutorExecutor接口进行了实现,最终调用抽象方法doXX;需要交由子类去实现。以doUpdatedoQuery为例,我们来看看子类实现。

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, 
          ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
    
        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(
                this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }
    
        return var9;
    }
    

    最终MappedStatement都是由StatementHandler去执行,至此一个映射语句的完整执行流程就此结束。

    看到这里有的小伙伴很能会心急了,怎么执行流程都梳理完了,还没找到Mapper的映射过程,别急,我们忽略了一个十分重要的类Configuration,我们再次必须强调一下,Configuration基本存在于整个流程,从通过SqlSessionFactoryBuilder构建SqlSessionFactory,到SqlSessionFactoryExecutor的创建,开启SqlSession,再到映射语句的执行。

    step.5 Configuration

    还记得我们开篇就提到过Configuration实例有两种构建方式,一种是通过XMLConfigBuilder#parse方法,另一种是利用public Configuration(Environment environment)构造方法;所以Configuration从实质上来说就是XML的java对象表示。

    Configuration

    这些元素在Configuration的成员变量中都可以找到;参考上图。

    step.5.1 注册,重头戏,Mapper映射过程

    我们先来看看Configuration的成员变量MapperRegistrymappedStatements

    image.png

    这里先剧透一下,MapperRegistryMapper注册器,而mappedStatements则是最终映射成的一个个sql(注解和Mapper.xml中的sql语句)集。

    在构建Configuration时会调用Configuration#addMapper()。完成Maper类对象(class对象)到MapperRegistry的注册,knownMappers是MapperRegistry的成员变量用于记录注册的Mapper。

    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    

    添加Maper类对象(class对象),获取时(getMapper)再通MapperProxyFactory构建MapperProxy返回
    添加完成之后则会将Mapper对应的XML进行加载,并添加到Configuration的mappedStatements中。

    getMapper addMapper

    addMapper调用链路如下:

    MapperAnnotationBuilder#parse()-->MapperAnnotationBuilder#loadXmlResource-->XMLMapperBuilder#parse()-->XMLMapperBuilder#configurationElement()-->XMLMapperBuilder#buildStatementFromContext()-->XMLStatementBuilder.parseStatementNode()-->MapperBuilderAssistant#addMappedStatement()

    最终由MapperBuilderAssistant辅助完成MappedStatement添加

    addMappedStatement
    step.5.2 使用,动态代理

    getMapper时,MapperProxyFactory#newInstance构建MapperProxy

    newInstance

    MapperProxy#invoke() 方法调用

    image.png

    MapperMethod#execute() 方法调用

    execute

    最终回到sqlSession,交由它去执行。

    总结

    通过对源码的分析认识,我们用一张流程图来总结大致流程。

    流程图

    项目调试地址:https://github.com/sexylowrie/mybatis-teach

    相关文章

      网友评论

          本文标题:这可能是简书最白话的MyBatis源码解读

          本文链接:https://www.haomeiwen.com/subject/rynqlctx.html