Mybatis源码分析

一、 环境搭建

1. mysql的环境

1.1 mysql安装地址

Linux环境 :

可使用yum或者docker下载mysql

Windows环境:

可上mysql的官网下载,这里推荐清华tuna镜像网,下载速度更快。

1.2 建表

这里我们需要建立一张测试表用于mybatis的debug

建表语句如下:

CREATE TABLE `user`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `age` int(0) NOT NULL,
  `create_time` datetime(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

这里创建了一张表user,字段有id、name、age和create_time

二、 Mybatis介绍

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录,大大简化了开发效率。

研究Mybatis源码可以有如下好处:

  • 了解Mybatis的运行流程,当Mybatis出现异常的时候能够更好定位bug
  • Mybatis出现性能瓶颈时,能够对Mybatis进行性能调优,对mybatis进一步封装等
  • 查询执行的SQL语句,这尤其对于使用了tk.mybatis的项目,能够清楚执行的SQL语句,是否符合预期等

三、 原生JDBC语句

我们先来看看,在没有使用工具(JDBCTemplate等)和框架 (Mybatis等)的情况下,我们使用JDBC查询表user中的所有数据是如何操作的

import java.sql.*;
public class Main {
    // JDBC 信息
    static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8";
    static final String USER = "root";
    static final String PASS = "123456";

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try{
            //STEP 2: 注册JDBC Driver
            Class.forName(JDBC_DRIVER);

            //STEP 3: 建立连接
            conn = DriverManager.getConnection(DB_URL,USER,PASS);

            //STEP 4: 执行查询
            stmt = conn.createStatement();
            String sql;
            sql = "SELECT * FROM user";
            ResultSet rs = stmt.executeQuery(sql);

            //STEP 5: 封装结果集
            while(rs.next()){
                String id  = rs.getString("id");
                String name = rs.getString("age");
                Integer age = rs.getInt("age");
                Date createTime = rs.getDate("create_time");
                User user = new User(id, name, age, createTime);

                System.out.println(user);
                System.out.println("---------------");
            }
            //STEP 6: 关闭资源
            rs.close();
            stmt.close();
            conn.close();
        }catch(SQLException e){
            e.printStackTrace();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            try{
                if(stmt!=null)
                    stmt.close();
            } catch (SQLException se2){

            }
            try{
                if(conn!=null)
                    conn.close();
            } catch (SQLException e){
                e.printStackTrace();
            }
        }
        System.out.println("Goodbye!");
    }
}

使用JDBC语句查询查询数据总结为如下步骤

  1. 注册JDBC Driver
  2. 建立连接,获取Connection对象
  3. 执行查询语句
  4. 将mysql结果集封装到Java对象中
  5. 关闭资源

我们发现,上述操作中,不同的DML语句上述的步骤是类似的,获取连接,执行语句(仅仅是SQL语句不同),封装结果集(封装到的对象不同),关闭资源。因此,我们可以将这些通用的操作封装到一个工具或者框架中,仅仅只需要提供执行的SQL语句,封装到的结果集。

而JDBCTemplate、Mybatis等框架,就是提供了这些功能的工具和框架。

四、 Mybatis源码的核心类

要深入理解Mybatis的流程和源码,我们首先介绍Mybatis的核心类以及它的功能,读者仅仅需要记住这些核心类以及他们的功能,后面会深入他们的源码。

1. SqlSessionFactory

SqlSession的工厂类,用于生产SqlSession,可以通过Configuration getConfiguration();获取Configuration对象。

DefaultSqlSessionFactory为SqlSessionFactory的实现类

2. SqlSession

Mybatis的关键类,通过SqlSession类执行语句,获取Mapper类等。

如查询有selectList、selectMap方法, 更新有update方法等

DefaultSqlSession和SqlSessionTemplate为SqlSession的实现类,DefaultSqlSession中有个重要的属性Executor executor。

3. Executor

执行器,真正获取连接,解析SQL语句,真正干活的类。前面的SqlSession正是组合Executor类来完成功能。

其中,CachingExecutor是一级缓存的实现,后面我们会介绍到。

4. Configuration

配置文件类,我们在编写Mybatis时需要使用一个全局的mybatis配置文件,以及多个XxxMapper.xml文件。而Java是一门面向对象的语言,因此使用了Configuration类将我们mybatis的配置文件信息封装起来。

5. MapperProxyFactory

听类名就猜到了该类的作用,MapperProxyFactory是一个用来创建MapperProxy的工厂类。

6. MapperProxy

MapperProxy,同样该类名的字面意思就是Mapper的代理类。我们在Mybatis框架中,通常使用的是Mapper的接口,而不需要创建真正的实体类。正是由MapperProxy代理类帮我们完成真正的逻辑。

7. 总结

接下来,我们用一张图总结上面的类关系图(非标准的UML类图)

  1. 由MapperProxyFactory创建UserMapper的代理类MapperProxy
  2. SqlSessionFactory创建了SqlSession
  3. SqlSession聚合了执行器Executor(Executor是真正干活的类)
  4. SqlSession和SqlSessionFactory都聚合了Configuration类(Mybatis的xml配置文件)
  5. MapperProxy聚合了SqlSession

五、 Mybatis的简单Demo

我们首先通过简单的Demo,看看如何使用Mybatis。使用到的数据库表为上述提到的User表。

以下Demo的代码链接,大家只需要安装好MySQL和修改对应的数据库配置信息,即可正确运行。

1. 创建数据库表对应的实体User

package com.junehua.pojo;

import java.util.Date;

public class User {

    private String id;

    private String name;

    private Integer age;

    private Date createTime;

    public User() {
    }

    public User(String id, String name, Integer age, Date createTime) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.createTime = createTime;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("User{");
        sb.append("id='").append(id).append('\'');
        sb.append(", name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append(", createTime=").append(createTime);
        sb.append('}');
        return sb.toString();
    }
}

2. 创建User对象对应的Mapper接口

package com.junehua.mapper;

import com.junehua.pojo.User;

import java.util.List;

public interface UserMapper {

    int insert(User user);

    List<User> listAll();
}

3. Mybatis配置文件

首先,我们在resources目录下创建一个mybatisConfig.xml,mybatisConfig.xml的配置文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!--mybatis全局配置文件-->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--<properties resource="db.properties"/>-->
    <settings>
        <!--开启驼峰命名规则自动转换-->
        <setting name="mapUnderscoreToCamelCase" value="true" />
        <!--是否开启二级缓存-->
        <setting name="cacheEnabled" value="false"/>
    </settings>
    <typeAliases>
        <!--数据库表对应的Entity类所在包-->
        <package name="com.junehua.pojo"></package>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <!--数据源信息-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/demo?useSSL=false&amp;serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--mapper文件所在的目录-->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>

</configuration>

4. 编写Mapper的映射文件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="com.junehua.mapper.UserMapper">

    <select id="listAll" resultType="com.junehua.pojo.User" >
        select * from user;
    </select>

    <insert id="insert" parameterType="com.junehua.pojo.User">
        insert into user(id, name, age, create_time) value(#{id}, #{name}, #{age}, now())
    </insert>

</mapper>

5. 编写并运行程序

public class MybatisDemo {

    @Test
    public void mybatisDemoTest() throws IOException {
        // Mybatis全局配置文件
        InputStream mybatisConfig = Resources.getResourceAsStream("mybatisConfig.xml");
        // 创建SqlSession工厂类
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mybatisConfig);
        // 由工厂类创建SqlSession类
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取Mapper文件
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 查询并打印数据
        List<User> users = userMapper.listAll();
        users.forEach(System.out::println);
    }
}

使用SqlSessionFactory工厂类创建SqlSession,由SqlSession获取UserMapper接口,注意,UserMapper是个接口,该接口的实现类是由MapperProxyFactory创建的MapperProxy代理类。

6. 运行结果

我们使用Navicat查看数据库表中的数据信息,如下:

共有两条数据,查看IDEA控制台输出的信息如下:

成功打印数据。

六、 Mybatis源码

通过以上Mybatis的Demo,我们对Mybatis有了一个基本的理解,接下来,我们将根据上面给出的Demo,研究Mybatis的实现和核心源码。

public class MybatisDemo {

    @Test
    public void mybatisDemoTest() throws IOException {
        // Mybatis全局配置文件
        InputStream mybatisConfig = Resources.getResourceAsStream("mybatisConfig.xml");
        // 创建SqlSession工厂类
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mybatisConfig);
        // 由工厂类创建SqlSession类
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取Mapper文件
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 查询并打印数据
        List<User> users = userMapper.listAll();
        users.forEach(System.out::println);
    }
}

1. SqlSessionFactory和Configuration

首先,我们看到通过new SqlSessionFactoryBuilder().build(mybatisConfig);创建了一个SqlSessionFactory

这里传入了我们的Mybatis配置文件,所以我们不难猜测这里会将mybatisConfiguration.xml解析为Configuration类。

我们进入 SqlSessionFactoryBuilder.build(),该方法返回SqlSessionFactory对象

// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

// 我们接着进入build方法
// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

这里的 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties)返回一个 XMLConfigBuilder parser ,我们进入XMLConfigBuilder 的parse()方法

// XMLConfigBuilder 
public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

果然,这里返回了一个Configuration,我们进入Configuration类如下:

public class Configuration {

    protected Environment environment;

    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled = true;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled = true;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel = true;
    protected boolean cacheEnabled = true;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName = true;
    protected boolean returnInstanceForEmptyRow;

    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
        .conflictMessageProducer((savedValue, targetValue) ->
                                 ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    
    ....
}

这里仅仅截取了部分的代码,我们观察上面的protected boolean mapUnderscoreToCamelCase;

这不正是Mybatis全局配置文件中是否开启下划线转驼峰命名的配置吗。其他的属性也是如此,将全局配置文件、Mapper的xml映射到Configuration类中。

<settings>
    <!--开启驼峰命名规则自动转换-->
    <setting name="mapUnderscoreToCamelCase" value="true" />
</settings>

上面的Configuration类中还有个关键的属性Map<String, MappedStatement> mappedStatements,这个mappedStatements就是我们将XxxMapper.xml转化后存储的结果。

其中,

key:全类名方法名,如com.junehua.mapper.UserMapper.listAll

value:为一个MappedStatement对象,该对象保存了该方法的相关信息,如参数类型、resultSet类型等。

我们回到SqlSessionFactoryBuilder类中的build方法代码

// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

进入build(parser.parse())

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

就是返回了一个SqlSessionFactory的实现类DefaultSqlSessionFactory。

我们接下来进入SqlSessionFactory源码如下:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

可以看到SqlSessionFactory的作用是获取Configuration、创建SqlSession的工厂。

好的,第一部分的SqlSessionFactory和Configuration的源码我们分析清楚了,接下来我们总结一下:

我们的全局配置文件和Mapper的xml会存储在Configuration类中,SqlSessionFactory中保存了一份Configuration对象,如下图:

2. SqlSessionFactory.openSession();

我们进入到SqlSession sqlSession = sqlSessionFactory.openSession();

// DefaultSqlSessionFactory
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

继续进入到 openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);

// DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 返回一个Executor执行器
        final Executor executor = configuration.newExecutor(tx, execType);
        // 创建一个DefaultSqlSession
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

这里我们关键看configuration.newExecutor(tx, execType);这里会返回一个Executor对象。我们重新查看Executor的继承图。

这里我们进入到Configuration的newExecutor方法中

// Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

这里我们传入的executorType是ExecutorType.SIMPLE,因此会创建一个SimpleExecutor。我们看关键的一行代码

if (cacheEnabled) {
    executor = new CachingExecutor(executor);
}

这里就是Mybatis的一级缓存,如果cacheEnabled是true(Mybatis的全局配置文件mybatisConfig.xml中配置),那么就创建一个CachingExecutor,并把刚刚创建的Executor作为CachingExecutor的属性。

我们回到如下代码:

// DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 返回一个Executor执行器
        final Executor executor = configuration.newExecutor(tx, execType);
        // 创建一个DefaultSqlSession
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

这里会创建一个DefaultSqlSession,并把刚刚创建出来的executor传入。

接下来,我们总结一下SqlSessionFactory.openSession()的作用

  1. 创建一个Executor,这里我们创建了一个SimpleExecutor。如果Mybatis开启了一级缓存cacheEnabled(默认开启),那么创建一个CachingExecutor并且将SimpleExecutor传入。
  2. 创建一个DefaultSqlSession并返回

3. SqlSession.getMapper(UserMapper.class);

我们进入到sqlSession.getMapper(UserMapper.class); 这里的SqlSession的实现类是DefaultSqlSession

// DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

继续进入到Configuration的getMapper

// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

进入到如下代码:

// MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取MapperProxyFactory工厂
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 创建一个UserMapper代理类
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

在这里,我们看到了我们熟悉的MapperProxyFactory。对,MapperProxyFactory正是我们用于创建MapperProxyFactory的工厂类,果然,在下面出现了return mapperProxyFactory.newInstance(sqlSession);

我们进入到代码

// MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
    // 创建MapperProxy
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}


protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

这正是我们使用JDK动态代理的方法Proxy.newProxyInstance(类加载器, 代理接口, 拦截方式)。后面我们会看MapperProxy如何进行拦截。至此,我们通过UserMapper userMapper = sqlSession.getMapper(UserMapper.class);拿到了UserMapper的代理类,接下来,我们将进入到UserMapper的MapperProxy代理类,看看该代理类如何完成代理操作。

4. MapperProxy

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;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            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);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
        throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
            .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor
            .newInstance(declaringClass,
                         MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                         | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
            .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

    /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
            && method.getDeclaringClass().isInterface();
    }
}

这里,我们看到熟悉的InvocationHandler。对,这就是我们动态代理的拦截类,我们重点看InvocationHandler的抽象方法invoke。

// MapperProxy
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 当前方法是Object方法,则直接执行方法,如toString、equal等
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) { // 是否是默认方法 default修饰的方法
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 我们的demo将会进入到此处
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}

我们看到MapperMethod类,看类名大概的意思是Mapper的方法,这里是我们UserMapper的listAll()对应的额MapperMethod,代码如下:

public class MapperMethod {

    // sql的信息,对应那个方法。 sql的类型,select、update等
    private final SqlCommand command;
    // 方法描述,如方法的参数、参数类型、返回结果
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } 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;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
            throw new BindingException("Mapper method '" + command.getName()
                                       + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
    }
    
    ....
}

MapperMethod中有两个属性,分别是SqlCommand和 MethodSignature

public static class SqlCommand {

    // 这里的name 是我们的方法名 com.junehua.mapper.UserMapper.listAll
    private final String name;
    // 这里的type是我们方法的类型,例如INSERT、UPDATE等
    // 我们这里的listAll用的是SELECT
    private final SqlCommandType type;
    
    ....
}

// 我们方法的相关信息,例如方法的返回值、 参数名映射规则等
public static class MethodSignature {

    private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final boolean returnsCursor;
    private final boolean returnsOptional;
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;
    
    ....
}

了解了SqlCommand和MethodSignature,我们回到MapperMethod的execute方法

// MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
        case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
        }
        case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
        }
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                // demo将进入到此处
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } 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;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
                                   + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

这里,我们使用的是SELECT类型,并且返回的是List<User>。代码会进入到result = executeForMany(sqlSession, args),我们继续进入到executeForMany

// MethodMapper
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 转化参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
        result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
            return convertToArray(result);
        } else {
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
    }
    return result;
}

这里,总算来到了我们的SqlSession,执行了selectList方法,接下来,我们将分析SqlSession的源码。

5. SqlSession

我们进入SqlSession的实现类DefaultSqlSession。

public class DefaultSqlSession implements SqlSession {

    // 前面介绍的Configuration,存储mybatis的xml文件
    private final Configuration configuration;
    // 执行器,这里我们用到的实现类是SimpleExecutor,若开启一级缓存,这里使用的是CachingExecutor
    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;
    }

这里再次说明,SqlSession并不存在Connection、Statement处理等,真正执行JDBC连接、查询、封装结果的并不是SqlSession,而是SqlSession的Executor。

接下来,我们进入到DefaultSqlSession的sqlSession.selectList(command.getName(), param);

// DefaultSqlSession
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 还记得这里的statement值吗?
        // 获取Map中key:com.junehua.mapper.UserMapper.listAll的MappedStatement
        MappedStatement ms = configuration.getMappedStatement(statement);
        
        // 真正干活的executor
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

DefaultSqlSession真正执行查询的是Executor执行器,接下来,我们将分析SimpleExecutor源码。

6. SimpleExecutor

首先,我们复习一下Executor执行的继承关系图

我们使用到的是SimpleExecutor,我们进入到executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

// BaseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 这里获取到了一个BoundSql对象,BoundSql对象里面有个String sql属性,该sql属性就是我们在数据库中执行的SQL语句
    // 这里的BoundSql的sql值为“select * from user;”,即查询表user中的所有数据
    // 如果我们在程序中,需要知道执行的sql语句,可以打断点来到此处查看sql语句
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

// BaseExecutor
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            // 我们看queryFromDatabase,从数据库中查询,我们进入到对应的代码块
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}


private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 执行doQuery
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}

接下来,我们进入到SimpleExecutor的doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

// SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    // 这里看到了我们JDBC中熟悉的Statement
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();
        // 获取到一个StatementHandler的实现类RountingStatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 执行RoutingStatementHandler的query(stmt, resultHandler)
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

// Configuration 
// 此方法将会返回一个RoutingStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // RoutingStatementHandler会根据StatementType的类型,聚合一个StatementHandler对象
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 如果Mybatis有插件,则添加到此处
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

// 进入到prepareStatement(handler, ms.getStatementLog());
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 这里看到了我们JDBC中熟悉的Connection
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
}

我们进入到RountingStatementHandler的query(stmt, resultHandler);

// 进入到handler.query(stmt, resultHandler);
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 这里的delegate是PreparedStatementHandler
    return delegate.query(statement, resultHandler);
}

// delegate.query(statement, resultHandler);
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 这里看到了我们JDBC中熟悉的PreparedStatment
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行我们的sql语句
    ps.execute();
    // 使用DefaultResultSetHandler完成我们的结果集处理
    // 将查询出来的数据封装到List<User>中
    return resultSetHandler.handleResultSets(ps);
}

总结一下SimpleExecutor的执行流程:

  1. 获取执行的SQL语句:BoundSql boundSql
  2. 根据Statement的类型StatementType创建RoutingStatementHandler
  3. 执行SimpleExecutor的doQuery方法()
  4. 使用StatementHandler的query进行查询
  5. 使用ResultSetHandler对结果集进行封装

至此,我们对Mybatis的核心源码有了一定的了解,大家可以通过UserMapper中的insert(User user)方法,进行debug,走一遍Mybatis的源码实现。

如果本文有什么错误的地方,欢迎留言私信我。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值