springboot之使用redis为mybatis二级缓存

14 篇文章 0 订阅
9 篇文章 1 订阅

一.前言

Mybatis默认开启一级缓存,二级缓存需要手动开启。Mybatis的二级缓存是多个SqlSession共享的,作用于是mapper配置文件中同一个namespace,不同的SqlSession两次执行相同namespace下的sql语句且参数如果也一样,则通过缓存查询的cacheKey也是一样的,则最终执行的sql语句是相同的。每次查询都会先看看缓存中是否有对应查询结果,如果有就从缓存拿,如果没有就执行sql语句从数据库中读取,从而提高查询效率。本文提使用redis的hash结构存储来实现mybatis的二级缓存方案。

二.缓存机制分析

1.概述
  • 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等
  • 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear
  • MyBatis 的缓存采用了delegate机制 及 装饰器模式设计
2.Executor分析

Executor是跟SqlSession绑定在一起的,每一个SqlSession都拥有一个新的Executor对象,由Configuration创建。Executor接口代码如下

public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;

    int update(MappedStatement var1, Object var2) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

    <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

    List<BatchResult> flushStatements() throws SQLException;

    void commit(boolean var1) throws SQLException;

    void rollback(boolean var1) throws SQLException;

    CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);

    boolean isCached(MappedStatement var1, CacheKey var2);

    void clearLocalCache();

    void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);

    Transaction getTransaction();

    void close(boolean var1);

    boolean isClosed();

    void setExecutorWrapper(Executor var1);
}
Executor类结构图:

在这里插入图片描述

3.Executor源码浅析

Executor接口的实现类有2个,一个是BaseExecutor,另外一个是CachingExecutor。Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是一个Executor的装饰器,给一个Executor增加了缓存的功能。此时可以看做是对Executor类的一个增强,故使用装饰器模式是不错的选择。

Executor是跟SqlSession绑定在一起的,每一个SqlSession都拥有一个新的Executor对象,由Configuration创建。代码如下

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }

        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

1.BaseExecutor

  • SimpleExecutor
    每执行一次doUpdate或doQuery,就开启一个Statement对象,用完马上关闭Statement对象。(可以是Statement或PrepareStatement对象)
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;

        int var6;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }

        return var6;
    }

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }
  • ReuseExecutor
    执行doUpdate或doQuery
 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
        return handler.update(stmt);
    }

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    }

以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;
        //通过sql作为key查找是否存在
        if (this.hasStatementFor(sql)) {
            stmt = this.getStatement(sql);
            this.applyTransactionTimeout(stmt);
        } else {
            Connection connection = this.getConnection(statementLog);
            stmt = handler.prepare(connection, this.transaction.getTimeout());
            this.putStatement(sql, stmt);
        }

        handler.parameterize(stmt);
        return stmt;
    }
  
  //通过sql作为key,判断是否存在
  private boolean hasStatementFor(String sql) {
        try {
            return this.statementMap.keySet().contains(sql) && !((Statement)this.statementMap.get(sql)).getConnection().isClosed();
        } catch (SQLException var3) {
            return false;
        }
    }	
	
	
  //存放于statementMap中	
  private void putStatement(String sql, Statement stmt) {
        this.statementMap.put(sql, stmt);
    }
  • BatchExecutor
    执行doUpdate(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的(可以是Statement或PrepareStatement对象)
 public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
        BoundSql boundSql = handler.getBoundSql();
        String sql = boundSql.getSql();
        Statement stmt;
        if (sql.equals(this.currentSql) && ms.equals(this.currentStatement)) {
            int last = this.statementList.size() - 1;
            stmt = (Statement)this.statementList.get(last);
            this.applyTransactionTimeout(stmt);
            handler.parameterize(stmt);
            BatchResult batchResult = (BatchResult)this.batchResultList.get(last);
            batchResult.addParameterObject(parameterObject);
        } else {
            Connection connection = this.getConnection(ms.getStatementLog());
            stmt = handler.prepare(connection, this.transaction.getTimeout());
            handler.parameterize(stmt);
            this.currentSql = sql;
            this.currentStatement = ms;
            this.statementList.add(stmt);
            this.batchResultList.add(new BatchResult(ms, sql, parameterObject));
        }

        handler.batch(stmt);
        return -2147482646;
    }

2.CachingExecutor
先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。代码如下:

 public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        this.flushCacheIfRequired(ms);
        return this.delegate.update(ms, parameterObject);
    }
	
	//通过cacheKey获取缓存数据
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        this.flushCacheIfRequired(ms);
        return this.delegate.queryCursor(ms, parameter, rowBounds);
    }
	
	//缓存判断,如果存在则返回缓存中的数据
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
            this.flushCacheIfRequired(ms);
            //判断是否为isUserCache为true 且resusultHandle不为空
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                //通过key,获取缓存中的数据
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                //缓存不存在,直接去数据库查询
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }
				//存在,直接返回缓存数据
                return list;
            }
        }
		//直接去数据库查询	
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

三.实例分析

本实例采用的是springboot2.1.1

1.poml文件添加redis与mybatis包
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
2.application.xml配置

配置端口8016,mysql与redis以及mybatis的配置

server:
  port: 8016
spring:
  datasource:
    driver-class-name:  com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/lss0555?serverTimezone=UTC&characterEncoding=utf-8
    username: root
    password: 888888
    dbcp2:
      validation-query: select 1 from dual
      test-on-borrow: true
      min-evictable-idle-time-millis: 600000
      time-between-eviction-runs-millis: 300000
  redis:
    host: 127.0.0.1
    port: 6379
    password: 888888
    timeout: 10000
    jedis:
      pool:
        max-idle: 8
        min-idle: 10
        max-active: 100
        max-wait: -1


mybatis:
  configuration:
    mapUnderscoreToCamelCase: false
    cache-enabled: true
  type-aliases-package: com.example.mybatistest.model
  mapper-locations: classpath*:mapping/*.xml

mybatis开启缓存使用: cache-enabled: true

3.定义SpringContextHolder

实现了Spring的ApplicationContextAware来获取ApplicationContext,从中获取容器的bean

@Component
public class SpringContextHolder  implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextHolder.applicationContext = applicationContext; // NOSONAR
    }

    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        checkApplicationContext();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        return (T) applicationContext.getBeansOfType(clazz);
    }

    /**
     * 清除applicationContext静态变量.
     */
    public static void cleanApplicationContext() {
        applicationContext = null;
    }

    private static void checkApplicationContext() {
        if (applicationContext == null) {
            throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
        }
    }
}
4.实现ibatis的Cache接口 RedisCache
public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);

    // 读写锁
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

    private RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean("redisTemplate");

    private String id;

    public RedisCache(){

    }

    public RedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.info("Redis Cache id " + id);
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        if (value != null) {
            // 向Redis中添加数据,有效时间是1天
            logger.info("缓存:新增缓存  key:"+key);
            redisTemplate.opsForValue().set(key.toString(), value, 1, TimeUnit.DAYS);
        }
    }

    @Override
    public Object getObject(Object key) {
        try {
            if (key != null) {
                logger.info("缓存:获取  key:"+key);
                Object obj = redisTemplate.opsForValue().get(key.toString());
                return obj;
            }
        } catch (Exception e) {
            logger.error("redis 异常:"+e);
        }
        return null;
    }

    @Override
    public Object removeObject(Object key) {
        try {
            if (key != null) {
                logger.info("缓存:移除:"+key);
                redisTemplate.delete(key.toString());
            }
        } catch (Exception e) {
        }
        return null;
    }

    @Override
    public void clear() {
        logger.info("缓存:清空");
        try {
            Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
            if (!CollectionUtils.isEmpty(keys)) {
                redisTemplate.delete(keys);
            }
        } catch (Exception e) {
        }
    }

    @Override
    public int getSize() {
        Long size = (Long) redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize();
            }
        });
        logger.info("缓存:数量:"+size.intValue());
        return size.intValue();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }
}
5.接口测试用例

(1)dao层接口

@Mapper
public interface UserDaoMapper {
    List<User> userList();
}

(2)Mapping实现层

<mapper namespace="com.example.mybatistest.dao.UserDaoMapper">

    <cache  type="com.example.mybatistest.cache.RedisCache">
        <property name="eviction" value="LRU" />
        <property name="flushInterval" value="6000000" />
        <property name="size" value="1024" />
        <property name="readOnly" value="false" />
    </cache>
    
    <select id="userList"    resultType="com.example.mybatistest.model.User">
        select *  from `user`
    </select>
</mapper>

cache标签内属性:

  • eviction:定义缓存移除机制(算法),默认为LRU(最近最少使用),它会清除最少使用的数据,还有一种FIFO(先进先出),它会清除最先进来的数据。

  • flushInterval:定义缓存刷新周期,单位为毫秒。

  • size:标识缓存cache中容纳的最大元素,默认为1024。

  • readOnly:默认为false,可配置为true缓存只读。

注意:

  • select 默认useCache为true:使用缓存,flushCache为false:不清空缓存

  • insert、update、delete 默认flushCache为true:清空缓存

(3)Service层
UserServiceInter 接口层

public interface UserServiceInter {
   List<User> userList();
}

UserServiceImpl实现层

@Service
public class UserServiceImpl implements UserServiceInter {
    @Resource
    UserDaoMapper userDaoMapper;

    @Override
    public List<User> userList() {
        return userDaoMapper.userList();
    }
}

(4)controller层

	@GetMapping("/userList")
    public TResult getUserList(){
        TResult<User> result = new TResult<>();
        result.setList(userServiceInter.userList());
        return result;
    }

(5)测试结果
第一次访问 http://localhost:8016/userList1 控制台dubug信息如下:

2019-01-28 16:26:49.344 [http-nio-8016-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/userList2", parameters={}
2019-01-28 16:26:49.345 [http-nio-8016-exec-5] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to public com.example.mybatistest.model.TResult com.example.mybatistest.controller.UserController.userList2()
2019-01-28 16:26:49.345 [http-nio-8016-exec-5] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
2019-01-28 16:26:49.345 [http-nio-8016-exec-5] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1bd8321] was not registered for synchronization because synchronization is not active
2019-01-28 16:26:49.345 [http-nio-8016-exec-5] INFO  com.example.mybatistest.cache.RedisCache - 缓存:获取  key:-1672553611:3475886595:com.example.mybatistest.dao.UserDaoMapper.userList2:0:2147483647:select *  from `user`:SqlSessionFactoryBean
2019-01-28 16:26:49.345 [http-nio-8016-exec-5] DEBUG o.s.data.redis.core.RedisConnectionUtils - Opening RedisConnection
2019-01-28 16:26:49.347 [http-nio-8016-exec-5] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.347 [http-nio-8016-exec-5] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.347 [http-nio-8016-exec-5] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, epid=0x1] write() done
2019-01-28 16:26:49.347 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] write(ctx, AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
2019-01-28 16:26:49.348 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandEncoder - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379] writing command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.349 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] Received: 5 bytes, 1 commands in the stack
2019-01-28 16:26:49.349 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] Stack contains: 1 commands
2019-01-28 16:26:49.349 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.349 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], empty stack: true
2019-01-28 16:26:49.349 [http-nio-8016-exec-5] DEBUG o.s.data.redis.core.RedisConnectionUtils - Closing Redis Connection
2019-01-28 16:26:49.349 [http-nio-8016-exec-5] DEBUG com.example.mybatistest.dao.UserDaoMapper - Cache Hit Ratio [com.example.mybatistest.dao.UserDaoMapper]: 0.3333333333333333
2019-01-28 16:26:49.349 [http-nio-8016-exec-5] DEBUG o.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
2019-01-28 16:26:49.351 [http-nio-8016-exec-5] DEBUG o.m.spring.transaction.SpringManagedTransaction - JDBC Connection [HikariProxyConnection@21358673 wrapping com.mysql.cj.jdbc.ConnectionImpl@14e9682] will not be managed by Spring
2019-01-28 16:26:49.351 [http-nio-8016-exec-5] DEBUG c.example.mybatistest.dao.UserDaoMapper.userList2 - ==>  Preparing: select * from `user` 
2019-01-28 16:26:49.351 [http-nio-8016-exec-5] DEBUG c.example.mybatistest.dao.UserDaoMapper.userList2 - ==> Parameters: 
2019-01-28 16:26:49.354 [http-nio-8016-exec-5] DEBUG c.example.mybatistest.dao.UserDaoMapper.userList2 - <==      Total: 5
2019-01-28 16:26:49.354 [http-nio-8016-exec-5] INFO  com.example.mybatistest.cache.RedisCache - 缓存:新增缓存  key:-1672553611:3475886595:com.example.mybatistest.dao.UserDaoMapper.userList2:0:2147483647:select *  from `user`:SqlSessionFactoryBean
2019-01-28 16:26:49.354 [http-nio-8016-exec-5] DEBUG o.s.data.redis.core.RedisConnectionUtils - Opening RedisConnection
2019-01-28 16:26:49.354 [http-nio-8016-exec-5] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=SETEX, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.355 [http-nio-8016-exec-5] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=SETEX, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.355 [http-nio-8016-exec-5] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, epid=0x1] write() done
2019-01-28 16:26:49.355 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] write(ctx, AsyncCommand [type=SETEX, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
2019-01-28 16:26:49.356 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandEncoder - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379] writing command AsyncCommand [type=SETEX, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.357 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] Received: 5 bytes, 1 commands in the stack
2019-01-28 16:26:49.357 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] Stack contains: 1 commands
2019-01-28 16:26:49.357 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode AsyncCommand [type=SETEX, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:26:49.357 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=SETEX, output=StatusOutput [output=OK, error='null'], commandType=io.lettuce.core.protocol.Command], empty stack: true
2019-01-28 16:26:49.357 [http-nio-8016-exec-5] DEBUG o.s.data.redis.core.RedisConnectionUtils - Closing Redis Connection
2019-01-28 16:26:49.357 [http-nio-8016-exec-5] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1bd8321]
2019-01-28 16:26:49.357 [http-nio-8016-exec-5] DEBUG o.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
2019-01-28 16:26:49.357 [http-nio-8016-exec-5] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
2019-01-28 16:26:49.358 [http-nio-8016-exec-5] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Writing [TResult{errorCode=0, model=null, list=[User{id=1, username='22', password='dd', Salt='ee'}, User{id=2, username='dd', password='dd', Salt='dd'}, User{id=3, username='ccc', password='ccc', Salt='sd'}, User{id=4, username='d', password='d', Salt='d'}, User{id=5, username='1', password='1', Salt='11'}]}]
2019-01-28 16:26:49.359 [http-nio-8016-exec-5] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK

发现里面是有走数据库的查询操作
再次访问,log如下:

2019-01-28 16:27:54.968 [http-nio-8016-exec-7] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/userList2", parameters={}
2019-01-28 16:27:54.968 [http-nio-8016-exec-7] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to public com.example.mybatistest.model.TResult com.example.mybatistest.controller.UserController.userList2()
2019-01-28 16:27:54.968 [http-nio-8016-exec-7] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
2019-01-28 16:27:54.968 [http-nio-8016-exec-7] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@364b19] was not registered for synchronization because synchronization is not active
2019-01-28 16:27:54.969 [http-nio-8016-exec-7] INFO  com.example.mybatistest.cache.RedisCache - 缓存:获取  key:-1672553611:3475886595:com.example.mybatistest.dao.UserDaoMapper.userList2:0:2147483647:select *  from `user`:SqlSessionFactoryBean
2019-01-28 16:27:54.969 [http-nio-8016-exec-7] DEBUG o.s.data.redis.core.RedisConnectionUtils - Opening RedisConnection
2019-01-28 16:27:54.969 [http-nio-8016-exec-7] DEBUG io.lettuce.core.RedisChannelHandler - dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:27:54.969 [http-nio-8016-exec-7] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, epid=0x1] write() writeAndFlush command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:27:54.969 [http-nio-8016-exec-7] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, epid=0x1] write() done
2019-01-28 16:27:54.969 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] write(ctx, AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise)
2019-01-28 16:27:54.970 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandEncoder - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379] writing command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:27:54.971 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] Received: 298 bytes, 1 commands in the stack
2019-01-28 16:27:54.971 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xc7f50bf0, /127.0.0.1:49477 -> /127.0.0.1:6379, chid=0x1] Stack contains: 1 commands
2019-01-28 16:27:54.971 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]
2019-01-28 16:27:54.971 [lettuce-nioEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decoded AsyncCommand [type=GET, output=ValueOutput [output=[B@15219b4, error='null'], commandType=io.lettuce.core.protocol.Command], empty stack: true
2019-01-28 16:27:54.972 [http-nio-8016-exec-7] DEBUG o.s.data.redis.core.RedisConnectionUtils - Closing Redis Connection
2019-01-28 16:27:54.972 [http-nio-8016-exec-7] DEBUG com.example.mybatistest.dao.UserDaoMapper - Cache Hit Ratio [com.example.mybatistest.dao.UserDaoMapper]: 0.5
2019-01-28 16:27:54.972 [http-nio-8016-exec-7] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@364b19]
2019-01-28 16:27:54.972 [http-nio-8016-exec-7] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
2019-01-28 16:27:54.972 [http-nio-8016-exec-7] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Writing [TResult{errorCode=0, model=null, list=[User{id=1, username='22', password='dd', Salt='ee'}, User{id=2, username='dd', password='dd', Salt='dd'}, User{id=3, username='ccc', password='ccc', Salt='sd'}, User{id=4, username='d', password='d', Salt='d'}, User{id=5, username='1', password='1', Salt='11'}]}]
2019-01-28 16:27:54.975 [http-nio-8016-exec-7] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK

发现这时候已经没有走数据库查询,直接从redis缓存中拿出数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值