Hexo

点滴积累 豁达处之

0%

Mybatis核心组件介绍及源码解读

Mybatis核心组件介绍及源码解读

Mybatis核心成员

  • Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
  • SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
  • Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
  • ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
  • ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
  • TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
  • MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装
  • SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql 表示动态生成的SQL语句以及相应的参数信息

Mybatis_code01

Configuration SqlSessionFactory SqlSession

sqlSession创建流程图

Mybatis_code02

1
2
3
4
5
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 然后根据 sqlSessionFactory 得到 session
SqlSession session = sqlSessionFactory.openSession();

首先会创建SqlSessionFactory建造者对象,从它来创建SqlSession

org.apache.ibatis.session.SqlSessionFactoryBuilder#build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.
}
}
}

parser.parse()方法会加载mybatis配置文件,初始化Configuration

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

1
2
3
4
5
6
7
8
9
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}

MapperProxy

MapperProxy创建流程图

1
2
3
4
5
6
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 然后根据 sqlSessionFactory 得到 session
SqlSession session = sqlSessionFactory.openSession();
ICategoryMapper mapper = session.getMapper(ICategoryMapper.class);

Mybatis_code03

在mybatis中,通过MapperProxy动态代理咱们的dao, 也就是说, 当咱们执行自己写的dao里面的方法的时候,其实是对应的mapperProxy在代理。

org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

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

最终MapperProxyFactory来创建代理

org.apache.ibatis.binding.MapperRegistry#getMapper

1
2
3
4
5
6
7
8
9
10
11
12
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);
}
}
}

Excutor

sql执行

1
2
3
4
5
6
7
8
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 然后根据 sqlSessionFactory 得到 session
SqlSession session = sqlSessionFactory.openSession();
ICategoryMapper mapper = session.getMapper(ICategoryMapper.class);
// 最后通过 session 的 selectList() 方法调用 sql 语句 listStudent
Category category = mapper.selectOne(1L);

Mybatis_code04

每个MapperProxy对应一个dao接口, 那么咱们在使用的时候,都会进入invoke方法

org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker#invoke

1
2
3
4
5
6
7
8
9
10
11
12
13
@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);
//二话不说,主要交给MapperMethod自己去管
return mapperMethod.execute(sqlSession, args);
}

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
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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的 selectList方法

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList

1
2
3
4
5
6
7
8
9
10
11
12
13
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}

return var6;
}

然后,通过一层一层的调用,最终会来到doQuery方法 (以SimpleExecutor为例)
org.apache.ibatis.executor.SimpleExecutor#doQuery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;

List var9;
try {
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);
} finally {
this.closeStatement(stmt);
}

return var9;
}

结果集交给ResultSetHandler去处理

org.apache.ibatis.executor.statement.PreparedStatementHandler#query

1
2
3
4
5
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}

四大核心对象

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) –执行sql

ParameterHandler (getParameterObject, setParameters) –获取、设置参数

ResultSetHandler (handleResultSets, handleOutputParameters) –处理结果集

StatementHandler (prepare, parameterize, batch, update, query) –记录sql

自定义插件示例

定义插件

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
@Intercepts(value = {@Signature(
type= Executor.class, //这里对应4个类
method = "update", //这里对应4个类里面的参数
args = {MappedStatement.class,Object.class})}) //这里的参数类型,是对应4个类中的各种方法的参数。如果方法没有参数,这里直接写{}就可以了
public class DrafirePlugin implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("mybatis插件打印了乐乐");
Category obj = (Category)invocation.getArgs()[1];
MappedStatement streDemo = (MappedStatement)invocation.getArgs()[0];
SqlSource sqlSource = streDemo.getSqlSource();
BoundSql boundSql = sqlSource.getBoundSql(streDemo.getParameterMap());
String sql = boundSql.getSql();
obj.setCat_name("家电7");
return invocation.proceed();
}

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

@Override
public void setProperties(Properties properties) {

}
}

配置插件

1
2
3
<plugins>
<plugin interceptor="com.song.plugin.DrafirePlugin"> </plugin>
</plugins>

动态sql

相关接口和类

SqlNode

简单理解就是xml中的每个标签,比如上述sql的update,trim,if标签:

Mybatis_sql01

1
2
3
public interface SqlNode {
boolean apply(DynamicContext context);
}
SqlSource

Sql源接口,代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等:

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode

1
2
3
4
5
6
7
8
9
10
11
12
13
public SqlSource parseScriptNode() {
//解析动态标签
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
//创建DynamicSqlSource并返回
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//创建RawSqlSource并返回
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
1
2
3
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
BoundSql

BoundSql类,封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数:

查询源码链示例

1
2
3
4
5
6
7
8
9
10
11
12
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory.openSession(true);
SqlSession session = sqlSessionFactory.openSession();
ICategoryMapper mapper = session.getMapper(ICategoryMapper.class);
Category cagte = new Category();
cagte.setCat_id(2L);
cagte.setCat_name("家电5");
Category category = mapper.selectOne(cagte);
session.clearCache();
session.close();
MapperProxy#invoke

invoke 代理方法

org.apache.ibatis.binding.MapperProxy#invoke

1
2
3
4
5
6
7
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
MapperMethod#execute

execute 选择具体的CRUD方法

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
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}

}
DefaultSqlSession#selectList

selectList 查询单个也交给列表方法执行

org.apache.ibatis.session.defaults.DefaultSqlSession#selectList

1
2
3
4
5
6
7
8
9
10
11
12
13
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}

return var6;
}
CachingExecutor#query

query方法 生成BoundSql 和二级缓存key 然后交由下游执行

org.apache.ibatis.executor.CachingExecutor#query

1
2
3
4
5
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
CachingExecutor#query

query 判断是否走一二级缓存还是从数据查

org.apache.ibatis.executor.CachingExecutor#query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}

return list;
}
}

return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
SimpleExecutor#doQuery

doQuery 具体执行执行查询操作, 先获取StatementHandler 生成Statement 再查询

org.apache.ibatis.executor.SimpleExecutor#doQuery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;

List var9;
try {
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);
} finally {
this.closeStatement(stmt);
}

return var9;
}
DefaultParameterHandler#setParameters

setParameters 生成具体的Statement 过程中 填充下参数(即填充sql问号的值)

org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

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
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (this.boundSql.hasAdditionalParameter(propertyName)) {
value = this.boundSql.getAdditionalParameter(propertyName);
} else if (this.parameterObject == null) {
value = null;
} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
value = this.parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
value = metaObject.getValue(propertyName);
}

TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = this.configuration.getJdbcTypeForNull();
}

try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (SQLException | TypeException var10) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
}
}
}
}

}