有时查询数据库的频率非常高,是很耗费数据库资源的,影响客户操作体验;我们可以将一些变动不大且访问频率较高的数据,放置在一个缓存容器中,用户下一次查询的时候就从缓存容器中获取结果。MyBatis拥有自己的缓存结构。可以用来缓解数据库压力。
Mybatis提供一级缓存和二级缓存的机制。
一级缓存是SqlSession级别的缓存。在操作数据库时,每个SqlSession类的实例对象中都有一个数据结构(HashMap)可以用于存储缓存数据。不同的SqlSession类的实例对象的数据区域(HashMap)是互不影响的。
二级缓存是Mapper级别的缓存,多个SqlSession类的实例对象操作同一个Mapper配置文件中的SQL语句,多个SqlSession类的实例对象可以共用二级缓存,二级缓存是跨SqlSession的。
一级查询缓存
一级查询缓存存在于每一个SqlSession类的实例对象中,当第一次查询某一个数据时,SqlSession类的实例对象会将该数据存入一级缓存区域,在没有收到改变该数据的请求之前,用户再次查询该数据,都会从缓存中获取该数据,而不是再次连接数据库进行查询。
工作原理:假设要查询一条数据,当第一次查询id为1的用户信息时,sqlSession首先到一级缓存区域查询,如果没有相关数据,则从数据库查询。然后sqlSession将该查询结果保存到一级缓存区域。在下一次查询时,如果sqlSession执行了commit操作(即执行了修改、添加和删除),则会清空它的一级缓存区域,以此来保证缓存中的信息就是最新的,避免出现脏读现象。如果在这期间sqlSession一直没有执行commit操作修改数据,那么下一次查询id为1的用户信息时,sqlSeesion在一级缓存中就会发现该信息,然后从缓存中获取用户信息。MyBatis默认支持一级缓存
二级查询缓存
有些时候在web工程中会将查询操作的方法封装在某个Service方法中,当查询完一次后,Service方法结束,此时SqlSession类的实例对象就会关闭,一级缓存就会被清空。此时若再次调用Service方法查询同一个信息,新打开一个SqlSession类的实例对象,由于一级缓存是空的,因而无法从缓存中获取信息。
工作原理:二级缓存存在于Mapper实例中,当多个SqlSession类的实例对象加载相同的Mapper文件,并执行其中的SQL配置时,他们就共享一个Mapper缓存。与一级缓存类似,当SqlSession类的实例对象加载Mapper进行查询时,会先去Mapper的缓存区域寻找该值,若不存在,则去数据库查询,然后将查询出来的结果存储到缓存区域,待下次查询相同数据时从缓存区域中获取。当某个SqlSession类的实例对象执行了增,删,改,查等改变数据的操作时,Mapper实例都会清空其二级缓存。
注意事项:一个Mapper有一个自己的二级缓存区域(按照namespace划分),两个Mapper的namespace如果相同,那么这两个Mapper执行的SQL查询会被缓存在同一个二级缓存中。要开启二级缓存,需要进行两步操作。
第一步:在SqlMapConfig.xml中配置setting属性,设置名为"cacheEnabled"的属性值为"true"即可。
<setting name="cacheEnabled" value="true"/>
第二步:由于二级缓存是Mapper级别的,还需要开启二级缓存的具体mapper.xml文件中开启二级缓存,语法很简单,只需要在相应的mapper.xml中添加一个cache标签即可:
<!--开启本Mapper的namespace下的二级缓存-->
<cache />
编写测试实例:
public class Customer implements Serializable{
private int cus_id;
private String username;
private String acno;
private String gender;
private String phone;
private List<Batch> batchList;
//set,get
}
Customer类实现序列化接口的原因是,为了将缓存数据取出来执行反序列化操作,因为二级缓存数据存储介质多种多样(如内存,硬盘,服务器),不一顶都在内存中。
编写Test
public class TestDynatimic {
public DataConnection dataCon = new DataConnection();
@Test
public void TestFindCustomerAndBatchById() throws Exception{
SqlSession sqlSession = dataCon.getSqlSession();
//获取Mapper代理
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
//执行Mapper代理对象的查询方法
Customer customer = customerMapper.findCustomerById(1);
System.out.println("用户姓名:"+customer.getUsername()+" 卡号为"+customer.getAcno());
Customer customer2 = customerMapper.findCustomerById(1);
System.out.println("用户姓名:"+customer2.getUsername()+" 卡号为"+customer2.getAcno());
sqlSession.close();
}
}
输出结果为:
可以看到,第一次查询时,加载了数据库中的信息(获取连接,预编译SQL,获取查询结果)。而第二次查询时,控制台直接输出了用户信息,这说明该用户信息不是从数据库查的,而是从Mapper的二级缓存中获取的。
小贴士:虽然Mapper对象各不相同,但是加载了同一个Mapper配置文件,所以他们就会共享一个Mapper级别的缓存。
验证二级缓存清空:当一个Mapper执行了增,删,改,查的操作时,二级缓存会被清空,防止脏读,测试代码如下:
public class TestDynatimic {
public DataConnection dataCon = new DataConnection();
@Test
public void TestFindCustomerAndBatchById() throws Exception{
SqlSession sqlSession = dataCon.getSqlSession();
//获取Mapper代理
CustomerMapper customerMapper1 = sqlSession.getMapper(CustomerMapper.class);
//执行Mapper代理对象的查询方法
Customer customer1 = customerMapper1.findCustomerById(1);
System.out.println("用户姓名:"+customer1.getUsername()+" 卡号为"+customer1.getAcno());
CustomerMapper customerMapper2 = sqlSession.getMapper(CustomerMapper.class);
String acno="8888888888";
customer1.setAcno(acno);
customerMapper2.updateCustomerAcNo(customer1);
System.out.println("修改用户姓名:"+customer1.getUsername()+"的卡号为:"+customer1.getAcno());
CustomerMapper customerMapper3 = sqlSession.getMapper(CustomerMapper.class);
Customer customer3 = customerMapper3.findCustomerById(1);
System.out.println("用户姓名:"+customer3.getUsername()+" 卡号为"+customer3.getAcno());
sqlSession.close();
}
}
输出结果为:
可以看到,Mapper代理对象在一次和第三次查询的时候都从数据库获取了数据,这说明第二次操作设计增,删,改的其中一种,并且执行了commit方法。为了避免数据脏读,MyBatis清空了二级缓存,所以第二次从二级缓存中拿不到此数据。
综上所述,二级缓存有一下的特点:
第一:缓存是以namespace为单位的,不同namespace下的操作互不影响。
第二:增,删,改操作会清空namespace下的全部缓存。
第三:通常使用Mybatis Gnerator(逆向工程)生成的代码中,各个表都是独立的,每个表都有自己的namespace
注意:使用二级缓存时需要谨慎,有时候不同namespace下的SQL配置中可能缓存了相同的数据。例如在UserMapper.xml中有很多针对user表的操作。但是在一个xxxMapper.xml中,还有针对user单表的操作。这回导致user在两个命名空间下的数据不一致。如果在UserMapper.xml中做了刷新的操作,在XXXMapper.xml中缓存仍然有效;如果有针对user的单表查询,使用缓存可能得到不正确的结果。所以在使用二级缓存时,需要保证该Mapper下的数据不会在其他Mapper.xml文件中有缓存。如果无法避免这种情况的发生,且又需要使用二级缓存,建议使用拦截器判断执行的SQL设计哪些表,然后把相关表的缓存清空。