MyBatis 源码分析

一、HelloWorld Demo

一个最简单的MyBatis Demo

1、 代码

源码下载:mybatis-demo
对应DB表:mybatis-demo

public class UserService {
    public User getUserById(Integer userId) {
        SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            return userMapper.getUserById(userId);
        } finally {
            sqlSession.close();
        }
    }
}
public class MyBatisUtil
{
    private static SqlSessionFactory factory;

    private MyBatisUtil() {
    }

    static
    {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);
    }

    public static SqlSessionFactory getSqlSessionFactory()
    {
        return factory;
    }
}
  • mybatis-config.xml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE configuration
        PUBLIC '-//mybatis.org//DTD Config 3.0//EN'
        'http://mybatis.org/dtd/mybatis-3-config.dtd'>
<configuration>
    <properties resource='jdbc.properties'/>
    <typeAliases>
        <typeAlias type='com.sivalabs.mybatisdemo.domain.User' alias='user'></typeAlias>
    </typeAliases>
    <environments default='development'>
        <environment id='development'>
            <transactionManager type='JDBC'/>
            <dataSource type='POOLED'>
                <property name='driver' value='${jdbc.driverClassName}'/>
                <property name='url' value='${jdbc.url}'/>
                <property name='username' value='${jdbc.username}'/>
                <property name='password' value='${jdbc.password}'/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource='com/sivalabs/mybatisdemo/mappers/UserMapper.xml'/>
    </mappers>
</configuration>
  • 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.sivalabs.mybatisdemo.mappers.UserMapper'>

    <select id='getUserById' parameterType='int' resultType='com.sivalabs.mybatisdemo.domain.User'>
        SELECT
        user_id as userId,
        email_id as emailId ,
        password,
        first_name as firstName,
        last_name as lastName
        FROM USER
        WHERE USER_ID = #{userId}
    </select>
    <!-- Instead of referencing Fully Qualified Class Names we can register Aliases in mybatis-config.xml and use Alias names. -->
    <resultMap type='com.sivalabs.mybatisdemo.domain.User' id='UserResult'>
        <id property='userId' column='user_id'/>
        <result property='emailId' column='email_id'/>
        <result property='password' column='password'/>
        <result property='firstName' column='first_name'/>
        <result property='lastName' column='last_name'/>
    </resultMap>

    <select id='getAllUsers' resultMap='UserResult'>
        SELECT * FROM USER
    </select>

2、代码逻辑

  • 在执行userMapper.getUserById(userId)之前,会先执行2.1和2.1代码
  • 2.3是userMapper.getUserById(userId)真正执行的代码

2.1 解析配置文件(#hello-world-demo-2-1)

        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        }
        factory = new SqlSessionFactoryBuilder().build(reader);

该段代码主要把mybatis-config.xmlUserMapper.xml中的各种配置解析到Configuration实例中去。

2.2 构造UserMapper实例

SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  • 通过openSession()来进行实例的初始化,此处可暂时略过。
  • 通过getMapper()方法来获取Mapper实例。

此处getMapper()方法的内部实现是为UserMapper生成一个动态代理,并且返回:

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

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

此处的意思为:
MapperProxy这个类的invoke()方法来实现对应的函数调用流程。

2.3 执行对应查询代码

            return userMapper.getUserById(userId);

因为这里的userMapper对象是动态代理对象,所以其实这里执行的是MapperProxy.invoke()方法。
MapperProxy.invoke()方法内容如下:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

这里生成了一个MapperMethod 对象,并且调用mapperMethod.execute(sqlSession, args)来进行真正的DB查询逻辑。

mapperMethod.execute(sqlSession, args)的内部逻辑如下所示

2.3.1 找到接口对应的sql

DefaultSqlSession#selectList()方法内容如下:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

根据接口找到对应的sql,然后交由executor执行

2.3.2 执行sql,封装结果

缺省情况下executor是SimpleExecutorSimpleExecutor.doQuery()方法内容如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 执行sql,获得结果
  2. 把结果转换成接口中声明的类型然后返回

二、Plugin Demo

演示 MyBatis Plugin 功能的 Demo。

1、代码

源码下载:mybatis-demo
对应DB表:mybatis-demo

添加Interceptor类:

@Intercepts({@Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class})})
public class SQLStatsInterceptor implements Interceptor {

    public Object intercept(Invocation invocation) throws Throwable {

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        System.out.println("mybatis intercept sql:" + sql);
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        String dialect = properties.getProperty("dialect");
        System.out.println("mybatis intercept dialect:" + dialect);
    }
}

添加interceptor配置:

mybatis-config.xml 添加以下配置,

    <plugins>
        <plugin interceptor="com.sivalabs.mybatisdemo.interceptor.SQLStatsInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>

2、代码逻辑

2.1 解析配置文件

同上。2.1 解析配置文件

2.2 构造UserMapper实例

SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
2.2.1、通过openSession()来进行实例的初始化。

openSession()的内部内部实现如下:

  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);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

主要业务逻辑为

  1. 开启一个事务
  2. 构造一个newExecutor

configuration.newExecutor()的内部实现如下:

    executor = (Executor) interceptorChain.pluginAll(executor);

把所有的插件(Interceptor)嵌入到Executor中,具体实现如下:

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
  public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

细节已经展现,MyBatis 内部通过 Java 的动态代理来实现 Plugin(Interceptor)的嵌入。

2.2.2、通过getMapper()方法来获取Mapper实例。

同上。

2.3 执行对应查询代码

同上。

2.3.1 找到接口对应的sql

同上。

2.3.2 执行sql,封装结果

缺省情况下executor是SimpleExecutorSimpleExecutor.doQuery()方法内容如下:

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  1. 执行sql,获得结果
  2. 把结果转换成接口中声明的类型然后返回

其中1.调用了configuration.newStatementHandler()来构造一个 statement,其内部实现如下:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

我们看到了interceptorChain.pluginAll(statementHandler),这里的逻辑便和2.2.2一致了,用 Java 的动态代理,把Interceptor的代码嵌入到Handler实例中。

这样一来,如 SQLStatsInterceptor 的注解中声明的那样,在调用 StatementHandler.parameterize时,便会回调Interceptor的代码逻辑了。