涉及到大量数据访问的问题,一般使用缓存是一个提高效率的方法。MyBatis中其实一直都默认存在着一级缓存,即每个SqlSession会话之内都开启了一级缓存,即执行相同的mapper文件内的语句(即执行相同的sql语句),并且传入的参数也相同,其中第一次执行则会从数据库底层获取数据并缓存,后面执行则不再从底层数据库获取,而是从缓存区直接获取。而一级缓存并不能满足我们所有需求,比如在不同SqlSession会话中执行相同的查询操作,则一级缓存无法使用,还是会从访问数据库底层,此时,就需要开启二级缓存。具体内容下文进行详细介绍。
1.缓存介绍
如下图所示,是MyBatis一级缓存和二级缓存的关系以及各自的工作范围。
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写进缓存,当第二次传入相同参数时就不再需要访问数据库底层,而是直接从缓存中获取。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
以下给出两种缓存各自的实例,通过例子进行比较会有更清晰的认识。为避免文章篇幅赘余,本文所举的例子是使用的是之前写过的MyBatis简单开发一文中的例子。
1.一级缓存
package com.mybatis.test;
import java.util.*;
import java.io.InputStream;
import org.apache.ibatis.io.*;
import org.apache.ibatis.session.*;
import com.mybatis.mapper.UserMapper;
import com.mybatis.model.impl.*;;
public class MyBatisTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//读取MyBatis配置文件
InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,创建SqlSessionFactory类的实例。
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//创建Session实例
SqlSession session=sqlSessionFactory.openSession();
//创建User对象
UserMapper userMapper=session.getMapper(UserMapper.class);
//User user=new User("Carson","男",23);
//userMapper.insertUser(user);
//user=new User("THL","女",23);
//userMapper.insertUser(user);
//session.commit();
//List<User> list=userMapper.selectAllUser();
//System.out.println(list);
//userMapper.deleteUser();
User user1=userMapper.selectOneUser(29);
User user2=userMapper.selectOneUser(29);
session.commit();
//System.out.println(user1==user2);
//SqlSession session1=sqlSessionFactory.openSession();
//UserMapper userMapper1=session.getMapper(UserMapper.class);
//User user3=userMapper.selectOneUser(29);
//System.out.println(user1==user3);
session.close();
//session1.close();
}
}
运行上述程序,得到如下结果,可以看出同一个sqlsession中,执行相同的两个相同的查询操作,得到两个是相同的对象,说明第二次不是从低层数据库查询得到的,而是从缓存区域中获取的。
接下来,观察两个sqlSession中的情况:
package com.mybatis.test;
import java.util.*;
import java.io.InputStream;
import org.apache.ibatis.io.*;
import org.apache.ibatis.session.*;
import com.mybatis.mapper.UserMapper;
import com.mybatis.model.impl.*;;
public class MyBatisTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//读取MyBatis配置文件
InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,创建SqlSessionFactory类的实例。
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//创建Session实例
SqlSession session=sqlSessionFactory.openSession();
//创建User对象
UserMapper userMapper=session.getMapper(UserMapper.class);
//User user=new User("Carson","男",23);
//userMapper.insertUser(user);
//user=new User("THL","女",23);
//userMapper.insertUser(user);
//session.commit();
//List<User> list=userMapper.selectAllUser();
//System.out.println(list);
//userMapper.deleteUser();
User user1=userMapper.selectOneUser(29);
User user2=userMapper.selectOneUser(29);
session.commit();
//System.out.println(user1==user2);
SqlSession session1=sqlSessionFactory.openSession();
//UserMapper userMapper1=session.getMapper(UserMapper.class);
User user3=userMapper.selectOneUser(29);
System.out.println(user1==user3);
session.commit();
session.close();
//session1.close();
}
}
运行程序,得到如下结果,可以知道在两个不同的会话中执行两个相同的sql语句,两个对象并不相等,说明不同会话之中的查询操作,不在一级缓存的作用范围之内。
2.二级缓存
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同(有时可以用一个mapper.xml文件代理多个mapper接口),此时可以理解为二级缓存区域是根据mapper划分。每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。与一级缓存相同的是MyBatis内部存储缓存使用一个HashMap,key为hashCode+sqlID+sql语句,value则是从查询出来映射生成的java对象。sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域,直到下一次会话生成,则会再建立一个缓存区域。
以下代码则表示了两个是在不同sqlSession内但是在同一个namespace内的操作。
package com.mybatis.test;
import java.util.*;
import java.io.InputStream;
import org.apache.ibatis.io.*;
import org.apache.ibatis.session.*;
import com.mybatis.mapper.UserMapper;
import com.mybatis.model.impl.*;;
public class MyBatisTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//读取MyBatis配置文件
InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,创建SqlSessionFactory类的实例。
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//创建Session实例
SqlSession session=sqlSessionFactory.openSession();
//创建User对象
UserMapper userMapper=session.getMapper(UserMapper.class);
//User user=new User("Carson","男",23);
//userMapper.insertUser(user);
//user=new User("THL","女",23);
//userMapper.insertUser(user);
//session.commit();
//List<User> list=userMapper.selectAllUser();
//System.out.println(list);
//userMapper.deleteUser();
User user1=userMapper.selectOneUser(29);
User user2=userMapper.selectOneUser(29);
//session.commit();
session.close();
//System.out.println(user1==user2);
SqlSession session1=sqlSessionFactory.openSession();
UserMapper userMapper1=session1.getMapper(UserMapper.class);
User user3=userMapper1.selectOneUser(29);
System.out.println(user1==user3);
session1.close();
}
}
运行如下程序,得到下图结果,但是得到还不是同一个对象,那是因为没有开启二级缓存,因为二级缓存不像一级缓存默认开启的,需要手动开启。
package com.mybatis.test;
import java.util.*;
import java.io.InputStream;
import org.apache.ibatis.io.*;
import org.apache.ibatis.session.*;
import com.mybatis.mapper.UserMapper;
import com.mybatis.model.impl.*;;
public class MyBatisTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
//读取MyBatis配置文件
InputStream inputStream=Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,创建SqlSessionFactory类的实例。
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//创建Session实例
SqlSession session=sqlSessionFactory.openSession();
//创建User对象
UserMapper userMapper=session.getMapper(UserMapper.class);
//User user=new User("Carson","男",23);
//userMapper.insertUser(user);
//user=new User("THL","女",23);
//userMapper.insertUser(user);
//session.commit();
//List<User> list=userMapper.selectAllUser();
//System.out.println(list);
//userMapper.deleteUser();
long e1 =System.currentTimeMillis();
User user1=userMapper.selectOneUser(29);
//User user2=userMapper.selectOneUser(29);
//session.commit();
System.out.println(user1);
long e2 =System.currentTimeMillis();
System.out.println("第一次查询时间:"+(e2-e1));
session.close();
//System.out.println(user1==user2);
session=sqlSessionFactory.openSession();
long e3 =System.currentTimeMillis();
UserMapper userMapper1=session.getMapper(UserMapper.class);
User user3=userMapper1.selectOneUser(29);
//System.out.println(user1==user3);
System.out.println(user3);
long e4 =System.currentTimeMillis();
System.out.println("第二次查询时间:"+(e4-e3));
session.close();
}
}
测试结果如下:
首先比较两个Cache Hit Ratio即缓存命中率:第一个为0.0,说明没有从缓存区获取数据,而是从低层获取的;第二个为0.5,说明第一个没从缓存区获取,但第二个是从缓存区获取的,总的命中率为0.5。然后观察两次查询时间,第一次查询时间比第二次查询时间多一个数量级。
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
适用场景:
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。