Mybatis所用的模式和缓存
一.设计模式分析
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//创建工厂mybatis使用了构建者模式
SqlSessionFactory factory = builder.build(in);//builder就是构建者
//3.使用工厂生产SqlSession对象
SqlSession session1 = factory.openSession();//生产sqlSession使用了工厂模式,解耦
// SqlSession session2 = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
IUserDao userDao1 = session1.getMapper(IUserDao.class);//代理模式:不修改源码的基础下对已有的方法增强
// IUserDao userDao2 = session2.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao1.findAll();
for (User user : users) {
System.out.println(user);
}
1.建造者模式
概念解释:假如你现在要盖个厂子(汽车厂)。这个时候,如果自己去做,要考虑工人,盖楼所需要的材料,工具,施工车等等...,会比较麻烦。此时我们一般会把这些事情承包给包工队,让包工队去做这些事情,我们只需要告诉他们需求,最后给钱就可以了。
那么,上面的代码,SqlSessionFactoryBuilder 就是构建者,相当于包工队,它就负责去创建工厂, SqlSessionFactory factory = builder.build(in); 就使用到了构建者模式,你只需要把钱塞给这个builder,就可以造出你所要的工厂,省事又省力。
2.工厂模式
SqlSession session1 = factory.openSession();//生产sqlSession使用了工厂模式,解耦
概念解释:当你的厂子盖完后,你就可以拿厂子去生产汽车,如果自己去做,就要考虑到汽车所需要的零件,材料,工人等等,那么现在我不需要这些,我直接通过工厂模式来造车,过程我不管,你给我造出来就可以了,那么SqlSession就是我想要的车。
openSession()方法就是造车的方法,我不管你咋造的,你造出来可以用就行。
3.代理模式
概念解释:当你把车子造出来之后,我要开,我现在懒的连车都不想开,不想开咋办?找代驾,找个代驾帮我开。
IUserDao userDao1 = session1.getMapper(IUserDao.class);
这里就使用了代理模式。getmapper就是我找的代驾,
public interface IUserDao {
/**
* 查询所有操作
* @return
*/
List<User> findAll();
User findUserById(int id);
}
此时代驾要干的事情就是开车,开车是一个动作,即方法,我们把我们所有需要的执行动作放在这个接口里面。并且不需要自己创建对象,通过代理对象去执行方法,就可以完成了我们所需要的功能。
4.解释
为什么要生产代理对象去执行方法呢?为什么要使用代理模式?
假设你有一套房子要卖,一种方法是你直接去网上发布出售信息,然后直接带要买房子的人来看房子、过户等一直到房子卖出去,但是可能你很忙,你没有时间去处理这些事情,所以你可以去找中介,让中介帮你处理这些琐碎事情,中介实际上就是你的代理。本来是你要做的事情,现在中介帮助你一一处理,对于买方来说跟你直接交易跟同中介直接交易没有任何差异,买方甚至可能觉察不到你的存在,这实际上就是代理的一个最大好处。
接下来我们再深入考虑一下为什么你不直接买房子而需要中介?其实一个问题恰恰解答了什么时候该用代理模式的问题。
原因一:你可能在外地上班,买房子的人没法找到你直接交易。
对应到我们程序设计的时候就是:客户端无法直接操作实际对象。那么为什么无法直接操作?一种情况是你需要调用的对象在另外一台机器上,你需要跨越网络才能访问,如果让你直接去调用,你需要处理网络连接、处理打包、解包等等非常复杂的步骤,所以为了简化客户端的处理,我们使用代理模式,在客户端建立一个远程对象的代理,客户端就像调用本地对象一样调用该代理,再由代理去跟实际对象联系,对于客户端来说可能根本没有感觉到调用的东西在网络另外一端,这实际上就是Web Service的工作原理。另一种情况虽然你所要调用的对象就在本地,但是由于调用非常耗时,你怕影响你正常的操作,所以特意找个代理来处理这种耗时情况,一个最容易理解的就是Word里面装了很大一张图片,在word被打开的时候我们肯定要加载里面的内容一起打开,但是如果等加载完这个大图片再打开Word用户等得可能早已经跳脚了,所以我们可以为这个图片设置一个代理,让代理慢慢打开这个图片而不影响Word本来的打开的功能。申明一下我只是猜可能Word是这么做的,具体到底怎么做的,俺也不知道。
原因二:你不知道怎么办过户手续,或者说除了你现在会干的事情外,还需要做其他的事情才能达成目的。
对应到我们程序设计的时候就是:除了当前类能够提供的功能外,我们还需要补充一些其他功能。最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类,此时我们就可以做一个该类的代理类,要求所有请求必须通过该代理类,由该代理类做权限判断,如果安全则调用实际类的业务开始处理。可能有人说为什么我要多加个代理类?我只需要在原来类的方法里面加上权限过滤不就完了吗?在程序设计中有一个类的单一性原则问题,这个原则很简单,就是每个类的功能尽可能单一。为什么要单一,因为只有功能单一这个类被改动的可能性才会最小,就拿刚才的例子来说,如果你将权限判断放在当前类里面,当前这个类就既要负责自己本身业务逻辑、又要负责权限判断,那么就有两个导致该类变化的原因,现在如果权限规则一旦变化,这个类就必需得改,显然这不是一个好的设计。
二.缓存
1.一级缓存
mybatis提供查询缓存,如果缓存中有数据,就不用从数据库中获取,用于减轻数据压力,提高系统性能
一级缓存是sqlSession级别的缓存,在操作数据库的时候,需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession的缓存区域(HashMap)是互相不受影响的。
mybatis默认是支持一级缓存的。
1.1.证明一级缓存的存在:
验证方法:
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取UserMapper的代理类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//第一次查询
User user = userMapper.findUserById(10);
System.out.println(user);
//第二次查询
User user2 = userMapper.findUserById(10);
System.out.println(user2);
sqlSes
通过执行该方法,查看打印日志可以看出就执行了一次sql语句的查询,因此可以判断第二次查询是没有从数据库中进行查询的。
那当什么时候一级缓存会被清空呢?
通过测试发现,当在第二次查询数据库之前对数据库进行了写的操作后,第二次查询就不会从缓存中查询结果,而是直接从数据中查询结果。另一种就是在第一查询结果后,手动的方式清空一级缓存(使用sqlSession.clearCache();方法。)
测试方法:
2.二级缓存
二级缓存是Mapper级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是夸SqlSession的。(二级缓存Mybatis默认是关闭的,需要自己去手动配置开启或可以自己选择用哪个厂家的缓存来作为二级缓存)
2.1.开启二级缓存
首先在全局配置文件中配置开启二级缓存功能:
-
<!-- 开启二级缓存总开关 -->
-
<setting name="cacheEnabled" value="true"/>
在映射文件中去选择是否该映射文件使用二级缓存:
如果使用就进行以下配置,如果不是用,就不需要对映射文件做任何修改。
-
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
-
<cache></cache>
也可以设置选择二级缓存提工商
以下是我选择使用EhcacheCache缓存(注意:在使用EhcacheCache缓存需要导入jar包)
最后就是,在需要缓存的po类中去实现序列化:
。
验证二级缓存是否开启:
验证方法如下:
@Test
//测试二级缓存是否开启
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
//获取UserMapper的代理类
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(10);
System.out.println(user1);
//当sqlSession1执行close()的时候,才把sqlSession1查询的结果写到二级缓存中去。
sqlSession1.close();
// User u = new User();
// u.setUsername("小胖");
// u.setAddress("成都");
// u.setSex("2");
// u.setBirthday(new Date());
// userMapper3.insertUser(u);
// //当其中任何一个sqlSession执行commit()方法后将刷新整个缓存区
// sqlSession3.commit();
// sqlSession3.close();
//第二次查询
User user2 = userMapper2.findUserById(10);
System.out.println(user2);
sqlSession2.close();
}
测试结果:
同时通过测试,当在第二次查询的时候,向数据库提交数据后,就会对缓存进行刷新,这样在第二次查询的时候就没有走缓存,而是走的数据库。
2.2.禁用二级缓存
由于二级缓存默认是关闭的,如果不开启就不会使用二级缓存。如果,我们开启了二级缓存,而在有些查询结果集中,不需要受到二级缓存影响,该怎么去做呢?
在select查询中,默认是使用了二级缓存,如果不想使用二级缓存,就在select标签中有一个useCache的属性设置为false,就代表不使用二级缓存,每次进行查询数据都不会从缓存总获取,而是直接从数据库中进行查询。useCache的默认值是true,即代表statement使用二级缓存。
2.3.刷新二级缓存
在statement中设置flushCache=true,可以刷新二级缓存。默认情况下,select语句中的flushCache是false。如果是insert、update、delete语句,那么flushCache的默认值是true。如果将select语句中的flushCache值改为true,就意味着查询语句的二级缓存失效,每次查询都会从数据库进行查询。如果将select语句的flushCache值为false,就代表该查询语句使用了二级缓存,如果在数据库中修改了数据,而二级缓存中的数据还是原来的数据,那么这样就会出现脏读。
flushCache设置如下: