Código-fonte do cache Mybatis e aplicativo de cache distribuído

Mybatis

processo de carregamento mybatis

  1. O arquivo de configuração global Config.xml é analisado e colocado no objeto de configuração. A configuração de anotação e os arquivos de configuração SQL em Java são encapsulados no objeto de instrução e armazenados na memória, que também é mantida pelo objeto de configuração

  2. Carregamento do arquivo de mapeamento: o objeto mappedStatement analisa o sql, e cada objeto sql corresponde a um statementID e armazena o sql analisado no mapa, a chave é o statementId e o valor é o sql analisado, que é armazenado no sqlsource

  3. sqlSource armazena informações de sql e também fornece funções de análise de sql

    dynamicsqlSource: $ {} analise este tipo de sql dinâmico

    RawsqlSource: # {} Analise este tipo de sql dinâmico

    StaticsqlSource: Depois de analisar os dois tipos de sql acima, armazene-o em sqlSource, por meio deste sqlSource pode obter BoundSql
    combinado com o código-fonte staticSqlSource, será mais fácil de entender

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; sql analisado

    Listar mapeamentos de parâmetros; correspondente aos parâmetros de entrada

    sqlNode: estrutura em árvore

    sqlSource armazena informações de sql, que é uma coleção de sqlNode

Cache de primeiro nível Mybatis

Mybatis tem um cache de primeiro nível e um cache de segundo nível

Cache de nível 1

Introdução

O cache de primeiro nível é baseado em 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);

O código acima usa o mesmo demoMapper para duas consultas consecutivas. A primeira consulta realmente conectará e consultará o banco de dados e, na segunda vez, irá para o cache de primeiro nível de mybatis para buscá-lo. Ou seja, após a primeira consulta, o resultado da consulta será armazenado no cache de primeiro nível e, em seguida, a consulta será pesquisada no cache de primeiro nível novamente após a consulta e será retornado se corresponder, e será consultado no banco de dados se não corresponder.

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);

No código acima, após a primeira consulta, os dados são atualizados novamente e, em seguida, a segunda consulta é realizada.

Processo :

  1. A primeira consulta se conecta ao banco de dados e armazena os resultados da consulta no cache de primeiro nível.
  2. Quando os dados são atualizados, a operação de atualização limpa o cache de primeiro nível, de forma que o cache correspondente no cache de primeiro nível seja esvaziado neste momento.
  3. Na segunda consulta, ele não será obtido no cache de primeiro nível, portanto, vá até o banco de dados para consultar.

Análise de código fonte

Encontramos a interface SqlSesion. Aquela que parece ter um efeito no cache de primeiro nível é um método clearCache ()

Insira a descrição da imagem aqui

1. Insira a implementação do método () de clearCache DefaultSqlSession

Insira a descrição da imagem aqui

O código-fonte é o seguinte: Para facilitar a visualização, não vou tirar uma captura de tela e colar o código-fonte diretamente

  public void clearCache() {
    
    
    this.executor.clearLocalCache();
  }
2. Entre na interface do Executor

Insira a descrição da imagem aqui

3. Insira a implementação do método clearLocalCache () no Executor

O código-fonte implementado na classe BaseExecutor é o seguinte

 public void clearLocalCache() {
    
    
    if (!this.closed) {
    
    
      this.localCache.clear();
      this.localOutputParameterCache.clear();
    }
  }
4. Insira o método localCache.clear ():

O código-fonte da classe PerpetualCache é o seguinte

public void clear() {
    
    
    this.cache.clear();
 }
5.cache.clear (), qual é o objeto cache?

Olhando para o código-fonte abaixo, o objeto original que executa clear é apenas um objeto de mapa simples. A partir daqui, podemos saber que o cache de primeiro nível de mybatis é armazenado usando o Mapa.
Insira a descrição da imagem aqui

-------------------------------------------------- --------------------Linha divisória---------------------------- ------------------------------------------------

O código-fonte apresentado acima é a limpeza do cache de primeiro nível. E quanto à criação?

Vamos revisar o processo do código-fonte acima

SqlSession – DefaultSqlSession – Executor – BaseExecutor – PerpetualCache

O que é Executor? O executor, sabemos que o executor é utilizado para executar requisições SQL, já que o esvaziamento do cache é executado no exctor, a operação de armazenar os resultados e armazenar os resultados após a execução da requisição SQL também deve ser executada aqui.

1. O método no Executor, o método para criar um cache é o método createCacheKey ()

Insira a descrição da imagem aqui

2. Vá para a classe de implementação BaseExecutor para ver a implementação específica

O código-fonte é o seguinte:

  @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);
 }

Observe que esta linha de código CacheKey key = createCacheKey (ms, parameter, rowBounds, boundSql);

O código-fonte do método createCacheKey () aqui: O conteúdo é gravado diretamente no comentário do seguinte código

  @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;
  }

Resumindo: o cache de primeiro nível é criado aqui. O que está armazenado nele foi comentado no comentário acima, e irei listá-lo abaixo

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);

Cache secundário

Introdução

O cache secundário também armazena os resultados da consulta e o SQL correspondente. Após a consulta ser concluída, ela é colocada no cache e a consulta pode ser recuperada dele novamente.

Então, qual é a diferença entre ele e o cache de primeiro nível

O cache de primeiro nível foi introduzido acima e é baseado em SQLSession, ou seja, a mesma sqlSession.

O cache de segundo nível é baseado no nameSpace do mapeador, que é o cache de segundo nível de um mapeador (ou vários mapeadores com o mesmo nameSpace). Pode haver várias sqlSessions.

E o cache de segundo nível precisa ser ativado manualmente

usar

Adicione o seguinte código no arquivo de configuração global
Insira a descrição da imagem aqui

Em seguida, adicione o seguinte código ao mapeador específico que você precisa usar

Insira a descrição da imagem aqui

A classe de implementação usada por mybatis por padrão é a classe de cache PerpetualCache rastreada do código-fonte acima, que implementa a interface de cache. Portanto, aqui também podemos definir a classe de implementação do cache de segundo nível e implementar a interface do cache.

Cache secundário distribuído

Como o mybatis implementa o cache distribuído? Se você operar de acordo com o cache acima, será apenas um único cache.

Cache distribuído: vários servidores podem acessar o cache de operação

Aqui podemos usar Redis combinado com mybatis para implementar o cache distribuído

O método de uso é o seguinte:

1. Dependência do aumento do arquivo Pom

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

2. Especifique a classe de implementação de cache a ser usada no mapeador usado

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

3. Configure as informações do redis, etc.
Insira a descrição da imagem aqui

Análise de código fonte

Vamos dar uma olhada no cache secundário implementado por redisCache

1. Método de construção

Pelo código aqui, podemos ver que o cache secundário implementado pelo Redis usa jedis para operar.

 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 get.A partir daqui, qual é a estrutura de armazenamento do cache secundário?

A partir do comando hset, é fácil saber que a estrutura hash é usada para armazenar dados em cache

@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()));
    }
  });
}

Acho que você gosta

Origin blog.csdn.net/weixin_44969687/article/details/115331591
Recomendado
Clasificación