首先分享之前的所有文章 , 欢迎点赞收藏转发三连下次一定 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一 . 前言
好久没有关注 Mybatis 源码了 , 最近工作上碰到了一些 Mybatis 的问题 , 于是又花了一点时间把 Mybatis 的笔记整理了一遍 , 方便以后问题的排查
这一篇以一个小环节开始 , 来逐步的展开 Mybatis 的处理流程
二 . Logger 触发流程
以一个Query 流程为例,通常我们可以打印 SQL Log 以判断 SQL 的具体执行情况 , 以一个常见的 SQL 打印为例 :
JDBC Connection [HikariProxyConnection@2034411604 wrapping com.mysql.cj.jdbc.ConnectionImpl@53b8afea] will not be managed by Spring
==> Preparing: select sn, username, `password`, `power`, utype, isactive , age from user
==> Parameters:
<== Columns: sn, username, password, power, utype, isactive, age
<== Row: 0, 22, 33, 44, null, null, 1
<== Row: 1, 1, 1, 1, 1, 1, null
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64355120]
那么整体触发流程是什么样的呢 ? 其实查看 Log 得流程就可以了解到整个流程 ,其中包括 :
- Connection 连接的创建 : 连接对象信息 , 使用什么样的连接池 , 连接对象等等
- SQL 语句的生成 : 操作使用的什么 SQL , 占位符特性
- 参数的处理 : 查询使用的参数
- ROW 行的打印 : 查询的 colums 和 Row 行结果
- 查询总数的计算 : 当前查询的总数
- Session 的关闭 : Session 的额外处理 , 操作 , 使用的 Session 类型
每个 Log 的打印 , 往往意味着一个处理节点的完成 , 下面先来看一下整个处理流程 :
黄色标注的即为 log 打印处理 ,可以看到是贯穿到整个 Mybatis 流程中的.
补充 : Log 实现类
Log 核心接口 , 常用的实现有以下几个
C- StdOutImpl :实现 Log 接口,StdOut 实现类 , 基于 System.out 和 System.err 来实现
C- Slf4jImpl : 实现 Log 接口,Slf4j 实现类 ,
MC- Slf4jImpl , 构造函数中初始化对象
|- logger.getClass().getMethod
|- 会通过2中方式去生成具体的类
|- org.apache.ibatis.logging.slf4j.Slf4jLocationAwareLoggerImpl
|- new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger)
|- org.apache.ibatis.logging.slf4j.Slf4jLoggerImpl 类
M- error / warn / info / debug 等方法直接调用log.xxx
C- BaseJdbcLogger
|- Log 下 JDBC 包 ,有以下的实现类
|- 基于 JDBC 接口实现增强的案例,而原理上,也是基于 JDK 实现动态代理
// 其他 Logger 实现类
C- BaseJdbcLogger
C- ConnectionLogger
C- PreparedStatementLogger
C- StatementLogger
C- ResultSetLogger
补充 : LogFactory
MC- 构造器中 , 会初始化 logConstructor 属性 , 此处通过对各种实现进行 tryImplementation(Runnable runnable) 操作 , 判断能使用哪个实现
|- tryImplementation(LogFactory::useSlf4jLogging);
M- useSlf4jLogging : 调用 #setImplementation(Class<? extends Log> implClass) 方法,尝试使用指定的 Log 实现类
|- new Instance 方式 , 通过 getName 创建
M- useCustomLogging(Class<? extends Log> clazz) 方法,设置自定义的 Log 实现类
M- getLog : 获得 Log 对象
|- logConstructor.newInstance**
二 . Log 处理详情
下面再来简单过一下整个log的处理详情 :
3.1 连接信息打印
Mybatis 为了适配各类 log 框架 , 基于 Log 接口 实现了很多实现类 , 核心处理的包括 BaseJdbcLogger及其四 个子类 , 作用是 通过 JDK 动态代理的方式,将 JDBC 的操作,打印到日志中 .另外还有工厂类 LogFactory
通过 SpringManagedTransaction 获取连接信息时会触发 getConnection 操作>>
private void openConnection() throws SQLException {
// 获取连接和事务自动提交配置
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
// 打印 JDBC Connection 信息
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}
3.2 SQL 信息的打印
// S1 : ConnectionLogger 代理入口
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
// 反射获取 PreparedStatement
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
// 打印 Log
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// S2 : 调用具体的 Log 实现类
protected void debug(String text, boolean input) {
if (statementLog.isDebugEnabled()) {
statementLog.debug(prefix(input) + text);
}
}
触发方式
// S1 : 入口
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
// S2 : SQL 打印逻辑 PreparedStatementHandler
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
// 此处的 connection 会实际调用 ConnectionLogger
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
3.3 Select 结果的打印
查询结果的打印是在 ResultSetLogger 中进行 , 显示数据结构
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
// 当 Invoke 的方法为 next() 时 ,会返回 Boolean 型对象
Object o = method.invoke(rs, params);
if ("next".equals(method.getName())) {
if (((Boolean) o)) {
// 行数加一
rows++;
if (isTraceEnabled()) {
ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
// 如果是第一条 , 则打印请求头 ,即字段名
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
// 打印字段值
printColumnValues(columnCount);
}
} else {
// 如果 next 返回 false , 表示 next 不存在 , 打印结果
debug(" Total: " + rows, false);
}
}
clearColumnInfo();
return o;
}
ResultSetMetaData 数据结构
3.4 Close Session 的打印
在 SqlSessionInterceptor finally 中 , 最终会调用 Close 关闭 Session
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// SqlSessionHolder 用于在当前 TransactionSynchronizationManager 维持 Session
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
// 如果是 SqlSessionHolder 进行处理 ,则直接释放
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
// 如果是 session 处理 , 无事务 ,则直接关闭
session.close();
}
}
总结
从 Log 上大概了解到了 Mybatis 的处理流程 , 下面分别深入下细节以及学习如何进行扩展定制