TABLE OF CONTENTS

  1. Mybatis包结构
  2. Mybatis使用流程
  3. 一些关键逻辑和亮点
    1. 通过SqlSessionFactory工厂获取SqlSession
    2. 加载Mybatis配置文件
      1. 配置项的默认值
    3. SqlSession接口
    4. 获取Mapper代理类
    5. 执行Mapper代理方法
  4. SqlSession, SqlSessionFactory, SqlSessionFactoryBuilder的关系
    1. SqlSession的使用
    2. SqlSession的执行原理
  5. Excutor
  6. 总结

Mybatis包结构

Mybatis使用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Slf4j
public class MyBatisBootStrap {

@Test
public void test() {
try {
// 1. 读取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取Mapper
CategoryMapper categoryMapper = sqlSession.getMapper(CategoryMapper.class);
// 5. 执行接口方法
Category category = categoryMapper.queryCategoryByCategoryId(1);
log.info("category = " + JsonUtil.toJson(category));
// 6. 提交事物
sqlSession.commit();
// 7. 关闭资源
sqlSession.close();
inputStream.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

如果对于Mybatis启动不清晰,可以通过上面这段代码找到入口,阅读源码。

一些关键逻辑和亮点

通过SqlSessionFactory工厂获取SqlSession

工厂方法模式+建造者设计模式的应用

SqlSessionFactory通过工厂方法openSession创建SqlSession实例,具体的工厂类实现了工厂接口,负责创建特定类型的对象,使SqlSession的实例化延迟到SqlSessionFactory子类。客户端通过SqlSessionFactory接口和工厂子类来获取SqlSession,而不需要知道具体的对象创建过程。而工厂本身是一个复杂的对象,因此可以通过建造者模式SqlSessionFactoryBuilder来构建。

源码位置:
org.apache.ibatis.session.SqlSessionFactory

两个子类:
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
org.apache.ibatis.session.SqlSessionManager

建造者类SqlSessionFactoryBuilder
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)

加载Mybatis配置文件

1
2
// 2. 创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

上面这段代码,XMLConfigBuilder会解析inputStream,根据得到Configuration默认实例化一个工厂子类DefaultSqlSessionFactory。

源码位置:
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

配置项的默认值

org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement

SqlSession接口

sqlSession是操作数据库的高级接口,获取sqlSession有两种方式:
一种是从数据源中获取的,还有一种是从连接中获取。获取到的都是DefaultSqlSession对象,也就是sqlSession的默认实现。

SqlSession接口源码,下面是其主要方法,已过滤一些重载方法。
org.apache.ibatis.session.SqlSession

  • statement:Mybatis语句ID,Mapper接口的方法名称
  • RowBounds:分页对象,主要拼接sql中的start,limit条件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface SqlSession extends Closeable {

<T> T selectOne(String statement, Object parameter);

<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

int insert(String statement, Object parameter);

int update(String statement, Object parameter);

int delete(String statement, Object parameter);

void commit(boolean force);

void rollback(boolean force);

List<BatchResult> flushStatements();

@Override
void close();

void clearCache();

Configuration getConfiguration();

<T> T getMapper(Class<T> type);

Connection getConnection();
}

获取sqlSession源码位置:
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 方式1:从数据源中获取的
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();
}
}

// 方式2:从连接中获取
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

获取Mapper代理类

1
2
// 4. 获取Mapper
CategoryMapper categoryMapper = sqlSession.getMapper(CategoryMapper.class);

因为默认的SqlSession实现是DefaultSqlSession,进入源码可以得知,CategoryMapper是通过代理工厂MapperProxyFactory反射得到Mapper代理类。这段代码逻辑可以通过顺藤摸瓜,找到Mapper代理是通过Map<Class<?>, MapperProxyFactory<?>> knownMappers获取的。

源码入口:
org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

1
2
3
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}

org.apache.ibatis.session.Configuration#getMapper

1
2
3
4
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}

org.apache.ibatis.binding.MapperRegistry#getMapper找到这段代码可以得知:
1、代理工厂来自knownMappers
2、mapperProxyFactory.newInstance(sqlSession),Mapper每次都通过Mapper代理工厂生成,是一个代理类。

1
2
3
4
5
6
7
8
9
10
11
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

knownMappers是如何初始化?

org.apache.ibatis.binding.MapperRegistry#addMapper ==> org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

可以发现是在XMLConfigBuilder解析mybatis-config.xml时通过parseConfiguration方法进行的。仔细观察下图,还提供了一个Spring下的FacotryBean实现进行配置。

执行Mapper代理方法

从上面分析得知,获取得到的Mapper是通过MapperProxyFactory工厂类创建的MapperProxy代理类。因此,在执行Mapper接口的方法时,会调用MapperProxy的invoke方法。
通过Debug也可以得到Mapper的执行逻辑。

源码入口:
org.apache.ibatis.binding.MapperProxy#invoke

cachedInvoker(method)返回MapperMethodInvoker对象,执行invoke方法(具体通过哪个实现可以实际debug,这里是MapperMethodInvoker的子类PlainMethodInvoker的invoke方法)
org.apache.ibatis.binding.MapperMethod#execute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

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

SqlSession, SqlSessionFactory, SqlSessionFactoryBuilder的关系

SqlSession的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void testSelectOne() {
try {
// 1. 读取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 执行sql,下面2种写法结果一致
Category category = sqlSession.selectOne("moe.tsukasa.demo.dao.CategoryMapper.queryCategoryByCategoryId", 1);
log.info("category = [{}]", JsonUtil.toJson(category));
Category category2 = sqlSession.selectOne("queryCategoryByCategoryId", 1);
log.info("category2 = [{}]", JsonUtil.toJson(category2));
// 5. 关闭连接
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error("errMsg = [{}]", e.getMessage(), e);
}
}

SqlSession的执行原理

Excutor

Excutor是MyBatis持久层框架中的一个关键组件,负责SQL语句的执行,缓存的处理,事务管理,参数处理。

Executor接口有两个实现,一个是BaseExecutor抽象类,一个是CachingExecutor实现类。BaseExecutor抽象类有四个实现SimpleExecutor,BathExecutor, ReuseExecutor, ClosedExecutor。

Executor继承关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface Executor {

ResultHandler NO_RESULT_HANDLER = null;

int update(MappedStatement ms, Object parameter) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

List<BatchResult> flushStatements() throws SQLException;

void commit(boolean required) throws SQLException;

void rollback(boolean required) throws SQLException;

CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

boolean isCached(MappedStatement ms, CacheKey key);

void clearLocalCache();

void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

Transaction getTransaction();

void close(boolean forceRollback);

boolean isClosed();

void setExecutorWrapper(Executor executor);

}

Excutor是模板方法设计模式的应用,Executor接口有个抽象子类BaseExecutor,定义了一些模板方法,通过子类来实现。

SimpleExecutor是默认的Executor实现,适用于大多数情况,但不支持高级功能如二级缓存。我们只需要定义好myabtis-config.xml,MyBatis框架会负责调用适当的Executor来执行数据库操作。

总结

声明:本站所有文章均为原创或翻译,遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名 (Tsukasa) 及原文地址