收款定制开发Spring Boot 集成 Redis 配置 MyBatis 二级缓存

目录

写在前面

收款定制开发文中项目基于,收款定制开发如果你是新手,收款定制开发可以跟着上期内容先动收款定制开发手把项目框架搭建起来,收款定制开发然后在结合本期内容继收款定制开发续深入学习,收款定制开发这样会有更好的效果。

收款定制开发接下来正式介绍本文,收款定制开发本文讲的是在 Spring Boot 收款定制开发项目中集成使用 Redis,并使用 Redis 实现 的二级缓存。使用场景就是在高并发的环境下,大量的查询直接落入DB,会导致数据库宕机,从而导致服务雪崩的情况。我们使用Redis作为MyBatis二级缓存,可以充分的缓解数据库的压力,从而达到服务的高可用。

源码获取

源码在 GitCodeGitHub 以及 码云,持续更新中,别忘了 star 喔~

GitCode

https://gitcode.net/qq_41779565/my-project.git
  • 1

GitHub

https://github.com/micromaples/my-project
  • 1

码云Gitee

https://gitee.com/micromaple/my-project
  • 1

如果不会使用 Git 的小伙伴,我已经上传到了CSDN,,有会员的小伙伴直接下载即可,没有会员的小伙伴私聊我Mybatis二级缓存可直接获取

一、MyBatis缓存机制

Mybatis 提供了查询缓存来缓存数据,以提高查询效率。缓存级别分为一级缓存二级缓存

1.1、一级缓存

一级缓存为 SqlSession 级别的缓存,也就是会话级缓存,是基于HashMap的本地缓存,当同一个SqlSession执行两次相同的SQL语句时,第一次执行完后会将数据库中查询到的结果写到缓存,第二次查询时直接从缓存中读取,不经过数据库了。一级缓存默认是开启的。

1.2、

二级缓存为mapper级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 去操作数据库得到数据会存在二级缓存区域,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。其作用域是 mapper 的同一个 namespace,不同的 sqlSession 两次执行相同 namespace下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存需要在 setting 全局参数中配置开启二级缓存。

二、集成Redis

2.1、安装Redis

使用Docker Compose 安装Redis。docker-compose.yml内容如下:

version: '3.1'services:  redis:    image: redis:6.2.4    container_name: redis    restart: always    command: redis-server --requirepass 123456    ports:      - '6379:6379'    volumes:      - ./data:/data    environment:      TZ: Asia/Shanghai
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

安装启动完成后,可使用Redis连接工具测试

2.2、项目引入Redis

2.2.1、Maven依赖

<dependency>	<groupId>org.springframework.boot</groupId>	<artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>	<groupId>org.apache.commons</groupId>	<artifactId>commons-pool2</artifactId></dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

额外引入commons-pool2是因为data-redis底层Redis连接池基于apache commons-pool2开 发,不加入依赖会报ClassNotFoundException

2.2.2、配置application.yml

spring:  redis:    host: 192.168.110.158    port: 6379    password: 123456    lettuce:      pool:        #最大允许连接数        max-active: 100        #最小空闲连接数,最少准备5个可用连接在连接池候着        min-idle: 5        #最大空闲连接数,空闲连接超过10个后自动释放        max-idle: 10        #当连接池到达上限后,最多等待30秒尝试获取连接,超时报错        max-wait: 30000    timeout: 2000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2.2.3、配置序列化规则

RedisTemplateConfiguration配置类如下:

package com.micromaple.my.project.server.config;import com.fasterxml.jackson.annotation.JsonInclude;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;/** * RedisTemplate配置 * Title: RedisTemplateConfiguration * Description: * * @author Micromaple */@Configurationpublic class RedisTemplateConfiguration {        /**     * redisTemplate     *     * @param redisConnectionFactory     * @return     */    @Bean    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory                                                               redisConnectionFactory) {        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();        redisTemplate.setConnectionFactory(redisConnectionFactory);        // 使用Jackson2JsonRedisSerialize 替换默认序列化        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);        ObjectMapper objectMapper = new ObjectMapper();        //对于Null值不输出        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);        // 设置key和value的序列化规则        redisTemplate.setKeySerializer(new StringRedisSerializer());        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);        // 设置hashKey和hashValue的序列化规则        redisTemplate.setHashKeySerializer(new StringRedisSerializer());        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);        //afterPropertiesSet和init-method之间的执行顺序是afterPropertiesSet 先执行,init - method 后执行。        redisTemplate.afterPropertiesSet();        return redisTemplate;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

三、配置二级缓存

配置实现MyBatis二级缓存的方式有多种,比如:EhCacheJBossCacheRedis,其核心原理就是客户端实现 MyBatis 提供的Cache 接口,并重写其中的方法,达到二级缓存的效果。

本文以 Redis 为例。

2.1、开启二级缓存

application.yml 中增加如下配置:

# 开启MyBatis二级缓存mybatis:  configuration:    cache-enabled: true
  • 1
  • 2
  • 3
  • 4

如果使用的是 MyBatis-Plus ,则使用如下配置:

# MyBatis-Plus开启二级缓存mybatis-plus:  configuration:    cache-enabled: true
  • 1
  • 2
  • 3
  • 4

2.2、自定义缓存类

MybatisRedisCache 缓存工具类如下:

package com.micromaple.my.project.server.utils;import com.micromaple.my.project.server.config.ApplicationContextHolder;import org.apache.commons.collections.CollectionUtils;import org.apache.ibatis.cache.Cache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.redis.core.RedisTemplate;import java.util.Set;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/** * MybatisRedisCache 缓存工具类 * Title: MybatisRedisCache * Description: * * @author Micromaple */public class MybatisRedisCache implements Cache {    private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();    private final String id; // cache instance id    private RedisTemplate redisTemplate;    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间    public MybatisRedisCache(String id) {        if (id == null) {            throw new IllegalArgumentException("Cache instances require an ID");        }        this.id = id;    }    @Override    public String getId() {        return id;    }    /**     * Put query result to redis     *     * @param key     * @param value     */    @Override    public void putObject(Object key, Object value) {        try {            redisTemplate = getRedisTemplate();            if (value != null) {                redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);            }            logger.debug("Put query result to redis");        } catch (Throwable t) {            logger.error("Redis put failed", t);        }    }    /**     * Get cached query result from redis     *     * @param key     * @return     */    @Override    public Object getObject(Object key) {        try {            redisTemplate = getRedisTemplate();            logger.debug("Get cached query result from redis");            return redisTemplate.opsForValue().get(key.toString());        } catch (Throwable t) {            logger.error("Redis get failed, fail over to db", t);            return null;        }    }    /**     * Remove cached query result from redis     *     * @param key     * @return     */    @Override    @SuppressWarnings("unchecked")    public Object removeObject(Object key) {        try {            redisTemplate = getRedisTemplate();            redisTemplate.delete(key.toString());            logger.debug("Remove cached query result from redis");        } catch (Throwable t) {            logger.error("Redis remove failed", t);        }        return null;    }    /**     * Clears this cache instance     */    @Override    public void clear() {        redisTemplate = getRedisTemplate();        Set<String> keys = redisTemplate.keys("*:" + this.id + "*");        if (!CollectionUtils.isEmpty(keys)) {            redisTemplate.delete(keys);        }        logger.debug("Clear all the cached query result from redis");    }    /**     * This method is not used     *     * @return     */    @Override    public int getSize() {        return 0;    }    @Override    public ReadWriteLock getReadWriteLock() {        return readWriteLock;    }    private RedisTemplate getRedisTemplate() {        if (redisTemplate == null) {            redisTemplate = ApplicationContextHolder.getBean("redisTemplate");        }        return redisTemplate;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

ApplicationContextHolder如下:

package com.micromaple.my.project.server.config;import org.apache.commons.lang3.Validate;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.DisposableBean;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;/** * Spring bean的工具类 * Title: ApplicationContextHolder * Description: * * @author Micromaple */@Componentpublic class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {    private static final Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class);    private static ApplicationContext applicationContext;    /**     * 获取存储在静态变量中的 ApplicationContext     *     * @return     */    public static ApplicationContext getApplicationContext() {        assertContextInjected();        return applicationContext;    }    /**     * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型     *     * @param name     * @param <T>     * @return     */    public static <T> T getBean(String name) {        assertContextInjected();        return (T) applicationContext.getBean(name);    }    /**     * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型     *     * @param clazz     * @param <T>     * @return     */    public static <T> T getBean(Class<T> clazz) {        assertContextInjected();        return applicationContext.getBean(clazz);    }    /**     * 实现 DisposableBean 接口,在 Context 关闭时清理静态变量     *     * @throws Exception     */    public void destroy() throws Exception {        logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext);        applicationContext = null;    }    /**     * 实现 ApplicationContextAware 接口,注入 Context 到静态变量中     *     * @param applicationContext     * @throws BeansException     */    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        ApplicationContextHolder.applicationContext = applicationContext;    }    /**     * 断言 Context 已经注入     */    private static void assertContextInjected() {        Validate.validState(applicationContext != null, "applicationContext 属性未注入,请在 spring-context.xml 配置中定义 SpringContext");    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

2.3、增加注解

在 Mapper 接口中增加 @CacheNamespace(implementation = MybatisRedisCache.class) 注解,声明需要使用二级缓存。

package com.micromaple.my.project.server.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.micromaple.my.project.server.domain.SysUser;import com.micromaple.my.project.server.utils.MybatisRedisCache;import org.apache.ibatis.annotations.CacheNamespace;/** * <p> * 用户表 Mapper 接口 * </p> * * @author Micromaple * @since 2022-09-21 21:51:15 */@CacheNamespace(implementation = MybatisRedisCache.class)public interface SysUserMapper extends BaseMapper<SysUser> {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.4、测试验证

访问查询所有用户接口http://localhost:8899/sys-user/get/all

访问完成后,我们打开Redis查询工具,可以看到已经将我们查询出来的数据缓存起来了。效果图如下:

接着,我们再次访问查询所有用户接口,我们可以在控制台日志中看到,第二次查询并没有走数据库,而是直接在Redis中取出来了

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发