Mybatis 源码分析与及Mybatis-spring整合(上)

僤鯓⒐⒋嵵緔 提交于 2019-12-18 03:58:46

本文将分析Mybatis 如何获取SQL,执行SQL,与spring整合后spring做了些什么


  • JDBC

mybatis实际上对JDBC进行了封装,那么先写一个JDBC的测试方法

 @Test
    public void testJdbc() throws SQLException, ClassNotFoundException {
        String sql="SELECT * FROM my_user";
        String driver="com.mysql.jdbc.Driver";//需要的数据库驱动
        String url = "jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai";
        String userName = "root";
        String password = "root";
        Class.forName(driver);
        Connection conn =DriverManager.getConnection(url,userName, password);//建立连接
        PreparedStatement pstmt=conn.prepareStatement(sql);
        ResultSet rs=pstmt.executeQuery();//执行语句
        System.out.println(rs);
        conn.close();
    }

这里不对这段代码过多阐述,下文分析mybatis时涉及的部分会再论述,这里先留个印象.


  • Mybatis

接下来看看单纯的mybatis代码.代码结构如下

 mybatis-config配置文件内容如下:

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;allowMultiQueries=true&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mybatis/mapper/UserMapper.xml"/>
        <!--<mapper class="study.mybatis.mapper.MyUserMapper"/>-->
    </mappers>
</configuration>

配置文件中mappers属性可以使用两种写法.一种是度配置文件,一种是读class类,如果使用xml的配置方式,由于xml中引用了MyUserMapper,所以无论是注解方式还是xml配置方式的SQL都会生效,而如果只引用MyUserMapper,则xml中的SQL无法生效.

MyUserMapper代码如下:

这里使用了两种写法,一种从mapper配置文件中获取SQL,一种从注解中配置SQL.

关于配置这里不多赘述,有需求可以去官网了解 :https://mybatis.org/mybatis-3/zh/index.html.

UserMapper.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="study.mybatis.mapper.MyUserMapper">
    <select id="selectCount" parameterType="integer" resultType="java.lang.Integer">
        SELECT COUNT(0) FROM  my_user
    </select>
</mapper>

接下来看一段测试代码.跟着这段代码看看源码内部逻辑

    @Test
    public void testXml() throws IOException {
        String resource="mybatis-config.xml";
        InputStream  inputStream= Resources.getResourceAsStream(resource);
        // 加载配置文件,初始化SqlSessionFactory
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
        // 获取sqlSession
        SqlSession sqlSession=sqlSessionFactory.openSession();
        // 执行代码
        Integer selectCountResult=sqlSession.selectOne("study.mybatis.mapper.MyUserMapper.selectCount");
        Integer selectCountByAnnotationResult=sqlSession.selectOne("study.mybatis.mapper.MyUserMapper.selectCountByAnnotation");
        // 打印结果
        System.out.println(selectCountResult);
        System.out.println(selectCountByAnnotationResult);
        //关闭sqlSession
        sqlSession.close();
    }

先说结论:加载配置文件流,构建SqlSessionFactory,在构建的过程中会解析mybatis-config配置,解析配置时会遍历配置里面的所有mapper,并且再遍历每个mapper里的所有SQL,并且优先xml中配置的SQL,后解析注解中的配置

 

首先看看SqlSessionFactory初始化的时候做了什么.

 首先获取流,进入build,构建方法

 中间逻辑为读取XML中的所有配置,并初始化,这里被解析的有mybatis-config.xml文件,也包括在该配置文件中配置的mapper.也就是如下部分,而这部分中可以又包含了若干的mapper.在SqlSessionFactory初始化的时候会将mapper文件一起解析.

 具体逻辑就是遍历所有mapper,解析mapper中的sql.

在XMLConfigBuilder的mapperElement方法中会遍历mybatis配置文件汇总配置的所有mapper对象

而在遍历mapper对象时还会进入每个mapper中遍历里面配置的所有SQL.

在XMLMapperBuilder中的buildStatementFromContext方法会解析mapper中的所有方法,本例只有一个,所以size为1

 而解析SQL方法时就会涉及到SQL的拼接.在SqlSourceBuilder中会对SQL进行处理.这里也就是mybatis配置文件中会将#{value}替换到执行SQL的逻辑,这里顺便提一句,使用${value}的话,不会被替换,而是直接拼接SQL,这也是#与$在mybatis配置(mapper)中的区别

 遍历了一个mapper(xml)中的SQL后,会再次扫描注解中的SQL

 在MapperAnnotationBuilder中会解析出注解中的SQL,然后走和解析Mapper一样的后续逻辑

最后将信息全部封装给mybatis的 Configuration 对象中完成了初始化逻辑,那么接下来继续看测试代码的后续逻辑

openSession()方法则是获取SqlSession 

现在来看看查询方法

这里不一一截图,将调用栈给贴出来

org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String)

org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

org.apache.ibatis.mapping.MappedStatement#getBoundSql

执行到MappedStatement#getBoundSql时会获取需要执行的SQL语句

而这条SQL就是在SqlSessionFactory初始化时给Configuration对象赋值的.这样,执行的SQL就找到了,那么最终是如何执行的呢?

这里也把调用栈贴出来

org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String)

org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

org.apache.ibatis.executor.SimpleExecutor#doQuery


org.apache.ibatis.executor.statement.RoutingStatementHandler#query

org.apache.ibatis.executor.statement.PreparedStatementHandler#query


最后到了PreparedStatementHandler的query方法,可以看到,最后调用的是JDBC的方法,PreparedStatement的execute()方法


 

好的,那么这里可以看到mybatis最终调用的是JDBC的方法,执行SQL,而被执行的SQL是在SqlSessionFactory初始化的时候从配置文件及注解中加载进Configuration对象中的.

那么使用sqlSession的selectOne方法与调用mapper对象有什么区别呢?

再看下一个测试方法

     @Test
    public void testMapper() throws IOException {
        String resource="mybatis-config.xml";
        InputStream  inputStream= Resources.getResourceAsStream(resource);
        // 加载配置文件,初始化SqlSessionFactory
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
        // 获取sqlSession
        SqlSession sqlSession=sqlSessionFactory.openSession();
        // 获取mapper
        MyUserMapper myUserMapper=sqlSession.getMapper(MyUserMapper.class);
        Integer result =myUserMapper.selectCount();
        System.out.println(result);
    }

 跟入代码,可以看到,最后执行的逻辑,依旧是sqlSession的selectOne方法

以上就是mybatis部分源码分析内容(个人理解)

mybatis与spring的整合,及源码分析,请看下节.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!