面试官:mybatis的缓存机制是怎么样的?

MyBatis的缓存分为一级缓存和二级缓存,两种缓存的缓存粒度是一样的,都是对应一条sql查询语句,但是二者的生命周期是不一样的,一级缓存的生命周期是SqlSession对象的使用期间,随着SqlSession对象的死亡而消失;二级缓存的生命周期是同MyBatis应用一样长的;并且是首先查询二级缓存,然后再查询一起缓存正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持。

1.缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper ,并且可自定义存储源,如 Ehcache。二级缓存的作用域是全局的,二级缓存在SqlSession关闭或提交之后才会生效。

在分析MyBatis的二级缓存之前,我们先简单看下MyBatis中一个关于二级缓存的类(其他相关的类和接口之前已经分析过):org.apache.ibatis.mapping.MappedStatement:MappedStatement类在Mybatis框架中用于表示XML文件中一个sql语句节点,即一个、或者标签。Mybatis框架在初始化阶段会对XML配置文件进行读取,将其中的sql语句节点对象化为一个个MappedStatement对象3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。mybatis的一级缓存作用就是在同一个session过程中,将会对相同数据的存取不通过数据库,而是通过缓存机制提高效率。

看测试代码

importjava.io.IOException;importjava.io.InputStream;importjunit.framework.TestCase;importorg.apache.ibatis.io.Resources;importorg.apache.ibatis.session.SqlSession;importorg.apache.ibatis.session.SqlSessionFactory;importorg.apache.ibatis.session.SqlSessionFactoryBuilder;importorg.junit.Test;importcom.guowuxin.mybatis.mapper.UserMapper;importcom.guowuxin.mybatis.model.User;

/*用户表drop table if exists user;create table user(    id int primary key auto_increment,    username varchar(50) unique,    password varchar(100),    useraddress varchar(50));insert intouservalues(1,"guowuxin","guowuxin","beijing");insert intouservalues(1,"guoxiaoming","guowuxin","beijing");*/

//如果两次查询都是查询1 将只会发送一条sql语句到数据库,否则将是2条sql语句

public class TestClass extends TestCase{    public static SqlSessionFactory sqlSessionFactoy;    static{        String resource ="mybatis-config.xml";        try {            InputStream inputStream = Resources.getResourceAsStream(resource);            sqlSessionFactoy = new SqlSessionFactoryBuilder().build(inputStream);        } catch (IOException e) {            e.printStackTrace();        }    }    @Test    public void testgetUserById() {        SqlSession sqlSession = sqlSessionFactoy.openSession();        try {                  UserMapper mapper = sqlSession.getMapper(UserMapper.class); User u1=mapper.getUserById(1);                System.out.println(u1); User u2=mapper.getUserById(1);                System.out.println(u2);                sqlSession.commit();      } finally {            sqlSession.close();        }    }}

packagecom.guowuxin.mybatis.mapper;importcom.guowuxin.mybatis.model.User;//通过面向接口的mybatis编程方式,需要保证方法名和配置文件中的id名称一致publicinterfaceUserMapper{publicUser getUserById(int id);}packagecom.guowuxin.mybatis.model;publicclassUser{privateint id;privateint age;privateString userName;privateString userAddress;publicint getId() {returnid;    }publicvoidsetId(int id) {this.id = id;    }publicString getUserName() {returnuserName;    }publicvoidsetUserName(String userName) {this.userName = userName;    }publicString getUserAddress() {returnuserAddress;    }publicvoidsetUserAddress(String userAddress) {this.userAddress = userAddress;    }publicint getAge() {returnage;    }publicvoidsetAge(int age) {this.age = age;    }    @OverridepublicString toString()    {finalStringBuilder builder =newStringBuilder();        builder.append("id=").append(id).append(",");        builder.append("age=").append(age).append(",");        builder.append("userName=").append(userName).append(",");        builder.append("userAddress=").append(userAddress).append(".");returnbuilder.toString();    }}

log4j.rootLogger=DEBUG,Console��#Consolelog4j.appender.Console=org.apache.log4j.ConsoleAppenderlog4j.appender.Console.layout=org.apache.log4j.PatternLayoutlog4j.appender.Console.layout.ConversionPattern=%d%t%-5p(%c) -%m%nlog4j.logger.com.ibatis=DEBUGlog4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUGlog4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUGlog4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUGlog4j.logger.java.sql.Connection=DEBUGlog4j.logger.java.sql.Statement=DEBUGlog4j.logger.java.sql.PreparedStatement=DEBUG

<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><?xml version="1.0"encoding="UTF-8"?><!DOCTYPE mapper    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"    "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 这里namespace必须是PostsMapper接口的路径,不然要运行的时候要报错 “is not known to the MapperRegistry”--><!-- 这儿的resultType是配置在mybatis-config.xml中得别名 -->select * from user where id=#{id}

结果:………………..

2016-02-24 20:12:37,568 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:37,568 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==>  Preparing: select *from user whereid=? 2016-02-24 20:12:37,586 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==> Parameters: 1(Integer)id=1,age=0,userName=guowuxin,userAddress=beijing.id=1,age=0,userName=guowuxin,userAddress=beijing.2016-02-24 20:12:37,593 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Resetting autocommittotrueon JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:37,593 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:37,593 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Returned connection 3874052topool.

如果取得2个不同的id…………………..

2016-02-24 20:12:57,655 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Created connection 3874052.2016-02-24 20:12:57,656 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:57,656 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==>  Preparing: select *from user whereid=? 2016-02-24 20:12:57,664 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==> Parameters: 1(Integer)id=1,age=0,userName=guowuxin,userAddress=beijing.2016-02-24 20:12:57,679 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:57,679 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==>  Preparing: select *from user whereid=? 2016-02-24 20:12:57,679 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==> Parameters: 2(Integer)id=2,age=0,userName=guoxiaoming,userAddress=beijing.2016-02-24 20:12:57,679 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Resetting autocommittotrueon JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:57,679 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:12:57,679 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Returned connection 3874052topool.

二级缓存机制指的则是在不同的session之间都可以共享相同的数据内容。

importjava.io.IOException;importjava.io.InputStream;importorg.apache.ibatis.io.Resources;importorg.apache.ibatis.session.SqlSession;importorg.apache.ibatis.session.SqlSessionFactory;importorg.apache.ibatis.session.SqlSessionFactoryBuilder;importorg.junit.Test;importcom.guowuxin.mybatis.mapper.UserMapper;importcom.guowuxin.mybatis.model.User;importjunit.framework.TestCase;publicclassTestTwoCacheextendsTestCase{publicstaticSqlSessionFactory sqlSessionFactory;static{        String resource ="mybatis-config.xml";try{            InputStream inputStream = Resources.getResourceAsStream(resource);            sqlSessionFactory =newSqlSessionFactoryBuilder().build(inputStream);        }catch(IOException e) {            e.printStackTrace();        }    }    @TestpublicvoidtestCache() {        SqlSession sqlSession1 = sqlSessionFactory.openSession();      SqlSession sqlSession2 = sqlSessionFactory.openSession();try{                  UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);                  User u1=mapper1.getUserById(1);                System.out.println(u1);                sqlSession1.commit();                UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);                User u2=mapper2.getUserById(1);                System.out.println(u2);                sqlSession2.commit();      }finally{            sqlSession1.close();            sqlSession2.close();      }    }}

在没有开启二级缓存之前,将会执行两次sql语句。

2016-02-24 20:28:10,786 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - PooledDataSource forcefully closed/removed all connections.2016-02-24 20:28:10,850 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Openning JDBC Connection2016-02-24 20:28:11,011 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Created connection 3874052.2016-02-24 20:28:11,012 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:28:11,013 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==>  Preparing: select *from user whereid=? 2016-02-24 20:28:11,030 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==> Parameters: 1(Integer)id=1,age=0,userName=guowuxin,userAddress=beijing.2016-02-24 20:28:11,040 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Openning JDBC Connection2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Created connection 29420695.2016-02-24 20:28:11,154 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@1c0ec97]2016-02-24 20:28:11,154 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==>  Preparing: select *from user whereid=? 2016-02-24 20:28:11,154 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==> Parameters: 1(Integer)id=1,age=0,userName=guowuxin,userAddress=beijing.2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Resetting autocommittotrueon JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3b1d04]2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Returned connection 3874052topool.2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Resetting autocommittotrueon JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c0ec97]2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1c0ec97]2016-02-24 20:28:11,154 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Returned connection 29420695topool.------------------------

在实体类映射文件中加入<?xml version=”1.0” encoding=”UTF-8” ?>

<!DOCTYPE mapper    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"    "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- 这里namespace必须是PostsMapper接口的路径,不然要运行的时候要报错 “is not known to the MapperRegistry”--><!-- 这儿的resultType是配置在mybatis-config.xml中得别名 -->select * from user where id=#{id}

2016-02-25 10:15:11,162 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Created connection 22522451.2016-02-25 10:15:11,164 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ooo Using Connection [com.mysql.jdbc.JDBC4Connection@157aa53]2016-02-25 10:15:11,164 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==>  Preparing: select *from user whereid=? 2016-02-25 10:15:11,182 mainDEBUG(com.guowuxin.mybatis.mapper.UserMapper.getUserById) - ==> Parameters: 1(Integer)id=1,age=0,userName=guowuxin,userAddress=beijing.2016-02-25 10:15:11,196 mainDEBUG(org.apache.ibatis.cache.decorators.LoggingCache) - Cache Hit Ratio [com.guowuxin.mybatis.mapper.UserMapper]: 0.5id=1,age=0,userName=guowuxin,userAddress=beijing.2016-02-25 10:15:11,196 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Resetting autocommittotrueon JDBC Connection [com.mysql.jdbc.JDBC4Connection@157aa53]2016-02-25 10:15:11,196 mainDEBUG(org.apache.ibatis.transaction.jdbc.JdbcTransaction) - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@157aa53]2016-02-25 10:15:11,197 mainDEBUG(org.apache.ibatis.datasource.pooled.PooledDataSource) - Returned connection 22522451topool.

就会发现cache命中,只执行了一次sql语句———————————————如果二级缓存想要命中实现,则必须要将上一次sqlSession commit之后才能生效,不然将不会命中,原因:两个不同的session必须提交前面一个session才能缓存生效的原因是因为mybatis的缓存会被一个transactioncache类包装住,所有的cache.putObject全部都会被暂时存到一个map里,等事务提交以后,这个map里的缓存对象才会被真正的cache类执行putObject操作。这么设计的原因是为了防止事务执行过程中出异常导致回滚,如果get到object后直接put进缓存,万一发生回滚,就很容易导致mybatis缓存被脏读

参考文章:http://my.oschina.net/KingPan/blog/280167#OSC_h1_1

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351