Mybatis缓存结构

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xhf852963/article/details/81912781

      有时查询数据库的频率非常高,是很耗费数据库资源的,影响客户操作体验;我们可以将一些变动不大且访问频率较高的数据,放置在一个缓存容器中,用户下一次查询的时候就从缓存容器中获取结果。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设计哪些表,然后把相关表的缓存清空。

猜你喜欢

转载自blog.csdn.net/xhf852963/article/details/81912781