概要:
- 执行流程解析
- 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.builder.xml.XMLMapperBuilder >org.apache.ibatis.builder.xml.XMLStatementBuilder >org.apache.ibatis.builder.SqlSourceBuilder >org.apache.ibatis.scripting.xmltags.XMLScriptBuilder >org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
|
SqlSession 会话对象创建
首先我们还是先来了解一下会话对像的组成结构如下图:

会话构建源码解析:
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()
|
方法执行流程

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)对象生成之前完成


根据第四点和上面俩图得知插件拦截的时,拦截的目标类和目标方法如下
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";
@Getter @Setter private long maxTime = 0;
@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) { }
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()); 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;
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)); } }
|