springboot集成redis

Springboot集成redis

redis设置:

  • 修改redis服务器的配置文件
vim /usr/local/redis/bin/redis.conf

bind 0.0.0.0 
protected-mode no
  • 重新启动redis
systemctl restart redis.service   #重新启动服务

注意:服务器的话需要设置安全组开放端口

1.导入依赖

 <!--  springboot-redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.全局配置文件中配置redis信息

# 应用名称
spring:
  application:
    name: springboot-redis01
  redis:
    host: 47.93.190.68
    port: 6379
    database: 0
    jedis:
      #redis连接池信息
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
        max-wait: -1

server:
  servlet:
    context-path: /redis01

通过以上配置后springboot就为我们提供了RedisTemplate和StringRedisTemplate(对key,value形式的都是string操作)

相关操作

StringRedisTemplate

对string类型的操作方式:

//操作string
stringRedisTemplate.opsForValue().set("string01","string");
System.out.println(stringRedisTemplate.opsForValue().get("string01"));
//操作hash类型
stringRedisTemplate.opsForHash().put("hash-user","username","name");
stringRedisTemplate.opsForHash().put("hash-user","userage","age");
//操作list
stringRedisTemplate.opsForList().rightPushAll("list","l1","l2");
//操作set
stringRedisTemplate.opsForSet().add("set01", "daaa");
//操作zset
stringRedisTemplate.opsForZSet().add("zset01", "zset", 1);
  • 绑定一个键(k)进行操作:通常是对一个key进行多次的操作时使用。

//没有key就会添加key
BoundValueOperations<String, String> boundValueOps = stringRedisTemplate.boundValueOps("name");
//追加的方式
boundValueOps.append("hahaha");
boundValueOps.append("hahaha");
System.out.println(boundValueOps.get());

//重新赋值
boundValueOps.set("hanwei hello");
System.out.println(boundValueOps.get());

注意: 一旦绑定key之后后续根据返回对象的操作都是基于这个key的操作

redisTemplate

  • 一般操作是将对象存储在redis中。
@Test
    public void testRedisTemplate(){
        //通过这种方式是获取不到stringRedisTemplate方式设置的值的
        System.out.println(redisTemplate.opsForValue().get("name"));//null

        //设置key的序列化方式为string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //以下是设置value的序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        //jackson
        ObjectMapper objectMapper = new ObjectMapper();
        //转换json格式的时候将原始类型保留,这样在反序列化的时候就能知道对应的类型信息
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        //修改存储在redis中的日期格式
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);


        User user = new User();
        user.setId(22).setName("hanhan").setBir(new Date());
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user").toString());

        //hash类型的是(key,(key,val)),所以需要单独设置序列化方式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.opsForHash().put("user1","user",user);
        System.out.println(redisTemplate.opsForHash().get("user1", "user"));
    }

注意redistemplate默认使用的是jdk的序列化,存储到redis中会是下面的情况:image-20201215184546111,所以我们将key的序列化改为string类型的,将value改为json序列化。

mybatis中使用redis

mybatis自身缓存存在问题,本地缓存local cache

​ 1.本地缓存存储在当前运行的jvm内存中,如果缓存数据过多会占用一定jvm内存,导致应用运行缓存。

​ 2.不能在分布式系统中做到缓存共享。

重写mybatis cache 使用redis作分布式缓存

如果使用mybatis的二级缓存只需要在mapper文件中添加,二级缓存的作用范围是每个maper。

自定义redis缓存作为mybatis的缓存

  • 导入依赖
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--  springboot-redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

<!--        mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

注意:mybatis的xml文件如果再java文件下的话,一定要加resources将xml发布

  • 自定义RedisCache类
package com.han.cache;

import com.han.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author M.han
 * @title: RedisCache
 * @projectName springboot-redis
 * @description: TODO
 * @date 2020/12/1520:18
 */
public class RedisCache implements Cache {
    private String id;

    public RedisCache(String id) {
        System.out.println("当前加入缓存的id==》" + id);
        this.id = id;
    }

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

    /***
     * 放入缓存
     * @param key
     * @param val
     */
    @Override
    public void putObject(Object key, Object val) {
        System.out.println("KEY:" + key);
        System.out.println("val:" + val);
        //获取对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //存储在Redis中,注意上面对key使用了string序列化,所以传入的key是string类型的
        redisTemplate.opsForValue().set(key.toString(), val);
    }

    /***
     * 从缓存中获取
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        System.out.println("从缓存中读取了=》" + key);
        //获取对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForValue().get(key.toString());
    }

    /***
     *  删除缓存中指定key的数据,在mybatis中没有使用该方法
     */
    @Override
    public Object removeObject(Object o) {
        return null;
    }

    /***
     * 清空缓存
     */
    @Override
    public void clear() {
        //获取对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.delete(redisTemplate.keys("*"));
    }

    /***
     * 缓存的命中概率
     * @return
     */
    @Override
    public int getSize() {
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return  redisTemplate.keys("*").size();
    }

    /***
     * 读写锁,可以为空,写写互斥,读写互斥,读读共享
     * @return
     */
    @Override
    public ReadWriteLock getReadWriteLock() {
        return new ReentrantReadWriteLock();
    }
}

  • 因为缓存类是mybatis使用而没有交给spring容器托管(因为在mybatis执行这个的时候要传入id),但是在RedisCache类中需要注入RedisTemplate,所以自定义一个获取spring工厂中的bean的工具类。
package com.han.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author M.han
 * @title: ApplicationUtils
 * @projectName springboot-redis
 * @description: 获取spring中的bean
 * @date 2020/12/15 20:26
 */
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    /***
     *
     * @param applicationContext 已经创建好的工厂对象
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtils.applicationContext = applicationContext;
    }
    //下面是自定义获取bean

    /***
     *根据id来获取bean
     */
    public static Object getBean(String id){
        return applicationContext.getBean(id);
    }

    /***
     * 通过类型获取bean
     * @param clazz
     * @return
     */
    public static Object getBean(Class clazz){
        return applicationContext.getBean(clazz);
    }

    /***
     * 根据id和类型同时获取bean
     * @param id
     * @param clazz
     * @return
     */
    public static Object getBean(String id,Class clazz){
        return applicationContext.getBean(id,clazz);
    }
}

  • mapper.xml文件中指定redis缓存
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.han.dao.UserMapper">
    <!--使用自定义的redis缓存实现-->
    <cache type="com.han.cache.RedisCache"/>

    <select id="findAll" resultType="com.han.pojo.User">
        select id,username,password from user
    </select>
</mapper>

  • yaml文件配置信息
# 应用名称
spring:
  application:
    name: springboot-redis01
  redis:
    host:  
    port:  
    database: 0
    jedis:
      #redis连接池信息
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
        max-wait: -1
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://--------/db?characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: 

server:
  servlet:
    context-path: /redis01
mybatis:
  mapper-locations: classpath*:com/han/dao/*.xml
  type-aliases-package: com.han.pojo
logging:
  level:
    root: info
    com.han.dao: debug

注意启动文件上添加MapperScan注解扫描。

通过以上配置,只要redis缓存中有该数据,mybatis就不会执行查询,而是从缓存中取数据。其他操作都是清空缓存。

以上的缓存模型是有问题的,因为无论是哪个namespace下执行清空缓存的方法,所以可以改进为hash结构的存储,每个namespace作为一个key,然后下面的数据存放在value中的key和val中

  • 修改为hash结构的存储
package com.han.cache;

import com.han.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author M.han
 * @title: RedisCache
 * @projectName springboot-redis
 * @description: TODO
 * @date 2020/12/1520:18
 */
public class RedisCache implements Cache {
    private String id;

    public RedisCache(String id) {
        System.out.println("当前加入缓存的id==》" + id);
        this.id = id;
    }

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

    /***
     * 放入缓存
     * @param key
     * @param val
     */
    @Override
    public void putObject(Object key, Object val) {
        System.out.println("KEY:" + key);
        System.out.println("val:" + val);
        //获取对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //存储在Redis中,注意上面对key使用了string序列化,所以传入的key是string类型的
        //使用hash
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.opsForHash().put(id.toString(),key.toString(),val);
    }

    /***
     * 从缓存中获取
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        System.out.println("从缓存中读取了=》" + key);
        //获取对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate.opsForHash().get(id.toString(),key.toString());
    }

    /***
     *  删除缓存中指定key的数据,在mybatis中没有使用该方法
     */
    @Override
    public Object removeObject(Object o) {
        return null;
    }

    /***
     * 清空缓存
     */
    @Override
    public void clear() {
        //获取对象
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.delete(id.toString());
    }

    /***
     * 缓存的命中概率
     * @return
     */
    @Override
    public int getSize() {
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //获取当前id下的缓存数据,hash的size返回的是long,转为int

        return redisTemplate.opsForHash().size(id.toString()).intValue();
    }

    /***
     * 读写锁,可以为空,写写互斥,读写互斥,读读共享
     * @return
     */
    @Override
    public ReadWriteLock getReadWriteLock() {
        return new ReentrantReadWriteLock();
    }
}

注意: 

​ 1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生dao数据过时。
  2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。
  总之,操作与查询在同一个namespace下的查询才能缓存,其他namespace下的查询都可能出现问题。

针对上面的问题解决办法如下:

使用mybatis的标签。作用: 让多个有关联关系的DAO共享同一个缓存,使用方法如下:

UserDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.han.dao.UserMapper">
    <!--使用自定义的redis缓存实现-->
    <cache type="com.han.cache.RedisCache"/>

    <select id="findAll" resultType="com.han.pojo.User">
        select id,username,password from user
    </select>
    <insert id="save" parameterType="com.han.pojo.User">
        insert into user(username,password) values (#{username},#{password})
    </insert>
</mapper>

DeptDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.han.dao.DeptMapper">
    <!--使用UserMapper的缓存,也就是这个下面的缓存会存放在UserMapper下,注意这里不写<cache/>标签-->
    <cache-ref namespace="com.han.dao.UserMapper"/>

    <select id="findAll" resultType="com.han.pojo.User">
        select id,username,password from user
    </select>
    <insert id="save" parameterType="com.han.pojo.User">
        insert into user(username,password) values (#{username},#{password})
    </insert>
</mapper>

这样处理后DeptMapper会使用UserMapper的缓存,也就是这两个的缓存都存在一个namespace下。

posted @ 2020-12-15 20:56  MrHanhan  阅读(237)  评论(0编辑  收藏  举报