Mybatis原理、插件与源码分析
![屏幕快照 2018-10-21 下午5.52.46](Mybatis原理、插件与源码分析/屏幕快照 2018-10-21 下午5.52.46.png)
查询执行流程分析
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
xxxMapper mapper = sqlSession.getMapper(xxxMapper.class);
mapper.selectXXX();
SqlSessionFactory的获得
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
经过这个方法后会返回一个包含Configuration对象的DefaultSqlSessionFactory实例。
![屏幕快照 2018-10-20 下午11.27.31](Mybatis原理、插件与源码分析/屏幕快照 2018-10-20 下午11.27.31.png)
configuration中有一个很重要的对象:mappedStatements (里面包含所有mapper.xml所映射的方法,标签,属性及sql语句和参数)
![屏幕快照 2018-10-20 下午11.28.40](Mybatis原理、插件与源码分析/屏幕快照 2018-10-20 下午11.28.40.png)
openSession()
与二级缓存相关若缓存开启executor会被包装
**executor = (Executor) interceptorChain.pluginAll(executor);**如果用拦截器件executor会再次被包装
//关键代码
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
最终会返回一个 DefaultSqlSession() 包含了Conguration和Executor
getMapper()
DefaultSqlSession().getMapper(XXXMapper.class)
—-> configuration.
——>mapperRegistry.getMapper(type, sqlSession);
——>mapperProxyFactory.newInstance(sqlSession); jdk动态代理
——>返回MapperProxy代理对象
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
selectById (一切curd语句都如此)
![屏幕快照 2018-10-21 下午5.37.21](Mybatis原理、插件与源码分析/屏幕快照 2018-10-21 下午5.37.21.png)
MapperProxy.invoke()—>
MapperMethod mapperMethod =cachedMapperMethod(method)
—>mapperMethod.execute(sqlSession, args) (MapperMethod类的实例封装着sql语句的类型,判断类型 包装参数 通过sqlSession执行语句,返回查询结果)
execute(SqlSession sqlSession, Object[] args)
....
Object result;
switch (command.getType()){
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
....
return result;
—>(Cache)Executor.query() 获取BoundSql(包含sql语句 参数 映射关系) 会创建缓存map,缓存就是一个map ,key为sql语句 参数 映射关系等一系列信息
–>SimpleExecutor.query() (会去本地缓存中拿值先去二级缓存中去拿 没有的话再去一级缓存中拿,通过上面说的key,若还没有会调用queryFromDatabase())
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
—>queryFromDatabase()首先会根据key 往本地缓存(一级缓存)中放入一个key-value 。value的值为占位符。
—>BaseExecutor.doQuery() (通过创建StatementHandler创建jdbc的Statement。最终创建了一个PrepareStatementHandler对象 ,默认为它 )
@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.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
—>PrepareStatementHandler(同时会创建ParameterHandler对象同时也有个拦截器插件interceptorChain作用于ParameterHandler对象,和ResultSetHandler对象也有个拦截器插件interceptorChain作用于ResultSetHandler对象)
这里类似于openSession一样 有个拦截器插件interceptorChain作用于StatementHandler
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
—>handler.
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
会预编译sql产生PreparedStatement
—>调用ParameterHandler 设置参数
—>调用TypeHandler设置参数
—>调用查询 已经可以查出来数据!!!!!!
—->调用TypeHandlerc处理结果
—>ResultSetHandler保存结果
小结
- 根据配置文件 初始化Configuration对象
- 创建一个DefaultSqlSession对象 包含了Configuration和Executor
- 创建ProxyMapper 包含了DefaultSqlSession
- 执行增删改查方法 通过代理对象去调用DefaultSqlSession的增删改查–>创建StatementHandler对象—>调用StatementHandler的增删改查
插件
四大对象
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
插件原理
四大对象的创建都会经过(XXX)interceptorChain.pluginAll(xxx)
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//Interceptor 拦截器是一个接口
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
通过阅读代码可知 每个target 都要被Interceptors数组中的每个 Interceptor的plugin方法拦截使用。
我们的插件可以为目标对象创建代理对象:AOP 代理对象可以拦截到四大对象的每一个执行
创建时间
Executor 执行openSession的时候
ParameterHandler (getParameterObject, setParameters) 执行查询语句的时候
然后会创建下面两个
ResultSetHandler (handleResultSets, handleOutputParameters) 同上
StatementHandler (prepare, parameterize, batch, update, query)同上
使用方式
1.创建 Interceptor实现类
2.使用 @Interceptor 完成插件签名
3.将写好的插件注册到全局配置中
代码详解
package plugin;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
//第二步: 完成插件的签名
@Intercepts({
@Signature(type = StatementHandler.class,method ="parameterize",args = {Statement.class})
})
//第一步:创建实现类
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyPlugin--->intercept"+invocation.getMethod());
//相关于执行目标方法
Object proceed = invocation.proceed();
return proceed;
}
@Override
public Object plugin(Object target) {
System.out.println("Mybatis 包装的对象!!!!"+target);
//使用mybatis为我们提供的Plugin 类的wrap方法 使用我们的拦截器将target封装
//本质上就是为我们创建代理对象
Object wrap = Plugin.wrap(target,this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println(properties);
System.out.println("配置");
}
}
在Mybatis的配置文件中加入如下信息
<!--第三步:插件配置文件-->
<plugins>
<plugin interceptor="plugin.MyPlugin">
<property name="Name" value="Hhaha"/>
<property name="XU" value="XIIXIX"/>
</plugin>
</plugins>
wrap 源码详解
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//判断interfaces 也就是插件的的签名 是否为空,若果为空代表该插件没有作用的对象或方法 所以不需要包装target 直接返回即可
if (interfaces.length > 0) {
//通过Jdk动态代理的方式创建代理对象,new Plugin(target, interceptor, signatureMap)) 中的Plugin是一个实现了InvocationHandler接口的对象,里面封装着所有为 invoke方法服务的成员(属性+方法)。
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}