大家都知道,数据库的本质是以文件的形式保存在硬盘上,所以每一次查询数据库的操作都是在进行IO操作,IO操作是很耗时的。所以为了提高查询效率,可以先从缓存(内存里面)取,如果缓存里面没有再访问数据库。

哪些数据适合使用缓存

经常查找又不经常改变的数据、数据的正确性对业务影响不大。有些数据尤其牵扯到钱的时候就不能使用缓存,比如股票价格、汇率、库存等

在MyBatis中缓存指的是什么

MyBatis缓存指的是对select标签语句的缓存

MyBatis的缓存有一级、二级缓存的概念

一级缓存(默认)

使用同一个SqlSession查询相同的select标签时,缓存共享。

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. Person person1 = session.selectOne("person.getOnePerson",1);
  4. System.out.println(person == person1); //true
  5. }

上面的person和person1内存地址相同,说明缓存共享。

当关闭SqlSession,缓存失效。对应上面try结束后关闭SqlSession,缓存失效。

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. System.out.println(person);
  4. }
  5. try(SqlSession session = MyBatisUtils.openSession()){
  6. Person person = session.selectOne("person.getOnePerson",1);
  7. System.out.println(person);
  8. }

上面打印的person内存地址不同,说明缓存失效。

当执行insert、update、delete、commit等方法时,会自动清理一级缓存

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. System.out.println(person);
  4. //当执行delete\update\insert\commit时会,清楚缓存
  5. // session.delete("namespace.delete");
  6. // session.update("namespace.update");
  7. // session.insert("namespace.insert");
  8. session.commit();
  9. Person person1 = session.selectOne("person.getOnePerson",1);
  10. System.out.println(person1);
  11. }

上面的person和person1内存地址不同,说明缓存失效。

其实每次我们处理客户端的请求查询数据库的时候都是开启不同的SqlSession,而不同的SqlSession缓存是不共享的,所以一级缓存命中率不高。

二级缓存(需要手动开启)

为了提高缓存命中率,可以开启二级缓存。二级缓存也就是namespace级别的缓存,即mapper.xml级别。同一个mapper.xml下的所有select标签进行缓存。当不同的SqlSession查询相同的select标签时,达到共享缓存。
二级缓存放在全局唯一的SqlSessionFactory里面。SqlSessionFactory里面有个map结构,key 为namespace ,value为对应的缓存数据。

二级缓存的时刻

当SqlSession关闭时,才会将查询的结果进行缓存。而且默认情况下,当执行mapper.xml下的insert、update、delete时,会自动清理其二级缓存。

开启二级缓存

在映射文件mapper中添加cache标签即可

  1. <cache />

默认会缓存映射文件中的所有select结果

外界测试

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. System.out.println(person);
  4. }
  5. try(SqlSession session = MyBatisUtils.openSession()){
  6. Person person1 = session.selectOne("person.getOnePerson",1);
  7. System.out.println(person1);
  8. }

报错如下

  1. org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.lff.beans.Person

JavaBean类要实现Serializable接口,实现序列化接口后,我们再次测试,遗憾的是当我们再次测试后,person和person1的对象内存地址并不相同,好奇怪?查看日志SQL语句确实只执行了一次,而且有缓存命中率,说明第二次查询的确实执行了缓存

  1. :43:04.636 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  2. 20:43:04.645 [main] DEBUG person.getOnePerson - ==> Preparing: SELECT person.* FROM person where id = ?
  3. 20:43:04.670 [main] DEBUG person.getOnePerson - ==> Parameters: 1(Integer)
  4. 20:43:04.685 [main] DEBUG person.getOnePerson - <== Total: 1
  5. com.lff.beans.Person@7ac2e39b
  6. 20:43:04.690 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  7. 20:43:04.691 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  8. 20:43:04.693 [main] DEBUG person - Cache Hit Ratio [person]: 0.5
  9. com.lff.beans.Person@19553973

我们尝试把映射文件中的标签改为下面的

  1. <cache readOnly="true"/>

执行上面相同的代码,日志信息

  1. :45:47.140 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@28e8dde3]
  2. 20:45:47.149 [main] DEBUG person.getOnePerson - ==> Preparing: SELECT person.* FROM person where id = ?
  3. 20:45:47.189 [main] DEBUG person.getOnePerson - ==> Parameters: 1(Integer)
  4. 20:45:47.208 [main] DEBUG person.getOnePerson - <== Total: 1
  5. com.lff.beans.Person@68fa0ba8
  6. 20:45:47.209 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@28e8dde3]
  7. 20:45:47.210 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@28e8dde3]
  8. 20:45:47.210 [main] DEBUG person - Cache Hit Ratio [person]: 0.5
  9. com.lff.beans.Person@68fa0ba8

这次person和person1是同一个,也执行了缓存操作。

关于readOnly属性官方是这样说的
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
也就是说,当readOnly为false时
当第一次执行如下代码后

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. System.out.println(person);
  4. }

会从数据库里面查询,查询后的结果会放到二级缓存中,放到二级缓存的数据是经过序列化后的数据。
当再次查询时

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person1 = session.selectOne("person.getOnePerson",1);
  3. System.out.println(person1);
  4. }

会从二级缓存反序列化一个新的person1对象。所以每次从缓存取的都是一个新的person对象,这样当修改person的属性后,也不会影响二级缓存中的数据,也就更加安全。

当然有的时候,你可能对某个select标签不想使用缓存,比如在做多条件查询的时候,每次的查询条件可能都不一样,这时使用缓存的意义不大。可以使用useCache选项进行关闭

  1. <select id="getOnePerson" resultMap="personMap" useCache="false">
  2. SELECT
  3. person.*
  4. FROM person where id = #{id}
  5. </select>

默认情况下当执行insert、update、delete后缓存就会清空。
如果不想清空可以设置flushCache选项

  1. <delete id="xxx" flushCache="false">
  2. </delete>

注意点

由于二级缓存是在SqlSession关闭时,才会将查询的结果进行缓存。所以当关闭SqlSession之前如果对查询结果进行修改,会将修改后的结果进行缓存,其它SqlSession再次查询时就会得到修改后的数据。

  1. try(SqlSession session = MyBatisUtils.openSession()){
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. System.out.println("修改之前的age是 = "+person.getAge());
  4. person.setAge(100);
  5. System.out.println("修改之后的age是 = "+person.getAge());
  6. }
  7. try(SqlSession session = MyBatisUtils.openSession()){
  8. Person person1 = session.selectOne("person.getOnePerson",1);
  9. System.out.println("其他SqlSession查询的age是 = "+person1.getAge());
  10. System.out.println(person1.getAge());
  11. }

打印日志

  1. 09:34:53.395 [main] DEBUG person.getOnePerson - ==> Preparing: SELECT person.* FROM person where id = ?
  2. 09:34:53.419 [main] DEBUG person.getOnePerson - ==> Parameters: 1(Integer)
  3. 09:34:53.433 [main] DEBUG person.getOnePerson - <== Total: 1
  4. 修改之前的age = 18
  5. 修改之后的age = 100
  6. 09:34:53.437 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  7. 09:34:53.438 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  8. 09:34:53.441 [main] DEBUG person - Cache Hit Ratio [person]: 0.5
  9. 其他SqlSession查询的age = 100
  10. 100

可以根据情况是否将修改的数据进行缓存,如果不想将修改的数据进行缓存,可以先关闭SqlSession,再修改数据

  1. SqlSession session = MyBatisUtils.openSession();
  2. Person person = session.selectOne("person.getOnePerson",1);
  3. System.out.println("修改之前的age是 = "+person.getAge());
  4. session.close();//修改数据之前关闭SqlSession
  5. person.setAge(100);
  6. System.out.println("修改之后的age是 = "+person.getAge());
  7. try(SqlSession session1 = MyBatisUtils.openSession()){
  8. Person person1 = session1.selectOne("person.getOnePerson",1);
  9. System.out.println("其他SqlSession查询的age是 = "+person1.getAge());
  10. System.out.println(person1.getAge());
  11. }

打印日志

  1. 09:37:34.143 [main] DEBUG person.getOnePerson - ==> Preparing: SELECT person.* FROM person where id = ?
  2. 09:37:34.180 [main] DEBUG person.getOnePerson - ==> Parameters: 1(Integer)
  3. 09:37:34.202 [main] DEBUG person.getOnePerson - <== Total: 1
  4. 修改之前的age = 18
  5. 09:37:34.207 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  6. 09:37:34.209 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3a3e78f]
  7. 修改之后的age = 100
  8. 09:37:34.213 [main] DEBUG person - Cache Hit Ratio [person]: 0.5
  9. 其他SqlSession查询的age = 18
  10. 18