MyBatis之缓存

本文详细介绍了MyBatis中的一级缓存和二级缓存机制。一级缓存作用于同一SqlSession,能提高重复查询的效率。二级缓存则在不同SqlSession间共享,需手动开启,并适用于访问频繁但实时性要求不高的查询场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        涉及到大量数据访问的问题,一般使用缓存是一个提高效率的方法。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.一级缓存
                         
        从上图一级缓存的原理图可以知道:一级缓存的作用域是sqlsession,因此其缓存区域是以sqlsession划分的。当执行一个sql语句时,先访问缓存区,如果缓存区有查询结果则从中获取,否则再访问底层数据库。MyBatis内部存储缓存使用一个HashMap,key为hashCode+sqlID+sql语句,value则是从查询出来映射生成的java对象。根据这个使用HashMap存储的原理,我们可以更清楚一级缓存是如何工作的。 sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域,直到下一次会话生成,则会再建立一个缓存区域。
        下图是该例子需要的简单的一个底层数据表:
                                                                
        现在通过以下程序进行检测,首先对同一个sqlsession会话之内进行操作,操作两次相同的查询,查询id为29的user对象。
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.二级缓存
        从上面的内容可以知道,一旦查询操作在sqlSession会话外,则一级缓存会失去作用,那么我们为了提高效率,需要使用二级缓存。下图是二级缓存的原理图:

                       

        二级缓存区域是根据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();
		

	}

}
        运行如下程序,得到下图结果,但是得到还不是同一个对象,那是因为没有开启二级缓存,因为二级缓存不像一级缓存默认开启的,需要手动开启。
                                    
        接下来进行二级缓存的开启。
1.首先在mybatis配置文件中进行全局配置:<setting name="cacheEnabled" value="true"/>,如下图所示:
                                         
2.需要mapper代理文件进行回应:即声明该代理文件对应的namespace开启二级缓存:<cache/>,如下图所示
                                         
3. 二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。比如本文查询结果是User类,则相应的pojo需要实现序列化和反序列化,即实现Serializable接口。
                                                     
进行测试,代码如下:

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等。


        

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值