MyBatis框架核心之(八)Mybatis一级缓存与二级缓存以及自定义缓存

2018-02-27 11:53:12来源:oschina作者:bugwfq人点击

分享
五、Mybati缓存(一级缓存与二级缓存)
一、一级缓存

1.什么是一级缓存


一级缓存是SqlSession级别的缓存,是基于PerpetualCache的HashMap本地缓存。在操作数据库时需要构造sqlSession对象,在对象中有个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间缓存数据区域 (HashMap)是互不影响的。


2.作用域


一级缓存的作用域是同一个SqlSession在同一个SqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据(前提是同一个session),将不再从数据库查询,从而提高查询效率。


(Mybatis默认开启一级缓存)


注意:


当 Session flush ,commit(执行插入、更新、删除),或 close 之后,该Session中的所有 Cache 就将清空。

代码实现(详细的代码会放在最后)



一级缓存的简单测试




//@Test

publicvoid firstGradeCache(){

//获取一个session会话

SqlSession session = getSession();

//获取一个执行sql语句的映射接口 A

MybatisCache firstGradeCacheA =session.getMapper(MybatisCache.class);

//获取一个执行sql语句的映射接口 B

MybatisCache firstGradeCacheB =session.getMapper(MybatisCache.class);

//通过A和B执行同一条sql

Student studentA =firstGradeCacheA.queryStudentById(1);

Student studentB =firstGradeCacheB.queryStudentById(1);

//通过log4j发现sql语句执行了一次 ,并且两个对象相同。说明是第一次查询存入到内存里面的对象

System.out.println(studentA==studentB);

}




缓存数据更新机制



当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。




//@Test

publicvoid firstGradeCacheCommit(){

//获取一个session会话

SqlSession session = getSession();

//获取一个执行sql语句的映射接口 A

MybatisCache firstGradeCacheA =session.getMapper(MybatisCache.class);

//获取一个执行sql语句的映射接口 update

MybatisCache update=session.getMapper(MybatisCache.class);

//获取一个执行sql语句的映射接口 A

MybatisCache firstGradeCacheB =session.getMapper(MybatisCache.class);

//让A先查询并获取实体1

Student studentA =firstGradeCacheA.queryStudentById(1);

//创建一个修改的数据

Student updateStudent = new Student(1,"wahaha",2);

//然后在用更新的执行接口修改一下数据

update.updateStudent(updateStudent);

//然后提交一下会话

session.commit();

//再查一下相同的用户

Student studentB =firstGradeCacheB.queryStudentById(1);

//会发现sql语句执行了两次,而且返回的对象也不是同一个地址了

System.out.println(studentA==studentB);

}



二、二级缓存

1.什么是二级缓存


二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。


2. 作用域


二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace


不同的sqlSession多次执行相同namespace下的Sql语句第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。


二级缓存是跨SqlSession的


重点:因为mybatis默认一级缓存一直存在(为了数据的安全性考虑),所以说只有等session操作完成,并且close,或commit该session后(建议每次查询都记得关闭会话,commit使用不当的话会将所有缓存清空),查到的值才会存入到缓存中,供其他session使用


3.启用二级缓存

1).在核心配置文件Mybatis.xml中配置标签


(默认是开启的但是必须配置cache标签才能使用所以可以不配置)



属性


描述


允许值


默认值




cacheEnable


对此配置文件下的所有cache进行全局性开关设置


true/false


true




Mybatis.xml文件配置







2).在Mapper.xml中开启二级缓存,当该namespace下的 sql执行完成会存储到它的缓存区域


(在mapper下第一行配置)



mapper.xml 文件配置




namespace="">

.........




标签中的属性简介


1. eviction 回收策略 (默认为 LRU)


【默认】LRU——最近最少使用的:移除最长时间不被使用的对象


【新】LFU——最近一段时间使用次数最少的(部分版本不能使用)


FIFO——先进先出的:按对象进入缓存的顺序来移除他们


SOFT——软引用:移除基于垃圾回收器状态和软引用规则的对象


WEAK——弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。


2. flushInterval (刷新间隔)


可以被设置为任意的正整数(60*60*1000这种形式是不允许的),而且它们代表一个合理的毫秒形式的时间段。


默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。


3.size (引用数目)


可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024.


4.readOnly (只读)


属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过发序列化)。这会慢一些,但是安全,因此默认是false。


5.type 自定义缓存的类全类名


自定义缓存需要实现cache接口,并且重写其中的方法,就可以使用第三方缓存了


代码实现



不使用提交,关闭和使用提交关闭




@Test

publicvoid secondGradeCacheNoCommitAndClose(){

//获取一个session工厂

SqlSessionFactory factory = getSqlSessionFactory();

//获取两个不同的会话

SqlSession sessionA =factory.openSession();

SqlSession sessionB =factory.openSession();

//执行接口

MybatisCache firstGradeCacheA =sessionA.getMapper(MybatisCache.class);

MybatisCache firstGradeCacheB=sessionB.getMapper(MybatisCache.class);

//查询id为1的学生

Student studentA=firstGradeCacheA.queryStudentById(1);

Student studentB=firstGradeCacheB.queryStudentById(1);

//比较两个结果相同(如果是使用了序列化和反序列化则不相同)

System.out.println(studentA==studentB);

//如果不提交不关闭则数据一直在一级缓存中,会发起两个sql语句

}




使用提交将数据存入二级缓存(注意如果是C/U/D操作后提交则会清空所有缓存)




@Test

publicvoid secondGradeCacheByCommit(){

//获取一个session工厂

SqlSessionFactory factory = getSqlSessionFactory();

//获取两个不同的会话

SqlSession sessionA =factory.openSession();

SqlSession sessionB =factory.openSession();

//执行接口

MybatisCache firstGradeCacheA =sessionA.getMapper(MybatisCache.class);

MybatisCache firstGradeCacheB=sessionB.getMapper(MybatisCache.class);

//查询id为1的学生

Student studentA=firstGradeCacheA.queryStudentById(1);

//关闭A会话,将一级缓存清空,存入二级缓存

sessionA.commit();

Student studentB=firstGradeCacheB.queryStudentById(1);

//比较两个结果相同(如果是使用了序列化和反序列化则不相同)

System.out.println(studentA==studentB);

//通过log4j显示的运行结果可以看出语句值查询了一次

}




使用关闭将数据存入二级缓存




@Test

publicvoid secondGradeCacheClose(){

//获取一个session工厂

SqlSessionFactory factory = getSqlSessionFactory();

//获取两个不同的会话

SqlSession sessionA =factory.openSession();

SqlSession sessionB =factory.openSession();

//执行接口

MybatisCache firstGradeCacheA =sessionA.getMapper(MybatisCache.class);

MybatisCache firstGradeCacheB=sessionB.getMapper(MybatisCache.class);

//查询id为1的学生

Student studentA=firstGradeCacheA.queryStudentById(1);

//关闭A会话,将一级缓存清空,存入二级缓存

sessionA.close();

Student studentB=firstGradeCacheB.queryStudentById(1);

//比较两个结果相同(如果是使用了序列化和反序列化则不相同)

System.out.println(studentA==studentB);

//通过log4j显示的运行结果可以看出语句值查询了一次

}



三、自定义缓存的实现

(下面是mybatis使用Redis数据库作为自定义缓存的实例)


自定义缓存必须实现Cache接口,并且重写其中的方法才能使用



自定义缓存的实现




package cn.et.fuqiang.cache.xml;

import java.io.IOException;

import java.util.concurrent.locks.ReadWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.ibatis.cache.Cache;

import redis.clients.jedis.Jedis;

public class RedisCache implements Cache {

/**

* 二级缓存是针对namespace的所以该id其实就是namespace

*/

private String id;

/**

* 实现的是第三方缓存 redis

* 使用Jedis

*/

private Jedis jedis;

/**

* 定义构造器,当容器自动调用时会实例化并传入对应的值

* @param id

*/

public RedisCache(final String id){

this.id=id;

init();

}

/**

* 初始化的连接方法

*/

public void init(){

jedis = new Jedis("localhost",6379);

}

/**

* 返回namespace

*/

public String getId() {

return id;

}

/**

* 将值存入redis中

* 方法中使用了自定义的序列化方法,使用set(byte[] key,byte[] value)存入

*/

public void putObject(Object key, Object value) {

if(key==null || value==null){

return;

}

try {

//将值存入

jedis.set(SerializationUtil.objectToByte(key), SerializationUtil.objectToByte(value));

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

/**

* 从redis中查找值

* 方法中使用了自定义的反序列化方法,使用get(byte[] key)将值去除

*/

public Object getObject(Object key) {

if(key!=null){

try {

byte[] value = jedis.get(SerializationUtil.objectToByte(key));

if(value==null){

return null;

}

return SerializationUtil.byteToObject(value);

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

return null;

}

/**

* 用于从缓存中删除指定的键值

*/

public Object removeObject(Object key) {

if(key!=null){

try {

Object value = getObject(key);

jedis.del(SerializationUtil.objectToByte(key));

return value;

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

return null;

}

/**

* 清空所有缓存的方法

*/

public void clear() {

jedis.flushAll();

}

public int getSize() {

return 0;

}

/**

* 直接返回 ReadWriteLock的子类ReentrantReadWriteLock()实体

*/

public ReadWriteLock getReadWriteLock() {

return new ReentrantReadWriteLock();

}

}



工具类SerializationUtil的代码



SerializationUtil 类




package cn.et.fuqiang.cache.xml;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

public class SerializationUtil {

/**

* 序列化

* @param object

* @return

* @throws IOException

*/

public static byte[] objectToByte(Object object) throws IOException{

/*

* 使用一个byte数组输出流将数据以byte数组写入到内存中 使用out.toByteArray()将数组返回

*/

ByteArrayOutputStream out = new ByteArrayOutputStream();

ObjectOutputStream obj = new ObjectOutputStream(out);

obj.writeObject(object);

return out.toByteArray();

}

/**

* 反序列化

* @param data

* @return

* @throws IOException

* @throws ClassNotFoundException

*/

public static Object byteToObject(byte[] data) throws IOException, ClassNotFoundException{

/*

* 使用一个byte数组读取流 ,将数组读入到ByteArrayInputStream流中最后反序列化出去

*/

ByteArrayInputStream inputStream = new ByteArrayInputStream(data);

ObjectInputStream obj = new ObjectInputStream(inputStream);

return obj.readObject();

}

}




总结:一级缓存和二级缓存独有缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有select 中的缓存将被clear。

以上代码实例所有代码如下


1.Mybatis主配置文件



mybatis.xml




<?xmlversion="1.0"encoding="UTF-8"?>

"cn/et/fuqiang/cache/jdbc.properties">

"cacheEnabled" value="true"/>

"cn.et.fuqiang.cache.entity"/>

"development">

"development">

"JDBC" />

"POOLED">

"driver" value="${driverClass}" />

"url" value="${url}" />

"username" value="${user}" />

"password" value="${password}" />

"cn/et/fuqiang/cache/xml/StudentMapper.xml"/>




2.接口映射的接口定义



package cn.et.fuqiang.cache.xml;

import cn.et.fuqiang.cache.entity.Student;

publicinterface MybatisCache {

public Student queryStudentById(Integer stuid);

publicvoid updateStudent(Student student);

}




3. 接口对应的mapper配置文件



StudentMapper.xml




<?xmlversion="1.0"encoding="UTF-8"?>

"cn.et.fuqiang.cache.xml.MybatisCache">

"FIFO"flushInterval="10800000"size="512"readOnly="true"type="cn.et.fuqiang.cache.xml.RedisCache">

"updateStudent" >

update student set stuname=#{stuname},gid=#{gid} where stuid=#{stuid}


4. 实体类



Student 实体类




package cn.et.fuqiang.cache.entity;

import java.io.Serializable;

publicclass Student implements Serializable {

/**

*

*/

privatestatic finallong serialVersionUID = 1L;

private Integerstuid;//学生id

private Stringstuname;//学生姓名

private Integergid;//班级id

public Student() {}

public Student(Integer stuid, String stuname, Integer gid) {

super();

this.stuid = stuid;

this.stuname = stuname;

this.gid = gid;

}

public Integer getStuid() {

returnstuid;

}

publicvoid setStuid(Integer stuid) {

this.stuid = stuid;

}

public String getStuname() {

returnstuname;

}

publicvoid setStuname(String stuname) {

this.stuname = stuname;

}

public Integer getGid() {

returngid;

}

publicvoid setGid(Integer gid) {

this.gid = gid;

}

}



5. 测试运行的代码



MybatisCacheTest




package cn.et.fuqiang.cache.xml;

import java.io.InputStream;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import org.junit.Test;

import cn.et.fuqiang.cache.entity.Student;

public class MybatisCacheTest {

public SqlSession getSession(){

//mybatis的配置文件

String resource = "mybatis.xml";

//使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)

InputStream is = MybatisCacheTest.class.getResourceAsStream(resource);

//构建sqlSession的工厂

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

//使用MyBatis提供的Resources类加载mybatis的配置文件(它也加载关联的映射文件)

//Reader reader = Resources.getResourceAsReader(resource);

//构建sqlSession的工厂

//SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

//创建能执行映射文件中sql的sqlSession

return sessionFactory.openSession();

}

public SqlSessionFactory getSqlSessionFactory(){

//mybatis的配置文件

String resource = "mybatis.xml";

//使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)

InputStream is = MybatisCacheTest.class.getResourceAsStream(resource);

//构建sqlSession的工厂

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

//使用MyBatis提供的Resources类加载mybatis的配置文件(它也加载关联的映射文件)

//Reader reader = Resources.getResourceAsReader(resource);

//构建sqlSession的工厂

//SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

//创建能执行映射文件中sql的sqlSession

return sessionFactory;

}

//@Test

public void firstGradeCache(){

/*

一级缓存

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,

在对象中有个(内存区域)数据结构(HashMap)用于存储缓存数据。

不同的SqlSession之间缓存数据区域 (HashMap)是互不影响的。

作用域

一级缓存的作用域是同一个SqlSession在同一个SqlSession中两次执行相同的sql语句

第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,

将不再从数据库查询,从而提高查询效率。当一个SqlSession结束后该SqlSession

结束后该SqlSession中的一级缓存也就不存在了。(Mybatis默认开启一级缓存)

*/

//获取一个session会话

SqlSession session = getSession();

//获取一个执行sql语句的映射接口 A

MybatisCache firstGradeCacheA = session.getMapper(MybatisCache.class);

//获取一个执行sql语句的映射接口 B

MybatisCache firstGradeCacheB = session.getMapper(MybatisCache.class);

//通过A和B执行同一条sql

Student studentA = firstGradeCacheA.queryStudentById(1);

Student studentB = firstGradeCacheB.queryStudentById(1);

//通过log4j发现sql语句执行了一次 ,并且两个对象相同。说明是第一次查询存入到内存里面的对象

System.out.println(studentA==studentB);

}

//@Test

public void firstGradeCacheCommit(){

/*

一级缓存

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,

在对象中有个(内存区域)数据结构(HashMap)用于存储缓存数据。

不同的SqlSession之间缓存数据区域 (HashMap)是互不影响的。

作用域

一级缓存的作用域是同一个SqlSession在同一个SqlSession中两次执行相同的sql语句

第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据(session在期间该session没有commit),

将不再从数据库查询,从而提高查询效率。(Mybatis默认开启一级缓存)

当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

注意:

如果SqlSession去执行commit操作(执行插入、更新、删除),

会清空SqlSession中的一级缓存,这样做的目的为了让缓存中的数据是最新的信息,避免脏读

*/

//获取一个session会话

SqlSession session = getSession();

//获取一个执行sql语句的映射接口 A

MybatisCache firstGradeCacheA = session.getMapper(MybatisCache.class);

//获取一个执行sql语句的映射接口 update

MybatisCache update= session.getMapper(MybatisCache.class);

//获取一个执行sql语句的映射接口 A

MybatisCache firstGradeCacheB = session.getMapper(MybatisCache.class);

//让A先查询并 获取实体1

Student studentA = firstGradeCacheA.queryStudentById(1);

//创建一个修改的数据

Student updateStudent = new Student(1,"wahaha",2);

//然后在用更新的执行接口修改一下数据

update.updateStudent(updateStudent);

//然后提交一下会话

session.commit();

//再查一下相同的用户

Student studentB = firstGradeCacheB.queryStudentById(1);

//会发现sql语句执行了两次,而且返回的对象也不是同一个地址了

System.out.println(studentA==studentB);

}

/**

二级缓存

二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,

不同的sqlSession多次执行相同namespace下的Sql语句第一次执行完毕会将数据库中查询的数据写到缓存(内存),

第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

所以说二级缓存是跨SqlSession的

Mybatis默认是开启二级缓存的,但是需要在mapper.xml中配置cache标签才能使用。

如果缓存中有数据就不用从数据库中获取,大大提高系统性能

注意:

要使用前一个session查询过的值,必须要等前一个session 关闭后才可以获取到

否则该值还在一级缓存中,因为mybatis的一级缓存是默认的无法取消,这也是为了数据安全性考虑

同一个SqlSessionFactory可以获取不同的session会话

使用二级缓存

1.在核心配置文件Mybatis.xml中配置标签

(默认是开启的但是必须配置cache标签才能使用所以可以不配置该配置文件)

2.在Mapper.xml中开启二级缓存,当该namespace下的 sql执行完成会存储到它的缓存区域

在mapper下第一行配置

.........

*/

@Test

public void secondGradeCacheNoCommitAndClose(){

//获取一个session工厂

SqlSessionFactory factory = getSqlSessionFactory();

//获取两个不同的会话

SqlSession sessionA = factory.openSession();

SqlSession sessionB = factory.openSession();

//执行接口

MybatisCache firstGradeCacheA = sessionA.getMapper(MybatisCache.class);

MybatisCache firstGradeCacheB= sessionB.getMapper(MybatisCache.class);

//查询id为1的学生

Student studentA=firstGradeCacheA.queryStudentById(1);

Student studentB=firstGradeCacheB.queryStudentById(1);

//比较两个结果相同(如果是使用了序列化和反序列化则不相同)

System.out.println(studentA==studentB);

//如果不提交不关闭则数据一直在一级缓存中,会发起两个sql语句

}

@Test

public void secondGradeCacheByCommit(){

//获取一个session工厂

SqlSessionFactory factory = getSqlSessionFactory();

//获取两个不同的会话

SqlSession sessionA = factory.openSession();

SqlSession sessionB = factory.openSession();

//执行接口

MybatisCache firstGradeCacheA = sessionA.getMapper(MybatisCache.class);

MybatisCache firstGradeCacheB= sessionB.getMapper(MybatisCache.class);

//查询id为1的学生

Student studentA=firstGradeCacheA.queryStudentById(1);

//关闭A会话,将一级缓存清空,存入二级缓存

sessionA.commit();

Student studentB=firstGradeCacheB.queryStudentById(1);

//比较两个结果相同(如果是使用了序列化和反序列化则不相同)

System.out.println(studentA==studentB);

//通过log4j显示的运行结果可以看出语句值查询了一次

}

@Test

public void secondGradeCacheClose(){

//获取一个session工厂

SqlSessionFactory factory = getSqlSessionFactory();

//获取两个不同的会话

SqlSession sessionA = factory.openSession();

SqlSession sessionB = factory.openSession();

//执行接口

MybatisCache firstGradeCacheA = sessionA.getMapper(MybatisCache.class);

MybatisCache firstGradeCacheB= sessionB.getMapper(MybatisCache.class);

//查询id为1的学生

Student studentA=firstGradeCacheA.queryStudentById(1);

//关闭A会话,将一级缓存清空,存入二级缓存

sessionA.close();

Student studentB=firstGradeCacheB.queryStudentById(1);

//比较两个结果相同(如果是使用了序列化和反序列化则不相同)

System.out.println(studentA==studentB);

//通过log4j显示的运行结果可以看出语句值查询了一次

}

@Test

public void queryStudentRedis(){

/**

*使用二级缓存时前面的方法查询过该用户后,该方法查询直接从redis中获取,不发起sql语句

*/

SqlSession session = getSession();

MybatisCache firstGradeCache = session.getMapper(MybatisCache.class);

Student student = firstGradeCache.queryStudentById(2);

session.close();

}

/**

* 提交一个数据看是否会将二级缓存清空

*/

@Test

public void commitData(){

SqlSession session = getSession();

MybatisCache update= session.getMapper(MybatisCache.class);

Student updateStudent = new Student(1,"wahaha",2);

update.updateStudent(updateStudent);

session.commit();

}

}


数据库的中的表可以根据实体的字段创建


自定义缓存类代码在上面,三、自定义缓存的实现中有代码

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台