Mybatis缓存源码及分布式缓存应用

Mybatis

mybatis加载过程

  1. Config.xml全局配置文件解析放到configuration对象中,Java中的注解配置及SQL配置文件封装到statement对象中存储在内存,同样由configuration对象来维护

  2. 映射文件加载:mappedStatement对象解析sql,每个sql对象对应一个statementID,将解析后的sql存储到map中,key就是statementId,value就是解析后的s q l,存储到sqlsource中

  3. sqlSource存储sql信息,还提供对sql的解析功能

    dynamicsqlSource:${}解析这种动态sql

    RawsqlSource:#{}解析这种动态sql

    StaticsqlSource:将以上两种sql解析后,存储到sqlSource,通过这个sqlSource可以获取到BoundSql
    结合staticSqlSource源码理解会会更容易些

public class StaticSqlSource implements SqlSource {
    
    

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    
    
    this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    
    
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    
    
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

}
  1. BoundSql:

    String sql;解析后的s q l

    List ParameterMappings; 对应传入的参数

    sqlNode :树状结构

    sqlSource存储sql信息,是通过sqlNode 的集合

Mybatis 一级缓存

mybatis是有一级缓存和二级缓存的

一级缓存

介绍

一级缓存是基于sqlSession的

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
// 第一次查询
Demo demo1 = demoMapper.findById(1);
// 第二次查询
Demo demo2 = demoMapper.findById(1);

上述代码,两次连续使用同一demoMapper进行查询,第一次查询确实会对数据库进行连接查询,而到第二次的时候,则会去mybatis的一级缓存中去取。也就是第一次查询后,会将查询结果存储到一级缓存,之后则再次查询会在一级缓存中去查找,匹配到则返回,匹配不到再去数据库中查询。

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
DemoMapper demoMapper = sqlSession.getMapper(DemoMapper.class);
// 第一次查询
Demo demo1 = demoMapper.findById(1);

//更新
Demo demo = new User();
demo.setId(1);
demo.setName("test");
demoMapper.updateDemo(demo);
// 第二次查询
Demo demo2 = demoMapper.findById(1);

上述代码,在第一次查询后,又对数据进行了update,再第二次查询。

过程

  1. 第一次查询对数据库进行连接查询,并将查询结果存储到一级缓存。
  2. 在将数据update,update操作会执行清空一级缓存,所以此时一级缓存中对应的缓存被清空了。
  3. 在进行第二次查询时,一级缓存中并不会获取到,所以去数据库中查询。

源码分析

我们找到SqlSesion接口.其中看起来对一级缓存有作用的也就是一个clearCache()方法

在这里插入图片描述

1.进入clearCache的()方法的实现DefaultSqlSession

在这里插入图片描述

源码如下:为了方便观看,我就不截图了,直接粘贴源码

  public void clearCache() {
    
    
    this.executor.clearLocalCache();
  }
2.进入到Executor接口

在这里插入图片描述

3.进入到Executor中clearLocalCache()方法实现

BaseExecutor类中实现的源码如下

 public void clearLocalCache() {
    
    
    if (!this.closed) {
    
    
      this.localCache.clear();
      this.localOutputParameterCache.clear();
    }
  }
4.进入到localCache.clear()方法:

PerpetualCache类源码如下

public void clear() {
    
    
    this.cache.clear();
 }
5.cache.clear(),cache对象是什么呢?

看下面的源码,原来执行clear的对象只是一个简单的map的对象。从这里我们就可以知道mybatis的一级缓存是使用Map来存储的。
在这里插入图片描述

----------------------------------------------------------------------分割线----------------------------------------------------------------------------

上面介绍的源码一看就是一级缓存的清空。那创建呢?

我们来回顾下上面源码的过程

SqlSession–DefaultSqlSession–Executor–BaseExecutor–PerpetualCache

Executor是什么?执行器,我们知道执行器是用来执行SQL请求的,既然清空缓存都在excutor执行了,那么执行完SQL请求获取到结果存储到一起缓存的操作肯定也是在这里执行的。

1.Executor中的方法,关于创建缓存的方法是createCacheKey()方法

在这里插入图片描述

2.去实现类BaseExecutor中看具体的实现

源码如下:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    
    
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

注意到这行代码CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

这里的createCacheKey()方法源码:内容直接写到下面代码的注释了

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    
    
    if (closed) {
    
    
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // boundSQL的ID,应该就是那个statementID,类似路径包名+类名+sql这样
    cacheKey.update(ms.getId());
    // 就是SQL中使用的offset,控制查询范围的那种
    cacheKey.update(rowBounds.getOffset());
    // SQL中有没有limit限制查询数量
    cacheKey.update(rowBounds.getLimit());
    // 这里就是具体的SQL语句了
    cacheKey.update(boundSql.getSql());
    
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    for (ParameterMapping parameterMapping : parameterMappings) {
    
    
      if (parameterMapping.getMode() != ParameterMode.OUT) {
    
    
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
    
    
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
    
    
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    
    
          value = parameterObject;
        } else {
    
    
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        
        // 这里value是什么,可以去回顾下加载过程,ParameterMapping存储的是SQL中的参数,所以这里就是存储的参数信息
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
    
    
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

综上:一级缓存就是在这里创建好了。其中存储的是什么,在上面的注释中已经备注好了,下面再罗列下

CacheKey cacheKey = new CacheKey();
// boundSQL的ID,应该就是那个statementID,类似路径包名+类名+sql这样
cacheKey.update(ms.getId());
// 就是SQL中使用的offset,控制查询范围的那种
cacheKey.update(rowBounds.getOffset());
// SQL中有没有limit限制查询数量
cacheKey.update(rowBounds.getLimit());
// 这里就是具体的SQL语句了
cacheKey.update(boundSql.getSql());
// 这里value是什么,可以去回顾下加载过程,ParameterMapping存储的是SQL中的参数,所以这里就是存储的参数信息
cacheKey.update(value);

二级缓存

介绍

二级缓存也是存储查询结果和对应的SQL,查询完放到缓存,再次查询可以从里面去取。

那么它和一级缓存有什么区别呢

一级缓存上面已经介绍了,是基于SQLSession的,即同一个sqlSession.

二级缓存是基于mapper的nameSpace的,也就是一个mapper(也可以是nameSpace相同的多个mapper)的二级缓存,可以有多个sqlSession

并且二级缓存需要我们手动开启

使用

在全局配置文件中加入如下代码
在这里插入图片描述

再在需要使用的具体mapper中加入如下代码

在这里插入图片描述

mybatis默认使用的实现类是上面源码中追踪到PerpetualCache缓存类,实现了cache接口。所以这里我们也可以自己定义二级缓存的实现类,实现cache接口。

分布式下的二级缓存

mybatis怎么实现分布式缓存呢。按照上面的缓存来操作的话,只是单体缓存。

分布式缓存:多台服务器都可以访问操作缓存区

这里我们可以使用Redis结合mybatis来实现分布式缓存

使用方法如下:

1.pom文件增加依赖

<dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-redis</artifactId>
            <version>1.0.0-beta2</version>
</dependency>

2.具体使用的mappper中指定要使用的缓存实现类

<cache type="org.mybatis.caches.redis.RedisCache"></cache>

3.配置redis信息等等
在这里插入图片描述

源码分析

我们来看下redisCache实现的二级缓存

1.构造方法

从这里的代码我们可以看到,Redis实现的二级缓存使用的是jedis来进行操作的。

 public RedisCache(final String id) {
    
    
   if (id == null) {
    
    
     throw new IllegalArgumentException("Cache instances require an ID");
   }
   this.id = id;
   RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),
  redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),
  redisConfig.getDatabase(), redisConfig.getClientName());
 }
2.put get方法,从这里我们来看二级缓存的存储结构是什么?

从hset命令,很容易知道使用的是hash结构来存储缓存数据

@Override
public void putObject(final Object key, final Object value) {
    
    
  execute(new RedisCallback() {
    
    
    @Override
    public Object doWithRedis(Jedis jedis) {
    
    
      jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
      return null;
    }
  });
}

@Override
public Object getObject(final Object key) {
    
    
  return execute(new RedisCallback() {
    
    
    @Override
    public Object doWithRedis(Jedis jedis) {
    
    
      return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));
    }
  });
}

猜你喜欢

转载自blog.csdn.net/weixin_44969687/article/details/115331591