Mybatis原理、插件与源码分析


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() 包含了CongurationExecutor

getMapper()

DefaultSqlSession().getMapper(XXXMapper.class)

—-> configuration.getMapper(type, this);

——>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.query(stmt, resultHandler);

@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保存结果

小结

  1. 根据配置文件 初始化Configuration对象
  2. 创建一个DefaultSqlSession对象 包含了Configuration和Executor
  3. 创建ProxyMapper 包含了DefaultSqlSession
  4. 执行增删改查方法 通过代理对象去调用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;
}

文章作者: Bxan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bxan !
  目录