插件原理和Spring集成
目标:
掌握插件的使用方法和工作原理
掌握自定义插件的编写方法
掌握Spring集成mybatis的原理
mybatis插件
使用PageHelper插件
在mybatis-config.xml中配置plugins标签
PageHelper.startPage(0,5);
插件的实现
实现插件的3个步骤
1、实现Inteceptor接口
2、重写intercept
3、通过注解 指定要拦截的对象、方法、参数
可以参考下PageHelper的注解
插件不用改动原来的查询功能,就得到了新的增强,肯定是用到了代理模式
如果有多个插件层层拦截,用到了责任链模式、
哪些对象、哪些方法 可以被拦截
当然是被代理的对象
Executor 如果有二级缓存,那么先创建基本类型、再用二级缓存装饰、最后插件拦截
插件的原理
思考下:
1、使用代理模式,是jdk还是cglib动态代理
2、代理类何时创建,解析配置?创建会话?还是执行sql时
3、核心的对象被调用,流程是怎样的。如何依次执行多个插件
对Executor的拦截是 openSession时创建的。
Executor executor = this.configuration.newExecutor(tx, execType);
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
可以看到pluginAll 里迭代包装了我们原先创建的Executor。
Plugin 这个类是一个实现了InvocationHandler的类,所以他是代理管理类。
看看他的包装方法wrap(),
在wrap()方法中创建了一个Plugin对象,他封装了被代理类、Inteceptor
被代理对象有几个插件,就要被代理几次
被代理了肯定得先走代理管理类的invoke()方法
先走intercptor的intercept() ,方法为空直接走代理方法
如果有多个插件,再走一次Plugin的invoke()
配置的顺序和执行的顺序
配置的顺序跟执行的顺序是相反的,最先配置的插件离被代理类最近,必然最后执行
总结:
PageHelper原理
先看下PageHelper使用
PageHelper.startPage(0,5);
List<User> users = userMapper.queryUsers();
PageInfo pageInfo = new PageInfo(users,10);
先设置分页start和limit,再把生成的结果封装成PageInfo 返回给前台
SQL改写的实现,PageHelper如何通过拦截器实现分页
先看下他的实现方法
这个getPageSql针对多种数据库,有不同的实现方式
mysql:
oracle
如何把start和limit参数传给插件的
看下PageHelper.startPage(); 找到一个setLocalPage(page);
发现这个是一个ThreadLocal 分页副本。
而在AbstractHelperDialect中的 getPageSql (),获取分页信息是从getLocal中获取的。
所以每查一次就会有一个线程私有的Page变量
关键类:
常用场景、自定义插件
当一个表数据太大时,进行按月份分表
fee_202001-fee_202012
表有三个字段 id,费用,日期
sql:查询按照日期查询
实现思路:根据日期查询时,自动把表替换为对应的月份表
Spring集成
原生的mybatis api主要有三个关键类
SqlSessionFactory
SqlSession
getMapper返回的 mapper接口代理对象
虽然使用mybatis后,变的简单了,但是没有集成spring是没有灵魂的。
为了集成Spring,mybatis拓展了相关接口。把这三个对象都封装起来了
配置依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
配置数据源
配置mapper.xml路径
配置Mapper注解或者启动是MapperScan扫描包路径
就可以把mapper对象注入到Service中去使用了
思考下集成时发生了什么;
1、通过注解注入了一个接口,这个接口在IOC中也是一个代理对象吗
2、如果是代理对象,是不是还是用SqlSession执行的sql,SqlSession何时创建的
3、单例的SqlSessionFactory何时创建
配置依赖
applicationContext.xml中配置
SqlSessionFactory和mapper.xml
了解spring和mybatis的集成,只需要了解
1、SqlSessionFactory在哪里创建
2、SqlSession在哪里创建
3、代理类在哪里创建
创建SqlSessionFactory
直接进SqlSessionFactoryBean 看,不出意外实现了FactoryBean
SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener
InitializingBean
实现了InitializingBean的afterPropertiesSet()方法
调用buildSqlSessionFactory()方法,这里创建一个Configuration targetConfiguration;
存储所有的xml配置,熟悉的味道。
下面一堆typeAliases、plugins、typeHandlersPackage的解析,都是从这里来
这里通过xml解析的parse()方法和数据源enviroment都在源码分析里看到过,很类似
再下面就该到了解析Mapper环节了
xmlMapperBuilder.parse();喜闻乐见
mapper的parse()方法里做了两件事情
1、把增删改查标签换成statement标签
2、把mapper接口的class对象和对相应的Mapper代理工厂注册到MapperRegistry
最后调用一下build方法,还是返回一个DefaultSqlSessionFactory对象
sqlSessionFactoryBuilder.build(targetConfiguration);
总结:
通过实现InitializingBean ,重写afterPropertiesSet 方法,会在bean的属性设置完的时候被调用。
spring在启动这个bean的时候,实现了解析和创建工厂类
这个阶段的准备工作都是类似的,先解析配置,存到一个Configuration的对象中,把数据源交给事务管理,
解析mapper接口资源,每一个mapper接口都和他的代理工厂被注册起来
最后返回一个DefaultSessionFactory
FactoryBean
FactoryBean是spring的一个Bean接口,负责生产和装饰Bean,这里通过重写getObject(),里面调用
afterPropertiesSet(),去做mybatis的解析配置工作
ApplicationListener
实现这个接口是为了让DefaultSqlSessionFactory监控应用发出的一些事件
这里监听了 ContextRefreshedEvent ,上下文刷新事件,在spring容器加载之后执行
这里检查了ms是否加载完毕
创建会话SqlSession
在spring里没有直接用DefaultSqlSession,因为他是线程不安全的。之前我们每次会话会创建一个
Sqlsession,所以不会存在线程安全问题,现在交给容器管理,所以要考虑线程问题
Spring提供了SqlSessionTemplate ,他是SqlSession包装类,他也是单例的,可以被所有dao共享
打开SqlSessionTemplate ,发现里面也有很多curd方法,但是没有自己实现,都是通过一个代理调用的
this.sqlSessionProxy.selectOne(statement);
可以看到在初始化时,他是通过jdk动态代理实现的。。
那么所有的调用方法都会先执行 SqlSessionInterceptor 的invoke()方法
SqlSessionInterceptor 这个内部类invoke放
通过SqlSessionUtils 创建了一个SqlSession
把SqlSessionFactory、执行器类型、异常解析器都传进去
通过这个内部类的invoke方法可以看到,通过代理模式,调用一次方法,就走一次invoke,产生一个DefaultSqlSession的实例,保证了线程安全性。。
SqlSessionTemplate
SqlSessionTemplate 可以简化mybatis在spring中的使用
如何获取SqlSessionTemplate,并且在任何需要替代DefaultSqlSession的地方都可以使用,并且也必须是单例
mybatis提供了一个抽象类 SqlSessionDaoSupport,持有了SqlSessionTemplate对象
1、继承SqlSessionDaoSupport,再调用提供的增删改查
2、定义一个BaseDao继承SqlSessionDaoSupport,封装调用方法,Mapper接口实现类再继承BaseDao
上面的比较繁琐,还是使用Mapper接口代理类来实现方法的调用
Mapper接口的代理类肯定存在于BeanFactory中
思考:
1、何时注册到容器中的
2、注册的时候,是代理对象吗?那显然是一定
接口的扫描
在applicationContext.xml中配置了扫描包路径的MapperScannerConfigurer
打开 MapperScannerConfigurer,他实现了BeanDefinitionRegistryPostProcessor接口,主要是重写了postProcessBeanDefinitionRegistry() 这个方法
实现了这个方法就可以在spring创建Bean之前,修改某些Bean的内容,看下做了什么
1、把先扫描的包路径下的接口都封装成BeanDefinition,放到一个set里
2、processBeanDefinitions 处理 每一个BeanDefinition
definition.setBeanClass(this.mapperFactoryBeanClass);
这里BeanClass被注册成支持泛型的mapperFactoryBean,为什么要这样做
因为:MapperFactoryBean继承的SqlSessionSupport实现的FactoryBean;
继承SqlSessionSupport 意味着么一个注入Mapper的地方都可以拿到SqlSessionTemplate
接口注入使用
看看MapperFactoryBean类的getObject()可以看到调用的SqlSessionTemplate的getMapper方法
SqlSessionTemplate的本质就是一个代理,最终调用的getMapper()和DefaultSqlSession一样,都是
通过mapperProxyFactory 这个代理工厂创建jdk动态代理对象
总结:mybatis和spring集成时,spring做了什么
1、SqlSession的替代品SqlSessionTemplate,他有个内部类SqlSessionInterceptor,本质是对SqlSession的代理
2、提供了获取SqlSessionTemplate的抽象类SqlSessionDaoSupport
3、扫描Mapper接口,注册到容器中的是MapperFactoryBean,他继承了SqlSessionDaoSupport,可以获得
SqlSessionTemplate
4、mapper接口注入使用的时候,调用getObject方法,这里调用的是SqlSessionTemplate的getMapper
用jdk动态代理类返回的一个代理实例
5、调用Mapper接口的任意方法,触发管理类MapperProxy,进入sql流程
通过mybatis集成spring,思考下
1、为组件预留拓展接口
2、利用Spring的拓展机制,把组件集中到mybatis中
设计模式总结