Mybati执行对象创建过程与执行

概要:

  1. 执行流程解析
  2. myBatis插件开发

执行流程分析

Configuration全局配置对象创建

讲解解析流程之前先回顾一下myBatis 中配置文件的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mybatis-config.xml
<configuration>
<properties/>
<settting/>
<typeHandlers/>
<..../>
<mappers/>
</configuration>
mybatis-mapper.xml
<mapper >
<cache/>
<resultMap/>
<select/>
<update/>
<delete/>
<insert/>
</mapper>

配置文件的解析流程即是将上述XML描述元素转换成对应的JAVA对像过程,其最终转换对像及其关系如下图:

配置元素解析构建器

1
2
3
4
5
6
7
8
9
10
11
12
13
>org.apache.ibatis.builder.xml.XMLConfigBuilder
//生成org.apache.ibatis.session.Configuration对象 生命周期:整个应用周期
>org.apache.ibatis.builder.xml.XMLMapperBuilder
//生成生成configuration对象的MappedStatement key为namcspace + 语语句块的id
//生命周期:整个应用周期
>org.apache.ibatis.builder.xml.XMLStatementBuilder
//Statement 生命周期:整个应用周期
>org.apache.ibatis.builder.SqlSourceBuilder
//增删改查语句内容构建
>org.apache.ibatis.scripting.xmltags.XMLScriptBuilder
//动态SQL构建
>org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
//用注解方式构建起如@Select(...)

SqlSession 会话对象创建

首先我们还是先来了解一下会话对像的组成结构如下图:

image-20210516131743828

会话构建源码解析:

1
2
3
4
5
6
>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(boolean)
>org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
>org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory#newTransaction()
>org.apache.ibatis.session.Configuration#newExecutor() >org.apache.ibatis.executor.SimpleExecutor#SimpleExecutor >org.apache.ibatis.executor.CachingExecutor#CachingExecutor
//执行器插件包装
>org.apache.ibatis.plugin.InterceptorChain#pluginAll(executor) >org.apache.ibatis.session.defaults.DefaultSqlSession#DefaultSqlSession()

方法执行流程

image-20210516135633903

StatementHandler 源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
>org.apache.ibatis.executor.CachingExecutor#query()
>org.apache.ibatis.executor.BaseExecutor#query()
>org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
>org.apache.ibatis.session.Configuration#newStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler
org.apache.ibatis.session.Configuration#newParameterHandler
org.apache.ibatis.plugin.InterceptorChain#pluginAll(parameterHandler)
org.apache.ibatis.session.Configuration#newResultSetHandler
org.apache.ibatis.plugin.InterceptorChain#pluginAll(resultSetHandler)
>org.apache.ibatis.plugin.InterceptorChain#pluginAll(statementHandler)
>org.apache.ibatis.executor.BaseExecutor#getConnection
>org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement
>org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
>org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
org.apache.ibatis.type.BaseTypeHandler#setParameter
org.apache.ibatis.type.UnknownTypeHandler#setNonNullParameter
org.apache.ibatis.type.IntegerTypeHandler#setNonNullParameter

Mapper接口对象创建

1
2
3
4
5
6
org.apache.ibatis.binding.MapperProxyFactory.newInstance(MapperProxyFactory.java:48)
org.apache.ibatis.binding.MapperProxyFactory.newInstance(MapperProxyFactory.java:53)
org.apache.ibatis.binding.MapperRegistry.getMapper(MapperRegistry.java:50)
org.apache.ibatis.session.Configuration.getMapper(Configuration.java:779)
org.apache.ibatis.session.defaults.DefaultSqlSession.getMapper(DefaultSqlSession.java:291)
com.study.SecondLevelCacheTest.test01(SecondLevelCacheTest.java:46)

MyBatis插件开发

插件的四大扩展点

1、Executor

2、StatementHandler

3、ParameterHandler

4、ResultSetHandler

Executor提供了增删改查的接口.

StatementHandler负责处理Mybatis与JDBC之间Statement的交互.

ResultSetHandler负责处理Statement执行后产生的结果集,生成结果列表.

ParameterHandler是Mybatis实现Sql入参设置的对象。

分页插件实现

用户在接口中声明Page 对像实现后,由插件实现自动分页。

使用示例如下:

1
2
3
4
5
public class Page implements java.io.Serializable {   
private int szie; // 每页大
private int number; // 当前页码
}
List<User> selectUserList(@Param("user") User user, @Param("page") PageDTO page);
1
2
3
<select id="selectUserList" resultType="com.study.pojo.User">
select * from user where id = #{user.id}
</select>

实现目标分解:

1、判断方法参数是否包含Page对象

2、取出page对象生成 limit 语句

3、修改SQL语句添加 limit 语句

4、上述操作必须在Statement(PrepareStatement)对象生成之前完成

image-20210516160916272

image-20210516161122668

根据第四点和上面俩图得知插件拦截的时,拦截的目标类和目标方法如下

1
2
3
4
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
}

所以最终的拦截器代码如下

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
51
52
53
54
55
56
57
58
59
60
61
62
63
public class PagePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.getTarget();
}

@Override
public Object plugin(Object target) {
if(target instanceof StatementHandler){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[]{StatementHandler.class},
new PageHandler((StatementHandler) target));
}
return target;
}

@Override
public void setProperties(Properties properties) {

}

private class PageHandler implements InvocationHandler{

private StatementHandler statementHandler;

public PageHandler(StatementHandler statementHandler) {
this.statementHandler = statementHandler;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("prepare")){
((Map)statementHandler.getBoundSql().getParameterObject()).values().stream()
.filter(e -> e instanceof PageDTO)
.findFirst()
.ifPresent(
page -> {

appendPageSql((PageDTO) page);
}
);
}
return method.invoke(statementHandler,args);
}

private void appendPageSql(PageDTO page) {
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
String appendString = String.format(" limit %s,%s",(page.getNumber()-1) * page.getSzie(),page.getSzie());
String result = sql + appendString;
try {
setField(boundSql,"sql",result);
} catch (Exception e) {
e.printStackTrace();
}
}

private void setField(BoundSql boundSql, String field, String result) throws IllegalAccessException, NoSuchFieldException {
Field declaredField = BoundSql.class.getDeclaredField(field);
declaredField.setAccessible(true);
declaredField.set(boundSql,result);
}
}

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
public class PerformanceInterceptor implements Interceptor {
private static final String JDBC4_PREPARED_STATEMENT = "com.mysql.jdbc.JDBC4PreparedStatement";

/**
* SQL 执行最大时长,超过自动停止运行,有助于发现问题。
*/
@Getter
@Setter
private long maxTime = 0;
/**
* SQL 是否格式化
*/
@Getter
@Setter
private boolean format = false;


@Override
public Object intercept(Invocation invocation) throws Throwable {
Statement statement;
Object firstArg = invocation.getArgs()[0];
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
} else {
statement = (Statement) firstArg;
}
try {
statement = (Statement) SystemMetaObject.forObject(statement).getValue("stmt.statement");
} catch (Exception e) {
// do nothing
}

String originalSql = null;
String stmtClassName = statement.getClass().getName();
if (JDBC4_PREPARED_STATEMENT.equals(stmtClassName)) {
try {
Class<?> clazz = Class.forName(JDBC4_PREPARED_STATEMENT);
Method druidGetSqlMethod = clazz.getMethod("getSql");
Object stmtSql = druidGetSqlMethod.invoke(statement);
if (stmtSql != null && stmtSql instanceof String) {
originalSql = (String) stmtSql;
}
} catch (Exception ignored) {
}
}

if (originalSql == null) {
originalSql = statement.toString();
}
originalSql = originalSql.replaceAll("[\\s]+", " ");
int index = originalSql.indexOf(':');
if (index > 0) {
originalSql = originalSql.substring(index + 1, originalSql.length());
}
String sqlFormat = SqlUtil.sqlFormat(originalSql, format);
Object target = realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(target);
MappedStatement ms = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
Logger mybatisLogger = LoggerFactory.getLogger(this.getClass());
// 计算执行 SQL 耗时
long start = System.currentTimeMillis();
Object result = null;
try {
result = invocation.proceed();
} catch (InvocationTargetException | IllegalAccessException e) {
long timing = System.currentTimeMillis() - start;
StringBuilder formatSql = new StringBuilder()
.append("execute sql error, use time:").append(timing).append("\n")
.append(sqlFormat);
mybatisLogger.error(formatSql.toString());
throw e;
}
long timing = System.currentTimeMillis() - start;

// 格式化 SQL 打印执行结果
StringBuilder formatSql = new StringBuilder().append("Execute sql use time:").append(timing)
.append("\n")
.append(" Execute SQL:").append(sqlFormat)
.append("\n")
.append(" result: ").append(result);

if (this.getMaxTime() >= 1 && timing > this.getMaxTime()) {
mybatisLogger.error(formatSql.toString());
} else {
mybatisLogger.debug(formatSql.toString());
}

return result;
}

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

@Override
public void setProperties(Properties prop) {
String maxTime = prop.getProperty("maxTime");
String format = prop.getProperty("format");
if (!StringUtils.isEmptyOrWhitespaceOnly(maxTime)) {
this.maxTime = Long.parseLong(maxTime);
}
if (!StringUtils.isEmptyOrWhitespaceOnly(format)) {
this.format = Boolean.valueOf(format);
}
}

public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h.target"));
}
return (T) target;
}

}

带参数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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@Intercepts
({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlPrintInterceptor implements Interceptor{

private static final Logger log = LoggerFactory.getLogger(SqlPrintInterceptor.class);

private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameterObject = null;
if (invocation.getArgs().length > 1) {
parameterObject = invocation.getArgs()[1];
}

long start = System.currentTimeMillis();

Object result = invocation.proceed();

String statementId = mappedStatement.getId();
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
Configuration configuration = mappedStatement.getConfiguration();
String sql = getSql(boundSql, parameterObject, configuration);

long end = System.currentTimeMillis();
long timing = end - start;
if(log.isInfoEnabled()){
log.info("执行sql耗时:" + timing + " ms" + " - id:" + statementId + " - Sql:" );
log.info(" "+sql);
}

return result;
}

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

@Override
public void setProperties(Properties properties) {
}

private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
sql = replacePlaceholder(sql, value);
}
}
}
return sql;
}

private String replacePlaceholder(String sql, Object propertyValue) {
String result;
if (propertyValue != null) {
if (propertyValue instanceof String) {
result = "'" + propertyValue + "'";
} else if (propertyValue instanceof Date) {
result = "'" + DATE_FORMAT.format(propertyValue) + "'";
} else {
result = propertyValue.toString();
}
} else {
result = "null";
}
return sql.replaceFirst("\\?", Matcher.quoteReplacement(result));
}
}