推荐一个介绍mybatis的:https://blog.csdn.net/zwx900102/category_8934270.html
学习了自己做个总结
一:流程
//1,启动加载创建sqlSessionFactory
String config = "config.xml";
InputStream inputStream = Resources.getResourceAsStream(config);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2,创建 sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//3,创建mapper
EmailMapper emailMapper = sqlSession.getMapper(EmailMapper.class);
//4,执行sql
Email email = emailMapper.selectById(1L);
第一步:加载创建sqlSessionFactory
SqlSessionFactoryBuilder是一个创建sqlSessionFactory的类,采用了构造器模式的,这个类职责很简单,就是new 出sqlSessionFactory,内部有多个build方法,所以这个类生命周期很短。
build方法核心在于parser.parse()方法,这个方法会去解析配置文件,解析mapper接口,xml文件,关联mapper与xml,解析sql,保存Configuration中。
所以加载这一步是在spring启动的时候就加载起来的。
第二步,获取SqlSession
从sqlSessionFactory中获取SqlSession,sqlSessionFactory采用了工厂模式,sqlSessionFactory有多个openSession方法,根据入参不同来区别。
sqlSessionFactory#openSession方法核心是会根据configuration获取一个executor对象,
executor对象就是后面真正执行sql的对象。
第三步 获取mapper
在第一步加载的时候,mybatis会把加载好的所有mapper和方法保存在Configuration中,所以
EmailMapper emailMapper = sqlSession.getMapper(EmailMapper.class);
是在Configuration中get出来的
第四步:执行SQL
4.1 寻找SQL
通过MapperMethodInvoker代理模式构造了一个MapperMethod对象,MapperMethod会有执行的SQL,入参,返回结果集等信息
4.2执行SQL
前面说到sqlSession会有一个executor对象,executor是真正执行SQL流程的对象,具体知识可以看:【MyBatis系列7】原来SqlSession只是个甩手掌柜,真正干活的却是Executor等四大对象_zwx900102的博客-CSDN博客
executor总结:解析SQL,拼装入参,最终转换成PreparedStatement,去执行JDBC的execute方法,返回结果集,再解析结果集转成dto对象映射。所以底层还是用java的jdbc执行。
当然怎么转成PreparedStatement,怎么映射结果集,具体还是很复杂的。
二:mybatis的缓存
具体解析请看:【MyBatis系列8】给我五分钟,带你彻底掌握MyBatis的缓存工作原理_zwx900102的博客-CSDN博客
我这只是做简单的总结,方便自己理解。
看源码
首先,从结构来看,缓存的核心是Cache接口,decorators包下面的类都是实现了Cache接口的,但同时每一种Cache类中都定义了Cache delegate对象:
private final Cache delegate
其实这里用到了设计模式中的装饰器模式,实际Cache的实现类只有一个PerpetualCache
PerpetualCache实际是用了一个map来做缓存。
一级缓存
一级缓存也叫本地缓存,在MyBatis中,一级缓存是在会话(SqlSession)层面实现的,这就说明一级缓存作用范围只能在同一个SqlSession中,跨SqlSession是无效的。
MyBatis中一级缓存是默认开启的,不需要任何配置。
一级缓存只对同一个SqlSession有效。
一级缓存原理
既然一级缓存是sqlsession的,我们来看sqlSession的源码,sqlSession只有这5个对象,看前面两个:
Configuration是全局的,Configuration类里只有关于mapper和xml,结果集等信息,没有关于缓存的信息,所以跳过。
Executor,前面说过Executor是实际去执行SQL的地方,所以缓存也是放在这最合适的
这里用到了Cache的实际实现类,所以我们才说一级缓存是sqlsession(一个会话)层面的。
具体使用缓存,大概无非就是执行查询前判断下缓存(CacheKey)有没有,有就用,没有则执行查询SQL,并缓存结果,
一级缓存什么时候会被清除
1、就是获取缓存之前会先进行判断用户是否配置了flushCache=true属性(参考一级缓存的创建代码截图),如果配置了则会清除一级缓存。
2、MyBatis全局配置属性localCacheScope配置为Statement时,那么完成一次查询就会清除缓存。
3、在执行commit,rollback,update方法时会清空一级缓存。
二级缓存
二级缓存目的就是要实现作用范围更广,那肯定是要实现跨会话共享的,在MyBatis中二级缓存的作用域是namespace,也就是作用范围是同一个命名空间
在MyBatis中为了实现二级缓存,专门用了一个Executor的装饰器来维护:CachingExecutor
/**
* 大致流程
* 1,创建一级缓存CacheKey
* 2,获取二级缓存
* 3,如果没有获取到二级缓存则执行Executor的query方法,会走一遍一级缓存流程
* 4,查询结果进行缓存
*/
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//获取二级缓存
Cache cache = ms.getCache();
//如果Mapper.xml没有开启二级缓存,cache则会是Null
if (cache != null) {
//判断select标签是否配置了flushCache属性
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
//二级缓存不能缓存输出类型参数,所以判断确保没有输出参数
ensureNoOutParams(ms, boundSql);
//从缓存中获取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果为空就直接去查
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//注意:先缓存到一个临时属性中,等事务提交后才真正保存到二级缓存中,目的就是防止脏读
//查到进行缓存,
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
二级缓存需要开启吗
既然一级缓存默认是开启的,而二级缓存是需要我们手动开启的,那么我们什么时候应该开启二级缓存呢?
1、因为所有的update操作(insert,delete,uptede)都会触发缓存的刷新,从而导致二级缓存失效,所以二级缓存适合在读多写少的场景中开启。
2、因为二级缓存针对的是同一个namespace,所以建议是在单表操作的Mapper中使用,或者是在相关表的Mapper文件中共享同一个缓存。
自定义缓存
mybatis支持自定义使用第三方作为缓存,比如redis等。
三 mybatis的日志
mybatis支持6种日志:SLF4J,LOG4J,LOG4J2,JDK_LOGGING,COMMONS_LOGGING,STDOUT_LOGGING,NO_LOGGING。
采用的设计模式是适配器模式
在Configuration类中,可以看到有注册所有类型的日志,然后再setLogImpl中用LogFactory来创建日志对应的适配器,LogFactory静态初始化了所有的类型
setImplementation()方法适配指定的日志类型。
四 mybatis分页插件
在MyBatis中插件式通过拦截器来实现的,真正执行Sql的是四大对象:Executor,StatementHandler,ParameterHandler,ResultSetHandler。而MyBatis的插件正是基于拦截这四大对象来实现的。需要注意的是,虽然我们可以拦截这四大对象,但是并不是这四大对象中的所有方法都能被拦截,下面就是官网提供的可拦截的对象和方法汇总:
mybatis拦截器插件源码解析
在SqlSessionFactory build的时候解析配置文件,这里有个解析插件的 ,
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//拿到interceptor
String interceptor = child.getStringAttribute("interceptor");
//拿到配置文件
Properties properties = child.getChildrenAsProperties();
//反射得到拦截器对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//把配置我呢见属性放在拦截器中
interceptorInstance.setProperties(properties);
//把拦截器保存到configuration中,这其实是一个List
configuration.addInterceptor(interceptorInstance);
}
}
}
可以看到 InterceptorChain是Configuration一个属性,InterceptorChain内部其实是一个List,就是说如果我们实现了多个拦截器,Mybatis是遍历list执行所有的拦截器的。
注意:
interceptor.plugin(target)这个方法里,其实是wrap()方法,这个方法最底层这里有用到动态代理哦,所以mybatis的插件是通过拦截器使用代理模式工作的。
PageHelper分页插件
也是用到拦截器,在执行sql前拦截并setPage,注意,这里有用到线程本地变量。
为什么PageHelper只对startPage后的第一条select语句有效
在finally内把ThreadLocal中的分页数据给清除掉了,所以只要执行一次查询语句就会清除分页信息,故而后面的select语句自然就无效了。
五 mybatis中的设计模式
学习mybatis源码还能学习设计模式:构造模式,工厂模式,代理模式
缓存中用到的模式:装饰器模式
日志用到的模式:适配器模式,工厂模式
插件(分页插件)用到模式:拦截器(aop),动态代理模式