美文网首页
从Jdbc到Mybatis源码剖析(整体流程)

从Jdbc到Mybatis源码剖析(整体流程)

作者: 会转圈儿的冷冷凉 | 来源:发表于2020-02-04 23:50 被阅读0次

    1.我们先看jdbc的使用

    pom文件引入mysql驱动

    <!-- 避免生成大量 setter getter方法 -->
    <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.18</version>
            </dependency>
     <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.25</version>
      </dependency>
     <!-- 为了方便和javaBean的转换 此处引入fastjson -->
      <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.62</version>
       </dependency>
    

    新建测试类JdbcMain 主要代码如下:

    public class JdbcMain {
        public static void main(String[] args) throws SQLException {
          //登录名
            String uname ="root";
            //密码
            String password ="root123";
            //数据库地址
            String url ="jdbc:mysql://xxxxx:3306/xxx_production?useUnicode=true&characterEncoding=utf8";
            //执行的sql语句
            String sql ="select * from exam_user where name =?";
            //①通过DriverManager创建Connection对象的实例
            Connection connection = DriverManager.getConnection(url,uname,password);
            //②创建Statement对象 用于执行sql语句
            PreparedStatement statement = connection.prepareStatement(sql);
            //③装备sql
            statement.setString(1,"杨智");
            //④执行查询语句
            ResultSet resultSet = statement.executeQuery();
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            int resCount = resultSetMetaData.getColumnCount();
            Map map = new HashMap();
            //⑤ 遍历结果集resultSet可以看做n条数据的结果集 next方法可以指向第n行
            while (resultSet.next())
            {
                //依次遍历各个列
                for (int i=0;i<resCount;i++)
                {
                    String columnName =resultSetMetaData.getColumnName(i+1);
                    map.put(columnName,resultSet.getString(columnName));
                }
                ExamUser examUser = JSONObject.parseObject(JSONObject.toJSONString(map),ExamUser.class);
                System.out.print(examUser.toString());
            }
            resultSet.close();
            statement.close();
            connection.close();
    }
    

    ExamUser类:

    @Data
    public class ExamUser {
        private Long id;
        private String name;
        private String password;
        private Long type;
        private String login_name;
        private Date create_time;
    }
    

    回忆大学时,把自己的代码贴给已工作的人看,当他看到了大量的原生态的jdbc代码,对我说了一句,这样会写死人的,的确,原生的jdbc现在来看有很多的缺陷,比如
    ①sql和java代码相互耦合 遇到稍微复杂的业务逻辑将会是维护的灾难
    ②没有用到连接池 大量的关闭和创建数据库连接 浪费资源
    ③结果集手动序列化 十分不便
    此时我们需要转向帮我们做了很多事的mybaits 我们再看看mybaits的使用

    2.mybatis的使用

    先上一个项目目录 然后是对项目目录的逐个代码细节

    pom文件:

          <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.6</version>
            </dependency>
    
    image.png

    以上为目录结构

    Users中的代码:
    public class Users {
      private Long id;
      private String name;
    
    
      public Long getId() {
          return id;
      }
    
      public void setId(Long id) {
          this.id = id;
      }
    
      public String getName() {
          return name;
      }
    
      public void setName(String name) {
          this.name = name;
      }
    
      @Override
      public String toString() {
          return "Users{" +
                  "id=" + id +
                  ", name='" + name + '\'' +
                  '}';
      }
    }
    
    UserMapper中的代码:
    <?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.test.dao.UserDao">
        <select id="getUserById" resultType="com.test.enity.Users">
            select * from users where id = #{id}
        </select>
    </mapper>
    
    MybatisMain中的代码:
    public class MybatisMain {
        public static void main(String[] args)
        {
             //获取配置文件test-mybatis 并将其转换为输入流
            InputStream inputStream = MainApp.class.getResourceAsStream("/mybatis/test-mybatis");
            //初始化SqlSessionFactory
            SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
            //初始化 SqlSession
            SqlSession session =sqlSessionFactory.openSession();
            //反向代理获得UserDao 反向代理的过程中将 UserDao和 UserMapper.xml中的查询方法相关联
            UserDao userDao = session.getMapper(UserDao.class);
            Users users = userDao.getUserById(17L);
            System.out.print(users.toString());
        }
    }
    
    test-mybaits中的代码:
    <?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://mysqlserver.com:3306/xxx_production?characterEncoding=UTF-8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root123"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="com/test/mapper/UserMapper.xml"/>
        </mappers>
    </configuration>
    
    大体来说 将访问数据库的方式 分成了3个部分

    1.创建访问数据库的接口UserDao
    2.创建Mapper 并关联接口

      <mapper namespace="com.test.dao.UserDao">
     </mapper>
    

    3.封装jdbc(MybatisMain里的主方法)
    4.序列化对象并返回

    3.mybatis源码分析

    这里我们思考一下,mybaits都干了什么事情
    首先 将sql语句、配置信息与java类进行分离 这样解决了jdbc的第一个问题 sql和java混在一起
    下面自然产生出了3个疑问:
    1.mybaits怎么用到的配置文件
    2.mybaits怎么将接口和mapper整合在一起 调用时仿佛自己动态创建了UserDao的实现类
    3.mybaits怎么将结果集转换为我们用到的实体类(这个问题以后再说- -)
    4.既然mybaits封装了JDBC,那么和JDBC的代码是如何对应的
    带着这三个疑问 我们往下看:
    在主方法调用中我们看不见关于数据库链接的配置信息,第一步肯定是要加载这些配置文件,这就用到了

    InputStream inputStream = MainApp.class.getResourceAsStream("/mybatis/test-mybatis");
    SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
    

    SqlSessionFactory意思是创建SqlSession的工厂 我们先看看这个工厂的结构

    public interface SqlSessionFactory {
        SqlSession openSession();
    }
    

    为了能整体把握 我们先简易观察这是个接口 找到其实现类(因为主方法中的openSession没有任何参数 所以我们只关心没有任何参数的那个实现 以下贴源码过程大都略过了大量代码 只保留一些关键信息以便我们学习)

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
        private final Configuration configuration;
    
        public DefaultSqlSessionFactory(Configuration configuration) {
            this.configuration = configuration;
        }
    
        public SqlSession openSession() {
            return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
        }
        public Configuration getConfiguration() {
            return this.configuration;
        }
    
        private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
                DefaultSqlSession var8;
                var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
                rturn var8;
        }
    }
    

    DefaultSqlSessionFactory的构造函数中需要一个Configuration 类型的实例 这里我们知道这个就能自动联想到 SqlSessionFactoryBuilder.build()方法中 肯定有创建DefaultSqlSessionFactory的地方 我们进入SqlSessionFactoryBuilder这个类找找看:

    public class SqlSessionFactoryBuilder {
        public SqlSessionFactoryBuilder() {
        }
        //第一步 我们看见的build方法在这里
        public SqlSessionFactory build(InputStream inputStream) {
            return this.build((InputStream)inputStream, (String)null, (Properties)null);
        }
       //第二步 创建Configuration 对象
        public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            SqlSessionFactory var5;
            try {
                XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                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;
        }
      //第三步 创建DefaultSqlSessionFactory实例
        public SqlSessionFactory build(Configuration config) {
            return new DefaultSqlSessionFactory(config);
        }
    }
    

    很明显 这个build方法主要干只干了一件事,攒一个Configuration出来 顾名思义 Configuration就是我们在test-mybaits.xml中配置的信息,我们目前配置的信息有 数据库驱动、用户名、密码、mysql连接和一个我们用到的UserDao接口对应的Mapper,这里我们暂且不想xml如何转换为Configuration对象(一过脑子无非就是去xml文件中匹配各种标签,并将标签对应的键值赋值到Configuration的属性中,后面在继续阅读的过程中,发现必须阅读Configuration的部分解析过程),但已经解决了我们第一个疑问:1.mybaits怎么用到的配置文件 但是这一步mybatis仅仅是在做自己的事,好像和JDBC并无关联 我们暂且把后面的3个问题继续往后放
    问题回顾:

    2.mybaits怎么将接口和mapper整合在一起 调用时仿佛自己动态创建了UserDao的实现类
    3.mybaits怎么将结果集转换为我们用到的实体类
    4.既然mybaits封装了JDBC,那么和JDBC的代码是如何对应的
    

    下面我们看

     //初始化 SqlSession
      SqlSession session =sqlSessionFactory.openSession();
    

    干了什么事情 SqlSession可以理解成一次执行操作语句的会话 这里我们盲猜是在封装JDBC的

     Connection connection = DriverManager.getConnection(url,uname,password);
    

    但是事实证明我错了 这里笔者分别故意错误的修改了数据库连接地址,JDBC的DriverManager.getConnection时 jdbc抛出了超时的异常,而openSession依然没有报错而是在userDao.getUserById(17L)抛出了超时的异常。
    进入openSession看源码 发现最后只是返回了一个DefaultSqlSession对象回来:


    image.png

    这里mybatis仍然在做自己的事情
    来到

    //反向代理获得UserDao 反向代理的过程中将 UserDao和 UserMapper.xml中的查询方法相关联
    UserDao userDao = session.getMapper(UserDao.class);
    

    先看看SqlSession是个啥东西

    public interface SqlSession extends Closeable {
        Configuration getConfiguration();
    
        <T> T getMapper(Class<T> var1);
    
        Connection getConnection();
    }
    

    同样 我筛选了只和流程代码相关的东西,getMapper方法 我们日常贴出他的实现类

    
    public class DefaultSqlSession implements SqlSession {
        private final Configuration configuration;
        private final Executor executor;
        private final boolean autoCommit;
        private boolean dirty;
        private List<Cursor<?>> cursorList;
    
        public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
            this.configuration = configuration;
            this.executor = executor;
            this.dirty = false;
            this.autoCommit = autoCommit;
        }
    
        public Configuration getConfiguration() {
            return this.configuration;
        }
    
        public <T> T getMapper(Class<T> type) {
            return this.configuration.getMapper(type, this);
        }
    }
    

    一大坨实现类的代码我们又精简了,噢,原来是通过Configuration 的getMapper方法来获取到的UserDao的实现类,这里我们贴出Configuration的代码:

    public class Configuration {
    
    protected final MapperRegistry mapperRegistry;
     public Configuration() {
      this.mapperRegistry = new MapperRegistry(this);
    }
    public <T> T getMapper(Class<T> type) {
            return this.configuration.getMapper(type, this);
        }
    
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            return this.mapperRegistry.getMapper(type, sqlSession);
        }
    }
    

    发现getMapper方法实际上是调用的MapperRegistry 的getMapper方法,我们不得不了解一下MapperRegistry 这个Mapper注册类的结构

    public class MapperRegistry {
        private final Configuration config;
        //键是接口的类型 值是代理的泛型工厂对象
        private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
    
        public MapperRegistry(Configuration config) {
            this.config = config;
        }
    
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
            if (mapperProxyFactory == null) {
                throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
            } else {
                try {
                    return mapperProxyFactory.newInstance(sqlSession);
                } catch (Exception var5) {
                    throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
                }
            }
        }
    
        public <T> boolean hasMapper(Class<T> type) {
            return this.knownMappers.containsKey(type);
        }
    
        public <T> void addMapper(Class<T> type) {
            if (type.isInterface()) {
                if (this.hasMapper(type)) {
                    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
                }
    
                boolean loadCompleted = false;
    
                try {
                    this.knownMappers.put(type, new MapperProxyFactory(type));
                    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                    parser.parse();
                    loadCompleted = true;
                } finally {
                    if (!loadCompleted) {
                        this.knownMappers.remove(type);
                    }
    
                }
            }
    
        }
    }
    

    MapperRegistry 中包三个主要功能:
    1.含一个实例名为knownMappers的HashMap 用来存储从配置文件解析到的 接口名----与代理工厂的 全局变量
    2.getMapper方法 获取某个 entry
    3.addMapper方法
    这里我们不禁想到,我们调用的是getMapper方法来获取UserDao接口的实例,但是我们是在什么时间点调用的addMapper呢?我们想一下,肯定是在mybaits加载配置文件时干的,因为我们在test-mybaits里指定过mapper文件的地址,在mapper中又指定了对应接口的命名空间
    test-mybaits.xml:

    <mappers>
            <mapper resource="com/test/mapper/UserMapper.xml"/>
    </mappers>
    

    UserMapper.xml

    <mapper namespace="com.test.dao.UserDao">
        <select id="getUserById" resultType="com.test.enity.Users">
            select * from users where id = #{id}
        </select>
    </mapper>
    

    也就是mybatis将配置文件在开始时都解析完备并且放入内存中了,一共没几行的代码跑不了

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

    这段儿了 层层深入我们会发现XMLConfigBuilder中的mapperElement方法中包含这么一段儿
    深入过程(已经眼累的同学可以跳过)

    1.SqlSessionFactoryBuilder.build()
    2.XMLConfigBuilder.parse()
       XMLConfigBuilder.parseConfiguration()
       XMLConfigBuilder.mapperElement()
    3.Configuration.addMapper()
    4.MapperRegistry.addMapper(Type type)
    {
       MapperRegistry.knownMappers.put(type, new MapperProxyFactory(type));
    }
    
    private void mapperElement(XNode parent) throws Exception {
                 .........
                 .........
                 Class<?> mapperInterface = Resources.classForName(mapperClass);
                 //addMapper就是在调用MapperRegistry里的kownsMappers
                 this.configuration.addMapper(mapperInterface);
        }
    

    找到了这个接口的类型 并调用addMapper方法将它放入了knownMappers的HashMap
    的add方法,这里和解析xml配置有关,我们先不深究。
    我们深入getMapper方法:

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
           //knownMappers的键为Class的类型,值为MapperProxyFactory(Mapper代理工厂)的一个对象
            MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
            if (mapperProxyFactory == null) {
                throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
            } else {
                try {
                    //返回对象的实例
                    return mapperProxyFactory.newInstance(sqlSession);
                } catch (Exception var5) {
                    throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
                }
            }
        }
    

    knownMappers的键为Class的类型,值为MapperProxyFactory(Mapper代理工厂)的一个对象,我们看到了Proxy字样,也就是mybaits一直提的使用动态代理的地方,knownMappers键是让我们能找想实例化的对象的类型,值是我们能把这个对象实例化所用到的方法,到我们深入一下,脱下MapperProxyFactory裤子:

    public class MapperProxyFactory<T> {
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
    
       //addMapper时 knownMappers调用的此构造函数
        public MapperProxyFactory(Class<T> mapperInterface) {
            this.mapperInterface = mapperInterface;
        }
    
        public Class<T> getMapperInterface() {
            return this.mapperInterface;
        }
    
        public Map<Method, MapperMethod> getMethodCache() {
            return this.methodCache;
        }
    
      //主要看这段儿 利用动态代理来创建了接口的实现类
        protected T newInstance(MapperProxy<T> mapperProxy) {
            return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
        }
    
        public T newInstance(SqlSession sqlSession) {
            MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            return this.newInstance(mapperProxy);
        }
    }
    

    我们知道Proxy.newProxyInstance方法的第三个参数是实现了InvocationHandler接口的代理对象(这句话如果有疑问请复习一下动态代理的过程以及目的是什么),这个泛型的MapperProxyFactory代理对象工厂正是帮我们实例化出来要生成的代理对象,接下来我们将思路从工厂类转移到代理对象类,就像牛仔裤里套了一层棉裤一样,我们再脱下MapperProxy类这条裤子:

    public class MapperProxy<T> implements InvocationHandler, Serializable {
        private static final long serialVersionUID = -6424540398559729838L;
        private final SqlSession sqlSession;
        private final Class<T> mapperInterface;
        private final Map<Method, MapperMethod> methodCache;
    
        public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
            this.sqlSession = sqlSession;
            this.mapperInterface = mapperInterface;
            this.methodCache = methodCache;
        }
        //主要方法看这里:实现了接口的invoke方法
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           //为代理的方法做了缓存
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    
        private MapperMethod cachedMapperMethod(Method method) {
            MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
            if (mapperMethod == null) {
                mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
                this.methodCache.put(method, mapperMethod);
            }
    
            return mapperMethod;
        }
    }
    

    我们主要关注的是实现InvocationHandler接口的invoke方法中
    mapperMethod.execute(this.sqlSession, args)都干了些什么东西(如何去执行我们的sql语句的)

    public class MapperMethod {
        private final MapperMethod.SqlCommand command;
        private final MapperMethod.MethodSignature method;
    
        public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
            this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
            this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
        }
    
        public Object execute(SqlSession sqlSession, Object[] args) {
            Object param;
            Object result;
            switch(this.command.getType()) {
            case INSERT:
            case UPDATE:
            case DELETE:
            case SELECT:
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
            }
                return result;
        }
    }
    
    

    这里我们看到有一个swich循环判断的是SqlCommand的type属性,目前我们的select的属性的枚举类型为SELECT,因此调用了selectOne方法,DefaultSqlSession的selectOne方法实现如下:

    public <T> T selectOne(String statement, Object parameter) {
            List<T> list = this.selectList(statement, parameter);
            if (list.size() == 1) {
                return list.get(0);
            } else if (list.size() > 1) {
                throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
            } else {
                return null;
            }
        }
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
            List var5;
            try {
                MappedStatement ms = this.configuration.getMappedStatement(statement);
                var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
            } catch (Exception var9) {
                throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
            } finally {
                ErrorContext.instance().reset();
            }
    
            return var5;
        }
    

    this.selectList方法直接返回了一个泛型集合,看来整个sql已经执行完毕了,下面这两段代码 我们需要深入追究一下:

     MappedStatement ms = this.configuration.getMappedStatement(statement);
     var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    

    我们又看到了熟悉的 this.configuration.getMappedStatement(statement);
    (上一次是this.configuration.getMapper(type, this))
    我们又需要关心一个在执行new SqlSessionFactoryBuilder().build(inputStream)时的一个HashMap
    那么statement参数是一个字符串如下图:


    image.png

    Configuration类:

    public class Configuration {
    protected final Map<String, MappedStatement> mappedStatements;
    }
    

    键为我们上面看到的方法全路径,值为一个MappedStatement对象,此对象加载对应的坐标如下(心累的同学可以跳过):

    1.SqlSessionFactoryBuilder.build()
    2.XMLConfigBuilder.parse()
       XMLConfigBuilder.parseConfiguration()
       XMLConfigBuilder.mapperElement()
    3.XMLMapperBuilder.parse()
       XMLMapperBuilder.configurationElement()
       XMLMapperBuilder.buildStatementFromContext()
    4.XMLStatementBuilder.parseStatementNode()
    5.MapperBuilderAssistant.addMappedStatement()
    6.Configuration.addMappedStatement(statement)
    
    image.png

    看到这种流水账式并且hardcode的代码忽然倍感亲切(心累结束符)

    到这里,已经把需要执行的sql语句整理完毕,放到
    this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    去执行。
    Executor是一个接口 ,里面定义了query方法它长这个样子:

    //先进入此方法
    public interface Executor {
        <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
    }
    

    BaseExecutor是Executor 的实现类,它在实现了query方法

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
            BoundSql boundSql = ms.getBoundSql(parameter);
            CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
            //调用重载方法
            return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    
        public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
                List list;
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                return list;
            }
        }
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
            List list;
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
            return list;
        }
     protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;
    

    queryFromDatabase里的doQuery属于BaseExecutor中的抽象方法,我们找到它的实现类SimpleExecutor(别问我是如何找到这个类的 心累)

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
            Statement stmt = null;
            List var9;
                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);
            return var9;
        }
    

    query方法属于StatementHandler接口,它的实现类
    SimpleStatementHandler实现方式如下:

        public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
            String sql = this.boundSql.getSql();
            statement.execute(sql);
            return this.resultSetHandler.handleResultSets(statement);
        }
    
    到这里我们终于看到了JDBC里熟悉的方法,动态代理的最终目的我们也就知道了:

    解析出标签里的各项数据库配置和SQL最终生成一个Statement对象用来执行sql语句,这里需要补充一点的是,在prepareStatement是利用JdbcTransaction初始化事务处理器中的DataSource属性根据配置文件中配置的连接池名称,灵活的初始化Connections对象也就是连接池的创建。
    具体路程如下:(这里由于对数据库连接池的创建有疑问 因此补充一下数据库连接池的创建过程)

     //初始化SqlSessionFactory
    SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
    //初始化 SqlSession
    SqlSession session =sqlSessionFactory.openSession();
    

    ① new SqlSessionFactoryBuilder().build方法完成对Configration的Environment属性赋值
    Environment对象的属性如下

    public final class Environment {
      private final String id;
      //事务工厂  JdbcTransactionFactory
      private final TransactionFactory transactionFactory;
     //PooledDataSource 连接池
      private final DataSource dataSource;
    }
    

    对应编码:

    private void environmentsElement(XNode context) throws Exception {
            if (context != null) {
                Iterator var2 = context.getChildren().iterator();
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String id = child.getStringAttribute("id");
                        //初始化事务 JdbcTransactionFactory  是根据配置文件的 <transactionManager type="JDBC"/>解析出来的
                        TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
     //初始化连接对象PooledDataSourceFactory  根据<dataSource type="POOLED">标签解析
                        DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                        DataSource dataSource = dsFactory.getDataSource();
                        Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                        this.configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    

    ②在sqlSessionFactory.openSession()中的openSessionFromDataSource方法 根据Environment对象中的事务工厂来创建事务的实例

     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
            Transaction tx = null;
    
            DefaultSqlSession var8;
            try {
                Environment environment = this.configuration.getEnvironment();
                TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
                //此处根据JdbcTransactionFactory  创建出来JdbcTransaction JdbcTransaction 中使用的是PooledDataSource
                tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
                Executor executor = this.configuration.newExecutor(tx, execType);
                var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
            } catch (Exception var12) {
                this.closeTransaction(tx);
                throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
            } finally {
                ErrorContext.instance().reset();
            }
            return var8;
        }
    

    至此我们已经解决了大部分问题:
    1.mybaits怎么用到的配置文件
    答:通过SqlSessionFactoryBuilder的build方法 利用XMLConfigBuilder的parse方法 将配置文件内容转换为
    Configuration类 这个类中有想要的一些资源
    2.mybaits怎么将接口和mapper整合在一起 调用时仿佛自己动态创建了UserDao的实现类
    答:分为两步:
    ①在使用XMLConfigBuilder解析配置文件时预加载了需要 用到的接口 并将它们放到一个map中
    ②通过动态代理的方式将接口和实现类 整合在一起
    3.mybaits怎么将结果集转换为我们用到的实体类(这个问题以后再说- -)
    4.既然mybaits封装了JDBC,那么和JDBC的代码是如何对应的
    DefaultSqlSession中 SimpleExecutor实现类的
    prepareStatement()方法里通过数据库连接池代替了 jdbc数据库驱动

     private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
            Connection connection = this.getConnection(statementLog);
            Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
            handler.parameterize(stmt);
            return stmt;
        }
    

    SimpleStatementHandler类中的query方法对应jdbc中的 statement.execute(sql);

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
            String sql = this.boundSql.getSql();
            statement.execute(sql);
            return this.resultSetHandler.handleResultSets(statement);
        }
    

    写到这里已经很累了,如果想有个自己的理解请自行打断点阅读源码
    用今日最后一丝力气想画出来一幅图来描述mybatis的几行代码做的事情


    image.png

    如果这篇文章帮助到你了,请来个小红心鼓励下哦~

    相关文章

      网友评论

          本文标题:从Jdbc到Mybatis源码剖析(整体流程)

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