前言
简单复习以下mybatis的原理为学习mybatis-plus和PageHelper做准备,sqlSessionFactory的生成之类的没什么作用没写。
主要资料来源于书籍《深入浅出MyBatis技术原理与实战》
顺便吐槽一下,mybatis的源码真的一点注释都没有,如果不是有资料真的不想看
SqlSession中的主要对象
StatementHandler: 使用数据库的Statement执行对应操作
ParameterHandler: 对Sql中的参数进行处理
ResultHandler: 对数据集进行封装返回处理
Executor:用于调度其他组件来执行对应的Sql
Executor
所谓调度其他组件就是通过Configuration对象来生成其他组件然后按照流程调用对应的组件。默认的SimpleExecutor执行简单查询的流程代码如下:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
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;
}
用文字描述就是先通过configuration对象参数创建一个StatementHandler 对象,然后使用StatementHandler通过方法prepare() 进行预编译和基础设置来创建Statement对象,再通过方法parameterize() 方法来设置参数,最后再将Statement和ResultHandler 传递给StatementHandler来执行并返回结果
StatementHandler
StatementHandler的创建,创建时会创建一个RoutingStatementHandler,RoutingStatementHandler会根据配置的参数来创建CallableStatementHandler
PreparedStatementHandler和SimpleStatementHandler中的一种,默认为PreparedStatementHandler,创建时的代码如下
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
需要注意的时实际用于处理的StatementHandler是创建的RoutingStatementHandler中的delegate 参数而非RoutingStatementHandler本身。
可以自定义的过程
Interceptor
mybatis提供一个叫做Interceptor的接口。是想该接口并通过注解可以对SqlSession中四个对象的任意一个方法进行拦截,接口的定义如下
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
// NOP
}
}
intercept() 方法类似于Invoker接口中的invoke() 方法,Invocation 对象本身就包含了调用所需的参数,可以通过MetaObject类来对它进行处理。
plugin() 方法用于生成代理对象,setProperties() 用于读取配置文件来初始化(好像不能直接读取springboot的配置文件),可以看到新版的mybatis中都提供了默认实现。
实现方法:
通过configuration创建Sqlsession中对象时会调用这样一个方法
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
可以看到会使用所有以注册的Interceptor来创建代理对象,这样所有注册了的Interceptor都可以代理具体的对象
简单示例
代码是从书中直接抄来的,为了方便运行小改了一下
@Intercepts({@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})
})
public class LimitInterceptor implements Interceptor {
//防止重名
private static final String LMT_TABLE_NAME = "limit_Table_Name_xxx";
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
//分离出原始对象,这步因为没注释我看不懂反正这么做就行了
while (metaStatementHandler.hasGetter("h")){
Object o = metaStatementHandler.getValue("h");
metaStatementHandler = SystemMetaObject.forObject(o);
}
String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
String limitSql;
//没有被重写过
if(!sql.contains(LMT_TABLE_NAME)){
sql = sql.trim();
//重新sql
limitSql = "SELECT * FROM (" + sql + ")" + LMT_TABLE_NAME + " limit 500";
metaStatementHandler.setValue("delegate.boundSql.sql", limitSql);
}
return invocation.proceed();
}
}
@Intercepts注解表示该类同于拦截mybatis组件的方法,@Signature注解用参数表明了拦截的方法
在springboot中直接提供bean即可注册
@Bean
public Interceptor myInterceptor(){
return new LimitInterceptor();
}