SpringBoot与Redis

2022-11-15 13:34:29 浏览数 (1)

使用 spring-data-redis 访问Redis

“spring-data-redis” 是 Spring 框架为 Redis 提供的简化抽象。底层可以支持Jedis、Lettuce 等客户端API(Spring Boot 2.x 后Lettuce为默认客户端API),并提供RedisTemplatehe、Repository和整合Spring缓存等多种简便的使用方式。

1 使用 RedisTemplate

(1)创建SpringBoot项目,添加redis支持

代码语言:javascript复制
        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis</artifactId>

        </dependency>

(2)配置 Redis 连接

代码语言:javascript复制
spring:

  #redis配置连接

  redis:

    database: 0

    host: localhost

    port: 6379

    password: 1234

    timeout: 120000

  # 配置MySQL数据源和JPA(以下配置与redis无关)

  datasource:

    url: jdbc:mysql://localhost:3306/MyCinema?serverTimezone=GMT+8

    username: root

    password: 1234

  jpa:

    show-sql: true

    hibernate:

      naming:

        implicit-strategy:  org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl

        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

(3)使用默认的 StringRedisTemplate

代码语言:javascript复制
@RunWith(SpringRunner.class)

@SpringBootTest

public class RedisTemplateTest {

    @Autowired

    private RedisTemplate<String, String> strRedisTemplate;

    @Test

    public void testStringRedisTemplate() {

        strRedisTemplate.opsForValue().set("timeStr", new Date().toLocaleString(), 1, TimeUnit.MINUTES);

        String time = strRedisTemplate.opsForValue().get("timeStr");

        System.err.println(time);

    }

}

“spring-boot-starter-data-redis” 默认提供了 StringRedisTemplate 实现,可以直接实现String型KV数据的保存。使用RedisTemplate读写数据,需要选择一个Operations操作,针对不同的数据类型(如string、hash、set、zset等),RedisTemplate提供了不同的操作方法,返回不同的Operations操作对象。

redisTemplate.opsForValue();    //操作字符串,返回ValueOperations对象

redisTemplate.opsForHash();     //操作hash,返回HashOperations对象

redisTemplate.opsForList();     //操作list,返回ListOperations对象

redisTemplate.opsForSet();      //操作set,返回SetOperations对象

redisTemplate.opsForZSet();     //操作有序set,返回ZSetValueOperations对象

如下面的单元测试所示:我们向 Redis 放入了一个key为timeStr的当前时间字符串,并设置了过期时间为1分钟。

(4)定义自己的对象型RedisTemplate

“spring-boot-starter-data-redis” 没有提供保存value为对象的RedisTemplate,但可以简单的自定义一个。

代码语言:javascript复制
@SpringBootApplication

public class BootRedisCacheDemoApplication {

   

    @Bean

    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){

        RedisTemplate<String, Object> tmpl = new RedisTemplate<String, Object>();   //创建RedisTemplate

        tmpl.setConnectionFactory(connectionFactory);                               //设置连接工厂

        tmpl.setKeySerializer(RedisSerializer.string());        //把key的序列化器设置为String序列化器

        tmpl.setValueSerializer(RedisSerializer.java());        //把key的序列化器设置为JDK序列化器

        tmpl.setHashKeySerializer(RedisSerializer.string());

        tmpl.setHashValueSerializer(RedisSerializer.java());

        return tmpl;

    }

    ...省略其他代码...

}

定义RedisTemplate对象时,应注意设置Redis序列化器。Redis实际上只能存放字符串型数据,如果要把Java对象保存到Redis中就需要把对象序列化成string再保存。spring-data-redis为我们提供了三种序列化器,他们都派生自RedisSerializer基类。

序列化器

工厂

描述

StringRedisSerializer

RedisSerializer.string()

字符串序列化器

JdkSerializationRedisSerializer

RedisSerializer.java()

JDK序列化器

GenericJackson2JsonRedisSerializer

RedisSerializer.json()

JSON序列化器

修改 Spring Boot 启动类,添加一个RedisTemplate<String,Object>的bean的声明。

经过上述定义,我们就可以使用 RedisTemplate 保存对象型数据了。下面单元测试向Redis放入一个Date对象,过期时间1分钟。

代码语言:javascript复制
@RunWith(SpringRunner.class)

@SpringBootTest

public class RedisTemplateTest {

    @Autowired

    private RedisTemplate<String, Object> objRedisTemplate;

    @Test

    public void testObjectRedisTemplate() {

        objRedisTemplate.opsForValue().set("timeObj", new Date(), 1, TimeUnit.MINUTES);

        Object time = objRedisTemplate.opsForValue().get("timeObj");

        System.err.println(time "t" time.getClass());

    }

}

2 使用 RedisTemplate 缓存数据对象,减少SQL查询

假设应用中有如下Movie实体类:

代码语言:javascript复制
@Data

@Entity

public class Movie implements Serializable {

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private int id;

    private String title;

    private String movieCode;

    private String director;

    private Date dateReleased;

    @ManyToOne

    @JoinColumn(name = "categoryId")

    private Category category;

}

我们可以使用之前定义强类型的 RedisTemplate<String,Movie>(也可以用之前自定义的弱类型RedisTemplate<String,Object>):

代码语言:javascript复制
   @Bean

    public RedisTemplate<String, Movie> movieRedisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Movie> template = new RedisTemplate<>();

        template.setConnectionFactory(redisConnectionFactory);

        template.setKeySerializer(RedisSerializer.string());

        template.setHashKeySerializer(RedisSerializer.string());

        template.setValueSerializer(RedisSerializer.java());

        template.setHashValueSerializer(RedisSerializer.java());

        return template;

    }

在业务对象中,我们可以先从Redis缓存中获取数据对象,如果缓存没有,我们才使用SQL从关系型数据库获取。

下面代码先从Redis的hash缓存中查找key为id(字符串)的对象,缓存中有就直接返回数据,缓存中没有就从数据库查找,查询后先把数据保存在Redis缓存中再返回。

代码语言:javascript复制
@Service

public class MovieBizImpl implements MovieBiz {

    private final static String CACHE = "mycinema-movie";   //定义hash缓存的主key

   

    @Autowired

    private MovieRepository movieDb;

    @Autowired

    private RedisTemplate<String,Object> rd;

   

    @Override

    public Movie findOneFromCache(int id) {

        HashOperations<String, String, Object> ops = rd.opsForHash();

        //先检查缓存中是否有所要的数据(hash中每行数据使用id(字符串)作为key),优先从缓存取

        if(rd.hasKey(CACHE) && ops.hasKey(CACHE, id "")) { 

            return (Movie)ops.get(CACHE, id "");           

        }else {

            Movie m = movieDb.findById(id).orElse(null);

            if(m!=null) {

                ops.put(CACHE, id "", m);                   //hash中每行数据使用id(字符串)作为key

                rd.expire(CACHE, 30, TimeUnit.SECONDS);     //设置缓存过期时间

            }

            return m;

        }

    }

}

注意:为了数据安全性,使用缓存时,必须设置缓存的时间!

3 使用 Redis Repository

Repository 是Spring Data的一种编程模式,在Repository模式下,只要编写一个接口继承自Repository或CrudRepository接口,无需编程就能实现数据和数据源之间的持久化,之前学习过的SpringDataJPA主要使用的就是Repository模式。Repository模式不仅可以用在JPA上,也可以用在Redis上。

在这种模式下,我们把Redis作为数据库看待而不是仅仅作为缓存看待,下面演示如何使用。

(1)创建实体类并使用 “Redis注解” 标记实体类缓存的规则

代码语言:javascript复制
@RedisHash(value="mycinema-category", timeToLive=60)

@Data

@NoArgsConstructor

@AllArgsConstructor

@Builder

public class CategoryCache {

    @Id                 //标记主键,可以用id作redis的key

    private int id;

    @Indexed            //标记索引,可以用name作为redis的key

    private String name;

}

(2)创建 Repository 用于 Redis缓存 存取数据

代码语言:javascript复制
//这里只能继承CrudRepository

public interface CategoryRedisRepository extends CrudRepository<CategoryCache, Integer> {

    Optional<CategoryCache> findOneByName(String name);

}

(3)在SpringBoot启动类中开启 “@EnableRedisRepositories”

代码语言:javascript复制
@SpringBootApplication

@EnableRedisRepositories

public class SpringRedisApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringJedisApplication.class, args);

    }

}

(4)测试 Redis Repository 的效果

代码语言:javascript复制
@SpringBootTest

class CategoryRedisRepositoryTest {

    @Autowired

    private CategoryRedisRepository target;

    @Test

    void testSave() {

        CategoryCache c = CategoryCache.builder().id(10).name("悬疑").build();

        target.save(c); //把对象存放到Redis

    }

    @Test

    void testFindOneByName() {

        CategoryCache c = target.findOneByName("悬疑").orElse(null);

        System.err.println("Test findOneByName: " c);   //根据名称索引获取对象

    }

    @Test

    void testFindById() {

        CategoryCache c = target.findById(10).orElse(null);

        System.err.println("Test findById: " c);    //根据Id获取对象

    }

}

4 使用 Spring 缓存抽象整合 Redis

使用RedisTemplate来缓存数据虽然可行但会产生许多管道代码。如果对缓存的操作不要求很精细,可以使用Spring提供的Cache抽象API来实现对业务查询的缓存。Spring Cache可以整合Redis 并提供声明式的缓存功能,让我们无需编码就可以透明的实现缓存功能。

Spring Cache提供的缓存注解:

注解

描述

@Cacheable

配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找

@CacheEvict

用来清除用在本方法或者类上的缓存数据

@CachePut

类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果

@Caching

注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict

@CacheConfig

配置在类上,cacheNames即定义了本类中所有用到缓存的地方,都去找这个库。只要使用了这个注解,在方法上@Cacheable @CachePut @CacheEvict就可以不用写value去找具体库名了

Spring Cache整合Redis的用法如下所示。

(1)修改 application.yml 添加 Spring 缓存配置(整合Redis) 

代码语言:javascript复制
spring:

  #spring缓存配置

  cache:

    type: redis

    redis:

      time-to-live: 60000   #缓存超时时间ms

      cache-null-values: false   #是否缓存空值

(3)在SpringBoot启动类中开启 “@EnableCaching”

代码语言:javascript复制
@SpringBootApplication

@EnableCaching

public class SpringJedisApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringJedisApplication.class, args);

    }

}

(4)使用注解为业务添加缓存

首先在业务类中添加 “@CacheConfig” 指定缓存的公共信息,然后用“@Cacheable”等注解指定每一个方法的具体缓存规则。

代码语言:javascript复制
@Service

@CacheConfig(cacheNames = "mycinema-user")    // 以 mycinema-user 作为hash本身的顶级key

public class UserBizImpl implements UserBiz {

    @Autowired

    private UserRepository userDb;

   

    @Cacheable(key="'all_users'")   // 以 all_users 作为hash内的二级key

    public List<User> findAll() {

        return userDb.findAll();

    }

    @Cacheable(key="'user_' #id")   // 以 user_id(参数) 作为hash内的二级key

    public User findById(int id) {

        return userDb.findById(id).orElse(null);

    }

    @CacheEvict(allEntries = true)  // 删除元素时清除当前hash的所有值

    public void delete(int id) {

        userDb.delete(userDb.findById(id).orElse(null));

    }

    @CachePut(key="'user_' #result.id") // 把返回值重新保存到user_id指定的key中

    @CacheEvict(key="'all_users'")      // 清除all_users缓存

    public User save(User user) {

        return userDb.saveAndFlush(user);

    }

    @CacheEvict(allEntries = true)

    public void clearCache() {}

}

创建单元测试测试缓存中的数据:

代码语言:javascript复制
@RunWith(SpringRunner.class)

@SpringBootTest

public class UserBizTest {

    @Autowired

    private UserBiz target;

   

    @Test

    public void testFindAll() {

        System.err.println(target.findAll());

    }

    @Test

    public void testFindById() {

        System.err.println(target.findById(1));

    }

    @Test

    public void testDelete() {

        target.delete(4);

    }

    @Test

    public void testSave() {

        User user = new User(0, "username1", "password1", "name1", "address1", "phone1", "email1", "role1");

        target.save(user);          // 测试添加数据

        user = target.findById(3);

        user.setAddress("address1");

        target.save(user);          // 测试修改数据

    }

}

0 人点赞