前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot与Redis

SpringBoot与Redis

作者头像
用户10175992
发布2022-11-15 13:34:29
4660
发布2022-11-15 13:34:29
举报
文章被收录于专栏:辰远

使用 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%2B8

    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);          // 测试修改数据

    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档