mybatis运行原理是什么?
实际上SqlSession的执行过程就是通过Executor、StatementHandler、ParameterHandler、ResultSetHandler这四个对象来完成对数据库的操作和返回结果的。
ResultSet rs =statement.executeQuery(sql);//级别根据这条语句就能判断3大对象
Executor是负责调用语句执行的。
public SqlSessionFactory getSqlSessionFactory() throws IOException{
String resource="mybatis-config.xml";
InputStream inputStream=Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test01() throws IOException{
//1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory= getSqlSessionFactory();
//2、获取sqlSession对象
SqlSession openSession =sqlSessionFactory.openSession();
try{
//3、获取接口的实现类对象
//会为接口自动的创建一个代理对象(MapperProxy),代理对象去执行增删改查方法
EmployeeMapper mapper=openSession.getMapper(EmployeeMapper.class);
Employee employee=mapper.getEmpById(2);
System.out.println(mapper.getClass());
System.out.println(employee);
}finally {
openSession.close();
}
}
构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 解析配置文件,放入Configuration
- 构造SqlSessionFactory
- 将MappedStatement放入SqlSessionFactory.Configuration对象中,之后会传递到SqlSession对象中。
Configuration中一个map来存放MappedStatement,key是接口的路径(namespace+id)。
MapperProxyFactory
- 在Configuration.mapperRegister中还注册了每个接口的MapperProxyFactory。
构建sqlsession
SqlSession openSession =sqlSessionFactory.openSession();
SqlSession最终交给Executor具体干活。
创建Executor【拦截器】
创建SqlSession前先创建Executor。
- 根据配置创建对应的Executor对象(SimpleExecutor、ReuseExecutor、BatchExecutor、CachingExecutor)
- 创建Executor时,会应用interceptor
创建出的Executor最终放入SqlSession中。
SqlSession对象内部有
- Configuration
- Executor
SqlSession直接操作情况(不获取Mapper)
getMapper
EmployeeMapper mapper=openSession.getMapper(EmployeeMapper.class);
返回的Mapper就是一个被MapperProxy包装的动态代理。
- 通过Configuration.mapperRegister,其中有knowMappers(Map)根据类名找到对应的的MapperProxyFactory
- MapperProxyFactory通过动态代理构建Mapper(MapperProxy代理)。
MapperProxy(InvocationHandler)
调用Mapper方法
1.MapperProxy动态代理方法调用,交给sqlSession
Employee employee=mapper.getEmpById(2);
实际是调用动态代理MapperProxy的invoke()方法。
调用mapperMethod.execute(sqlSession, args);
ParamNameResolver处理传入参数:
从Configuration中获取MappedStatement
- 调用Executor
- 调用selectOne()也是调用selectList()
也可以手动直接调用sqlsession.selectOne(),MapperProxy的目的就是为了调用方便
3.执行Executor
Executor类型
Mybatis一、二级缓存
一级缓存【SESSION和STATEMENT】
什么时候命中一级缓存:
- 同一个SqlSession
- 相同的StatementID
- 参数相同
一级缓存:
- 一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除。
- statement范围是针对statement内部子查询?
建议是把一级缓存的默认级别设定为Statement(禁用一级缓存)
整合Spring Mybatis一级缓存失效问题
- 你凭什么说Spring会导致MyBatis的一级缓存失效! - 云+社区 - 腾讯云
通过整合Spring后我们发现,我们的SqlSession对象被偷梁换柱了,换成了SqlSessionTemplate类
因为Spring只有在开启了事务之后,在同一个事务里的SqlSession会被缓存起来,同一个事务中,多次查询是可以命中缓存的
二级缓存【namespace】
开启二 级缓存
- 默认cacheEnabled=true
- sqlSession.commit() 才放入缓存。
二级缓存:
- SqlSession之间缓存数据
- 缓存是以namespace为单位的,不同namespace下的操作互不影响。
- 例如在UserMapper.xml中有大多数针对user表的操作。但是在另一个XXXMapper.xml中,还有针对user单表的操作。这会导致user在两个命名空间下的数据不一致
- insert,update,delete操作会清空所在namespace下的全部缓存。
- 同一个Mapper中的MappedStatement中的cache属性引用同一个对象。
- 不推荐使用mybatis二级缓存
二级缓存种类
BlockingCache
FifoCache
一个队列用来记录cache的顺序。
LruCache
LruCache使用LinkedHashMap来维护Lru
SoftCache
使用软引用
Executor查询流程
- 先调用CachingExecutor,
- 获取BoundSql
- 处理二级缓存
再调用SimpleExecutor、ReuseExecutor、BatchExecutor中的一种的doQuery()
StatementHandler:创建出Statement对象
三种StatementHandler:
一般都用PreparedStatementHandler
StatementHandler执行流程
1. 创建StatementHandler【拦截器】
根据Configuration,创建statementHandler(PreparedStatementHandler)
创建statementHandler过程中会创建ParameterHandler和ResultSetHandler。
Mybatis插件就是通过拦截器,返回动态代理
2. 创建ParameterHandler和ResultSetHandler【拦截器】
3. 创建Statement,包括使用ParameterHandler设置参数
- 其中包括使用ParameterHandler设置参数
- 设置参数时会用到TypeHandler
4. 执行StatementHandler.query(),PreparedStatement查询,ResultSetHandler处理结果
- 就是PreparedStatement#execute
- 然后ResultSetHandler处理结果
PreparedStatementHandler#query
Configuration
延迟加载
局部延迟加载:
全局延迟加载:
原理:通过动态代理,拦截方法,加载数据。
Mybatis和Spring整合
SqlSessionFactoryBean构造Configuration,DefaultSqlSessionFactory
SqlSessionFactoryBean#afterPropertiesSet
- this.sqlSessionFactory = buildSqlSessionFactory();
- 先构建Configuration,将Configuration传入DefaultSqlSessionFactory
MapperFactoryBean:配置mapper
spring中需要配置
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
spring帮我们调用了sqlSession.getMapper()操作。
每一个mapper单独配置太麻烦了,所以要引入MapperScannerConfigurer,自动扫描mapper
MapperScannerConfigurer(BeanDefinitionRegistryPostProcessor):自动扫描mapper
MapperScannerConfigurer是一个BeanDefinitionRegistryPostProcessor。
MapperScannerConfigurer(BeanDefinitionRegistryPostProcessor)会再spring启动时用来注册BeanDefinition。
这里扫描mapper包
ClassPathMapperScanner#processBeanDefinitions
最终注册的是MapperFactoryBean的BeanDefinition。【为什么是FactoryBean,因为sqlSession这时候还没有】
通过RuntimeBeanReference来引用SqlSessionFactory。
SqlSessionFactory在AutoConfiguration中已经注册了。
@MapperScan:加载MapperScannerConfigurer BeanDefinition
之前为手动加载mybatis-config.xml。spring需要自动将Mapper纳入到容器中。
- @MapperScan引入MapperScannerRegistrar.class
- MapperScannerRegistrar中创建MapperScannerConfigurer
Mybatis和Springboot整合总结
总结:SqlSessionFactory中解析xml,@MapperScan负责包装MapperProxyFactory到Spring容器。
MapperProxyFactory中关联SqlSessionFactory。
注册到Spring这样省去了EmployeeMapper mapper=openSession.getMapper(EmployeeMapper.class);这一步。Mapper从容器中获取。
Spring通过SqlSessionTemplate来保证SqlSession线程安全
如何保证SqlSession的线程安全? - SegmentFault 思否
SqlSessionTemplate持有SqlSession动态代理对象。