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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容