MyBatis一级缓存原理解析

作者: 匠丶 | 来源:发表于2018-12-05 22:02 被阅读143次

    Batis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上。MyBatis提供了一级缓存、二级缓存 这两个缓存机制,能够很好地处理和维护缓存,以提高系统的性能。本文将介绍MyBatis的一级缓存,并深入源码解析MyBatis一级缓存的实现原理。

    什么是一级缓存?

    每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。

    在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

    为了解决这一问题,减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

    如下图所示,MyBatis会在一次会话的表示----一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。



    对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。

    一级缓存的实现原理

    由于MyBatis使用SqlSession对象表示一次数据库的会话,那么,对于会话级别的一级缓存也应该是在SqlSession中控制的。

    实际上, MyBatis只是一个MyBatis对外的接口,SqlSession将它的工作交给了Executor执行器这个角色来完成,负责完成对数据库的各种操作。当创建了一个SqlSession对象时,MyBatis会为这个SqlSession对象创建一个新的Executor执行器,而缓存信息就被维护在这个Executor执行器中,MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。SqlSession、Executor、Cache之间的关系如下列类图所示:



    如上述的类图所示,Executor接口的实现类BaseExecutor中拥有一个Cache接口的实现类PerpetualCache,则对于BaseExecutor对象而言,它将使用PerpetualCache对象维护缓存。

    由于Session级别的一级缓存实际上就是使用PerpetualCache维护的,那么PerpetualCache是怎样实现的呢?

    PerpetualCache实现原理其实很简单,其内部就是通过一个简单的HashMap<k,v> 来实现的,没有其他的任何限制。如下是PerpetualCache的实现代码:

    public class PerpetualCache implements Cache {
     
      private String id;
     
      private Map<Object, Object> cache = new HashMap<Object, Object>();
     
      public PerpetualCache(String id) {
        this.id = id;
      }
     
      public String getId() {
        return id;
      }
     
      public int getSize() {
        return cache.size();
      }
     
      public void putObject(Object key, Object value) {
        cache.put(key, value);
      }
     
      public Object getObject(Object key) {
        return cache.get(key);
      }
     
      public Object removeObject(Object key) {
        return cache.remove(key);
      }
     
      public void clear() {
        cache.clear();
      }
     
      public ReadWriteLock getReadWriteLock() {
        return null;
      }
     
      public boolean equals(Object o) {
        if (getId() == null) throw new CacheException("Cache instances require an ID.");
        if (this == o) return true;
        if (!(o instanceof Cache)) return false;
     
        Cache otherCache = (Cache) o;
        return getId().equals(otherCache.getId());
      }
     
      public int hashCode() {
        if (getId() == null) throw new CacheException("Cache instances require an ID.");
        return getId().hashCode();
      }
     
    }
    

    一级缓存的生命周期有多长?

    a. MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

    b. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;

    c. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;

    d.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;


    CacheKey的定义

    我们知道,Cache最核心的实现其实就是一个Map,将本次查询使用的特征值作为key,将查询结果作为value存储到Map中。

    现在最核心的问题出现了:怎样来确定一次查询的特征值?

    换句话说就是:怎样判断某两次查询是完全相同的查询?

    也可以这样说:如何确定Cache中的key值?

    MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询:

    1. 传入的 statementId

    2. 查询时要求的结果集中的结果范围 (结果的范围通过rowBounds.offset和rowBounds.limit表示);

    3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

    4. 传递给java.sql.Statement要设置的参数值

    综上所述,CacheKey由以下条件决定:statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值

    相关文章

      网友评论

        本文标题:MyBatis一级缓存原理解析

        本文链接:https://www.haomeiwen.com/subject/xpdgcqtx.html