mybatis之缓存

数据库的知识一定离不开缓存,总是查数据库是很消耗数据库服务器的。

一级缓存

mybatis的一级缓存是自动开启的。我们感受一下:

   @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee emp1 = employeeMapper.selectById(1);
            Employee emp2 = employeeMapper.selectById(1);
            System.out.println("emp1 by id 1-->" + emp1);
            System.out.println("emp2 by id 1-->" + emp2);
            System.out.println("emp1 == emp2 ? " + (emp1 == emp2));
        } finally {
            sqlSession.close();
        }
    }

console的结果:

DEBUG 03-27 14:00:31,719 ==>  Preparing: select * from employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:00:31,763 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:00:31,803 <==      Total: 1  (BaseJdbcLogger.java:143) 
emp1 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp2 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp1 == emp2 ? true

注意到,sql语句只发了一次,并且,查询出的员工是同一个对象,这说明,第一次查询好后,mybatis已经将id为1的员工缓存起来的。

整个过程类似于这样:

package org.mybatis.lecture.test;

import org.mybatis.lecture.bean.Employee;

import java.util.HashMap;
import java.util.Map;

public class CacheMap {

    private static Map<Integer, Employee> employeeMapCache = new HashMap<>();
    private static Map<Integer, Employee> employeeMapDatabase = new HashMap<>();

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setId(1);
        emp.setLastName("King");
        emp.setEmail("11111@test.com");
        employeeMapDatabase.put(1, emp);

        Employee emp1 = selectById(1);
        System.out.println(emp1);
        Employee emp2 = selectById(1);
        System.out.println(emp2);
        System.out.println("emp1 == emp2 ? " + (emp1 == emp2));
    }

    private static Employee selectById(Integer id) {
        Employee employeeResult = employeeMapCache.get(id);
        if (employeeResult == null) {
            System.out.println("query the database...");
            Employee employeeFromDatabase = employeeMapDatabase.get(1);
            System.out.println("employee from database: " + employeeFromDatabase);
            System.out.println("save data to the cache...");
            employeeMapCache.put(1, employeeFromDatabase);
            return employeeFromDatabase;
        }

        return employeeResult;
    }
}

缓存有就拿缓存的,没有就从数据库中查,然后要存入缓存。


所谓一级缓存,就是范围比较小的,它的范围,就是一次会话,即一次sqlSession。所谓一个session,就是和数据库的一次连接。

我们可以开两次会话来查同一个数据:

  @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        try {
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee emp1 = employeeMapper.selectById(1);

            EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
            Employee emp2 = employeeMapper2.selectById(1);

            System.out.println("emp1 by id 1-->" + emp1);
            System.out.println("emp2 by id 1-->" + emp2);

            System.out.println("emp1 == emp2 ? " + (emp1 == emp2));
        } finally {
            sqlSession.close();
        }
    }

这时候的结果:

DEBUG 03-27 14:43:50,996 ==>  Preparing: select * from employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:43:51,046 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:43:51,080 <==      Total: 1  (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:43:51,084 ==>  Preparing: select * from employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:43:51,084 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 14:43:51,085 <==      Total: 1  (BaseJdbcLogger.java:143) 
emp1 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp2 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp1 == emp2 ? false

sql语句发了两条。员工对象也不一样了。

另一方面,假使我们还位于同一个session中,如果两次查同一条数据之间有增删改的操作,此时一级缓存也会失效。

   @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee emp1 = employeeMapper.selectById(1);
            
            Employee newEmp = new Employee();
            newEmp.setLastName("Lin");
            newEmp.setEmail("Lin@163.com");
            Integer ifCreated = employeeMapper.createEmployee(newEmp);

            System.out.println("ifCreated: " + ifCreated);
            
            Employee emp2 = employeeMapper.selectById(1);

            System.out.println("emp1 by id 1-->" + emp1);
            System.out.println("emp2 by id 1-->" + emp2);

            System.out.println("emp1 == emp2 ? " + (emp1 == emp2));

            sqlSession.commit();
        } finally {
            sqlSession.close();
        }
    }
DEBUG 03-27 19:40:19,969 ==>  Preparing: select * from employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,028 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,072 <==      Total: 1  (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,073 ==>  Preparing: insert into employee(last_name,email) values(?,?)   (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,074 ==> Parameters: Lin(String), Lin@163.com(String)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,075 <==    Updates: 1  (BaseJdbcLogger.java:143) 
ifCreated: 1
DEBUG 03-27 19:40:20,078 ==>  Preparing: select * from employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,078 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 19:40:20,079 <==      Total: 1  (BaseJdbcLogger.java:143) 
emp1 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp2 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp1 == emp2 ? false

在insert之后又发了一次sql,原因也很简单,因为你做了增删改,原来的数据可能会有变化。


二级缓存

二级缓存的的范围更大,它是面向namespace的。也就是说,同一个namespace中,是共享session的。

为了显式地开启二级缓存,需要在mybatis-config.xml中加入:

 <settings>
 <setting name="cacheEnabled" value="true"/>
    </settings>

虽然默认是true,但是最好还是写出来。

然后在sql映射文件中加入:

<mapper namespace="org.mybatis.lecture.mapper.EmployeeMapper">

    <cache></cache>
</mapper>

为了能够使用二级缓存,pojo必须实现序列化接口:

@Alias("emp")
public class Employee implements Serializable {
}

测试一下:

  @Test
    public void testSecondLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        try {
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee emp1 = employeeMapper.selectById(1);
            System.out.println("emp1 by id 1-->" + emp1);
            sqlSession.close();

            EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
            Employee emp2 = employeeMapper2.selectById(1);
            System.out.println("emp2 by id 1-->" + emp2);
            sqlSession2.close();

            System.out.println("emp1 == emp2 ? " + (emp1 == emp2));


        } finally {

        }
    }
DEBUG 03-27 20:30:49,902 Cache Hit Ratio [org.mybatis.lecture.mapper.EmployeeMapper]: 0.0  (LoggingCache.java:60) 
DEBUG 03-27 20:30:50,174 ==>  Preparing: select * from employee where id=?   (BaseJdbcLogger.java:143) 
DEBUG 03-27 20:30:50,277 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 03-27 20:30:50,324 <==      Total: 1  (BaseJdbcLogger.java:143) 
emp1 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
DEBUG 03-27 20:30:50,339 Cache Hit Ratio [org.mybatis.lecture.mapper.EmployeeMapper]: 0.5  (LoggingCache.java:60) 
emp2 by id 1-->Employee{id=1, lastName='King', email='11111@test.com'}
emp1 == emp2 ? false

注意到,sql语句还是发了一句。因为我关掉了第一个session(一级缓存失效了),所以一定是将员工1的数据存在了二级缓存中,才能不发sql就查出来。

同时注意,两次查询出的对象并非同一个。

若第一个session最后才关闭:

 @Test
    public void testSecondLevelCache() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        try {
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            Employee emp1 = employeeMapper.selectById(1);
            System.out.println("emp1 by id 1-->" + emp1);


            EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
            Employee emp2 = employeeMapper2.selectById(1);
            System.out.println("emp2 by id 1-->" + emp2);


            System.out.println("emp1 == emp2 ? " + (emp1 == emp2));


        } finally {
            sqlSession.close();
            sqlSession2.close();
        }
    }

这时候两个session各管各的,当然会发两次sql。之前是第一个session关了,才用上了二级缓存。


缓存的禁用

先说一级缓存,一般来说它禁用不掉,人家默认设置也是为了查询的优化。

不过如果你在settings中配置

 <setting name="localCacheScope" value="STATEMENT"/>

一级缓存也就被禁用了。

但是我们不这么做。

接下来讨论二级缓存的情况。

如果你的select这么改:

 <!--Employee selectById(Integer id);-->
    <select id="selectById" resultMap="mappingEmp" useCache="false">
        select * from employee where id=#{id}
    </select>

如此二级缓存失效。


EHCache

mybatis自己的缓存比较简单,但是它提供了Cache接口,由此可以自己写实现或者使用其他实现了Cache接口的第三方缓存。

这次就说一下EHCache的使用。

mybatis整合EHCache的 步骤

1,添加maven依赖:

    <dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-ehcache</artifactId>
      <version>1.2.0</version>
    </dependency>

因为EHCache依赖于slf4j,所以我们加入slf4j的绑定的依赖:

 <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.2</version>
      <scope>test</scope>
    </dependency>

2,在EmployeeMapper中加入<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>标签。这个EhcacheCache就实现了Cache接口。

3,在resources目录下添加配置文件ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="true"
         monitoring="autodetect" dynamicConfig="true">

    <!-- By default, Ehcache stored the cached files in temp folder. -->
    <!-- <diskStore path="java.io.tmpdir" /> -->

    <!-- Ask Ehcache to store cache in this path -->
    <diskStore path="/Users/aruyi/atao/cache" />

    <!-- 
    This cache contains a maximum in memory of 10000 elements, and will expire
    an element(eternal="false" means cache will expire sometime) if it is idle for more than 5 minutes and lives for more than
    10 minutes.

    If there are more than 10000 elements it will overflow to the
    disk cache, which in this configuration will go to wherever java.io.tmp is
    defined on your system. On a standard Linux system this will be /tmp" -->

    <defaultCache maxEntriesLocalHeap="10000"
           maxEntriesLocalDisk="100000"
           eternal="false"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="300" timeToLiveSeconds="600"
           memoryStoreEvictionPolicy="LFU"
           transactionalMode="off">
        <persistence strategy="localTempSwap" />
    </defaultCache>

</ehcache>
<diskStore path="/Users/aruyi/atao/cache" />

写自己要存的磁盘路径。更多的属性参照EHCache官网

如此便可使用了。

查询过后磁盘上会有缓存的文件:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值