我们可以将一些变动不大且访问频率高的数据,放置在一个缓存容器中,用户下一次查询时就从缓存容器中获取结果。

一级查询缓存

一级缓存是基于同一个SqlSession类的实例对象的。不同的SqlSession类的实例对象缓存的数据区域是互不影响的。MyBatis默认支持一级缓存。

一级缓存测试示例

例如,使用同一个sqlSession对象,对id为1的用户查询两次:

@Test  
public void testFindCustomerCache1() throws Exception{  
    SqlSession sqlSession=dataConnection.getSqlSession();  
  
    //调用userMapper的方法  
    User user1=  
            sqlSession.selectOne("test.findUserById",1);  
    System.out.println("用户姓名:"+user1.getUsername());  
  
    User user2=  
            sqlSession.selectOne("test.findUserById",1);  
    System.out.println("用户姓名:"+user2.getUsername());  
    sqlSession.close();  
}

查看运行结果:
image.png
从图中可以看出第一次查询id为1的用户信息时,首先打开数据库连接,建立数据库连接,然后预编译SQL语句,设置输入参数,最后从数据库中查询了id为1的用户的信息。而第二次查询id为1的用户时,发现没有任何日志输出,仅仅打印了取出数据时的信息,这说明第二次数据不是从数据库查询出来的,是从一级缓存中获取的。

在第二次查询id为1的用户的信息之前,修改用户的一个属性:

@Test  
public void testFindCustomerCache2() throws Exception{  
    SqlSession sqlSession=dataConnection.getSqlSession();  
  
    //调用userMapper的方法  
  User user1=  
            sqlSession.selectOne("test.findUserById",1);  
    System.out.println("用户姓名:"+user1.getUsername());  
  
    String username = "章却";  
    user1.setUsername(username);  
    System.out.println("修改用户名为:"+username);  
    sqlSession.update("test" +  
            ".updateUserName",user1);  
    sqlSession.commit();  
  
    User user2=  
            sqlSession.selectOne("test.findUserById",1);  
    System.out.println("用户姓名:"+user2.getUsername());  
    sqlSession.close();  
}

测试结果:
image.png
从中可以看出在查询缓存数据之前,如果发生了增、删、改等改变数据的操作,SqlSession类都会清空其一级缓存。

二级查询缓存

如果出现无法使用一级缓存的时候,可以使用二级缓存。
二级缓存是Mapper级别的缓存,多个SqlSession类的实例对象操作同一个Mapper配置文件中的SQL语句,多个SqlSession类的实例对象可以共用二级缓存,二级缓存是以namespace为单位的,不同namespace下的操作互不影响。

二级缓存测试实例

首先开启二级缓存需要两步操作,第一步在MyBatis的全局配置文件添加:

<setting name="cacheEnabled" value="true" />

然后要在需要开启二级缓存的具体mapper.xml文件中开启二级缓存:

<cache/>

将要查询的客户信息的Java实体类序列化接口:

package cn.com.mybatis.po;  
  
import java.io.Serializable;  
import java.util.List;  
 
//Serializable接口没有方法或字段,仅用于标识可序列化的语义
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 void testFindCustomerOnMapper1() throws Exception{  
      SqlSession sqlSession =  
            dataConnection.getSqlSession();  
    //获取Mapper代理  
  CustomerMapper customerMapper1 = sqlSession.getMapper(CustomerMapper.class);  
    //执行Mapper代理对象的查询方法  
  Customer customer1 =  
            customerMapper1.findCustomerById(1);  
    System.out.println("用户姓名:"+customer1.getUsername() + "|" + "卡号:"+customer1.getAcno());  
  
    //获取Mapper代理  
  CustomerMapper customerMapper2 = sqlSession.getMapper(CustomerMapper.class);  
    //执行Mapper代理对象的查询方法  
  Customer customer2 =  
            customerMapper2.findCustomerById(1);  
    System.out.println("用户姓名:"+customer2.getUsername() + "|" + "卡号:"+customer2.getAcno());  
  
}

运行测试结果:
image.png

验证二级缓存清空

当某个Mapper执行了增、删、改、查的操作时,二级缓存会被清空,以防止数据脏读。

@Test  
public void testFindCustomerOnMapper2() throws Exception{  
    SqlSession sqlSession =  
            dataConnection.getSqlSession();  
    //获取Mapper代理  
  CustomerMapper customerMapper1 = sqlSession.getMapper(CustomerMapper.class);  
    //执行Mapper代理对象的查询方法  
  Customer customer1 =  
            customerMapper1.findCustomerById(1);  
    System.out.println("用户姓名:"+customer1.getUsername() + "|" + "卡号:"+customer1.getAcno());  
  
    CustomerMapper customerMapper3 = sqlSession.getMapper(CustomerMapper.class);  
    String AcNo = "3228286666666";  
    customer1.setAcno(AcNo);  
    customerMapper3.UpdateCustomerAcNo(customer1);  
    System.out.println("修改用户姓名:" + customer1.getUsername() + "|" +"的卡号为:"+customer1.getAcno());  
    sqlSession.commit();  
  
  
    //获取Mapper代理  
  CustomerMapper customerMapper2 = sqlSession.getMapper(CustomerMapper.class);  
    //执行Mapper代理对象的查询方法  
  Customer customer2 =  
            customerMapper2.findCustomerById(1);  
    System.out.println("用户姓名:"+customer2.getUsername() + "|" + "卡号:"+customer2.getAcno());  
    sqlSession.close();  
}

运行测试结果:
image.png

需要注意,在使用二级缓存时,需要保证该Mapper下的数据不会在其他Mapper.xml文件中有缓存。比如,在UserMapper.xml中有很多针对user表的操作,但是在一个xxxMapper.xml中,还有针对user单表的操作。这会导致user在两个命名空间下的缓存数据不一致。
如果无法避免这种情况的发生,且又需要使用二级缓存,建议使用拦截器判断执行的SQL涉及哪些表,然后把相关表的缓存清空。


WinRT
24 声望4 粉丝

临渊羡鱼,不如退而结网