一.Mapper代理对象的生成流程:
1.ClassPathMapperScanner 扫描所有符合条件的Mapper接口(通过@MapperScan(package path) 开启Mapper接口扫描,如果不打这个注解,会通过MybatisAutoConfiguration默认扫描当前配置类的包里面的所有打上了@Mapper标签的Mapper接口)
2.将符合条件的Mapper接口的BeanDefinition的BeanClass设置为MapperFactoryBean既通过工厂方法生成代理对象
3.通过JDK的动态代理API生成代理对象,其中MapperProxy作为InvocationHandler的实现类在创建代理对象前会通过构造方法初始化接口类对象,方法缓存Map(key 是Method对象,value 是MapperMethod 对象) ,SqlSession的实现类SqlSessionTemplate 3个成员变量,通过java.lang.reflect.Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy)
生成代理类
4.注入到spring容器中
二.sql方法的执行流程
1.获取Mapper的代理对象,调用invoke方法(InvocationHandler的接口方法)
2.invoke方法中将目标方法封装成MapperMethod(封装了需要执行的Mapper中的Method对象和其相关的SqlCommand对象)
3.mapperMethod.execute(sqlsession),该sqlsession就是生成代理前注入MapperProxy的SqlSessionTemplate
4.判断sql的类型,增删改查的哪一种
5.根据不同类型选择执行SqlSessionTemplate 的方法如selectList,selectOne等
6.SqlSessionTemplate 会通过其内部的成员变量(SqlSession)sqlSessionProxy来执行这些方法
7.成员变量sqlSessionProxy默认是一个Jdk动态代理对象,通过SqlSessionTemplate构造方法中的
(SqlSession)java.lang.reflect.Proxy. newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
8.SqlSessionInterceptor的invoke方法会生成新的DefaultSqlSession,并初始化了其 configuration,executor
,autoCommit 三个成员变量
9.sqlSessionProxy通过DefaultSqlSession来真正执行这些方法
10.DefaultSqlSession 执行sql方法中通过其成员变量Executor(根据Executor类型选择实例,通过下面的newExecutor的方方法获取初始化实例,如果开启了二级缓存是会用CachingExecutor封装Executor的其他实现类如simpleExecutor,如果没有则不封装)来与数据库交互.
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
11.executor = (Executor) interceptorChain.pluginAll(executor)
会遍历所有的拦截器并执行拦截器的plugin方法,一般plugin方法的实现是Plugin.wrap(obj, this),
如下,其中Plugin is InvocationHandler,该方法会通过JDK动态代理生成executor的代理对象,代理对象会执行拦截器方法(下2,当执行方法签名与拦截器配置需要拦截的方法匹配时执行拦截器的方法),有多个拦截器的时候则会生成多层代理(递归,性能不高),如果在拦截器中调用的executor的方法不与后面的拦截器适配则后面的拦截器方法不会执行,如分页拦截器com.github.pagehelper.PageInterceptor
,他调用的executor的执行方法是 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException
,如果后面拦截器的@Intercepts(@Signature())
注解中配置的拦截方法签名与之不匹配,则不会执行后面的拦截器
//动态代理的生成方法
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//动态代理的执行方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
12.executor的实现类通过本地Map实现一二级缓存功能,一级是session级别的缓存,二级是nameSpace级别的缓存
13.一级缓存通过SqlSession中Executor的实现类(除了CachingExecutor)实现,如SimpleExecutor,其成员变量PerpetualCache localCache 用于存放一级缓存内容,当sqlSession.commit后会调用SimpleExecutor的commit其中会把一级缓存清除,sqlSession完成sql提交到数据库后,会判断这次会话是否被Spring的事物管理器管理,如果是则此次SqlSesionn不会调用commit方法,如果不是则会直接提交
14.二级缓存通过CachingExecutor的TransactionalCacheManager变量获取TransactionalCache,之后通过装饰链(TransactionalCache->SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache->HashMap<Object, Object>)获取真正缓存的数据的byte数组,并通过SerializedCache反序列化成相关的List 数据,在同一个nameSpace中执行update,delete等修改操作方法时,CachingExecutor会调用flushCacheIfRequired清除缓存,
@CacheNamespaceRef标签可以指定当前接口的sql标签方法的二级缓存归属于哪个nameSpace,不指定时,由于同一个Mapper中通过注解绑定的方法和通过xml绑定的方法的二级缓存是分别解析的,会导致同一Mapper中注解的更新方法无法清除xml查询方法的缓存,反之亦然,并且同一个Mapper的@CacheNamespace标签与其Xml里的只能开启一个,否则会报缓存已存在的错误,因此如果一个Mapper中有部分方法是注解的,有部分方法是xml的需要让这个nameSpace的二级缓存生效需要用@CacheNamespaceRef标签指定nameSpace和xml里面的一样,或者xml的标签指定的nameSpace和@CacheNamespace标记的Mapper接口名一致。
Mybatis的org.apache.ibatis.session.Configuration中保存了所有的二级缓存集合caches(StrictMap<Cache>),
存二级缓存的Key一般存两份nameSpace和其短称(最后一个点后面的词:如W.aa 短称是aa),指向同一个缓存集合,其余的如MappedStatements集合等也是这种存放规则
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
15.executor的实现类通过DataSource实例及相关的JDBC API来实现sql的执行及数据的返回