4.2 Spring整合Redis

99 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第20天,点击查看活动详情

4.2 Spring整合Redis

image-20220720101852295

1.引入依赖

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

2.配置Redis

application.properties 配置文件中配置 redis 相关的

# RedisProperties 配置redis相关的(使用redis中的哪个数据库、redis所在服务器的ip、redis服务器的端口)
spring.redis.database=11
spring.redis.host=localhost
spring.redis.port=6379

image-20220720150458382

编写配置类,构造RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 方法参数 RedisConnectionFactory 会自动被工厂注入
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 通过链接工厂创建链接
        template.setConnectionFactory(factory);

        // 设置key的序列化方式
        template.setKeySerializer(RedisSerializer.string());
        // 设置value的序列化方式
        template.setValueSerializer(RedisSerializer.json());
        // 设置hash的key的序列化方式
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置hash的value的序列化方式
        template.setHashValueSerializer(RedisSerializer.json());

        template.afterPropertiesSet();
        return template;
    }
}

image-20220720150628226

3.访问Redis

@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTest {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testStrings(){
        String redisKey = "test:count";

        redisTemplate.opsForValue().set(redisKey, 1);

        System.out.println(redisTemplate.opsForValue().get(redisKey));
        System.out.println(redisTemplate.opsForValue().increment(redisKey));
        System.out.println(redisTemplate.opsForValue().decrement(redisKey));
    }
    /*
    结果:
        1
        2
        1
    */
    @Test
    public void testHashes() {
        String redisKey = "test:user";

        redisTemplate.opsForHash().put(redisKey, "id", 1);
        redisTemplate.opsForHash().put(redisKey, "username", "zhangsan");

        System.out.println(redisTemplate.opsForHash().get(redisKey, "id"));
        System.out.println(redisTemplate.opsForHash().get(redisKey, "username"));
    }
    /*
    结果:
        1
        zhangsan
    */
    @Test
    public void testLists() {
        String redisKey = "test:ids";

        redisTemplate.opsForList().leftPush(redisKey, 101);
        redisTemplate.opsForList().leftPush(redisKey, 102);
        redisTemplate.opsForList().leftPush(redisKey, 103);

        System.out.println(redisTemplate.opsForList().size(redisKey));
        System.out.println(redisTemplate.opsForList().index(redisKey, 0));
        System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2));

        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
    }
    /*
    结果:
        3
        103
        [103, 102, 101]
        103
        102
        101
    */
    @Test
    public void testSets() {
        String redisKey = "test:teachers";

        redisTemplate.opsForSet().add(redisKey, "刘备", "关羽", "张飞", "赵云", "诸葛亮");

        System.out.println(redisTemplate.opsForSet().size(redisKey));
        System.out.println(redisTemplate.opsForSet().pop(redisKey));
        System.out.println(redisTemplate.opsForSet().members(redisKey));
    }
    /*
    结果:
        5
        诸葛亮
        [张飞, 关羽, 赵云, 刘备]
    */
    @Test
    public void testSortedSets() {
        String redisKey = "test:students";

        redisTemplate.opsForZSet().add(redisKey, "唐僧", 80);
        redisTemplate.opsForZSet().add(redisKey, "悟空", 90);
        redisTemplate.opsForZSet().add(redisKey, "八戒", 50);
        redisTemplate.opsForZSet().add(redisKey, "沙僧", 70);
        redisTemplate.opsForZSet().add(redisKey, "白龙马", 60);

        System.out.println(redisTemplate.opsForZSet().zCard(redisKey));
        System.out.println(redisTemplate.opsForZSet().score(redisKey, "八戒"));
        // reverseRank:由大到小     rank:由小到大
        System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey, "八戒"));
        System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey, 0, 2));
    }
    /*
    结果:
        5
        50.0
        4
        [悟空, 唐僧, 沙僧]
    */
    @Test
    public void testKeys() {
        redisTemplate.delete("test:user");

        System.out.println(redisTemplate.hasKey("test:user"));

        redisTemplate.expire("test:students", 10, TimeUnit.SECONDS);
    }
    /*
    结果:
        false
    */


    /**
     * 有可能每次访问的时候都访问同一个key,每个api都要把这个key传进去,
     * 有点麻烦,针对这样的逻辑我们可以绑定这个key,每次操作都一定是针对
     * 这个key的,不用传key了。
     */
    // 多次访问同一个key的话我们可以绑定key
    @Test
    public void testBoundOperations() {
        String redisKey = "test:count";
        BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
        operations.increment();
        operations.increment();
        operations.increment();
        operations.increment();
        operations.increment();
        System.out.println(operations.get());
    }
    /*
    结果:
        6
    */

    // 编程式事务
    @Test
    public void testTransactional() {
        Object obj = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String redisKey = "test:tx";

                operations.multi();         // 启用事务

                operations.opsForSet().add(redisKey, "zhangsan");
                operations.opsForSet().add(redisKey, "lisi");
                operations.opsForSet().add(redisKey, "wangwu");

                System.out.println(operations.opsForSet().members(redisKey));

                return operations.exec();   // 提交事务
            }
        });
        System.out.println(obj);
    }
    /*
    结果:
        []
        [1, 1, 1, [zhangsan, wangwu, lisi]]
     */
}

重点:redis中事务

因为redis是一个数据库,它也是支持事务的,但是它所支持的事务的机制不完全满足ACID四个特性,因为毕竟它不是关系型数据库,只有关系型数据库才严格满足这四个特点,整体来说redis的事务管理是比较简单的。它的机制是:当我启用事务以后,当我再去执行一个redis命令的时候,它并不会立刻执行这个命令,而是把这个命令放到一个队列里先存着,然后再执行一个命令再放到队列里,直到提交事务的时候,它会把队列中的命令一股脑发给redis服务器一起执行。这里有一个隐含的问题需要注意:因为事务之内的命令不会立刻执行,而是提交时统一批量的执行,所以如果在事务的过程中做了一个查询(这个查询不受事务管理),这个查询不会立刻返回结果,也就是说不要在事务中间去做查询,要么提交查,要么事务提交之后查!!!

Spring支持redis的声明式事务和编程式事务,也是声明式事务更简单,只要做一些配置,加上@Transactional注解就可以了,但是因为redis的事务有刚才所说的问题的存在,所以我们通常不会用声明式事务,因为声明式事务只能精确到一个方法,在方法上加上@Transactional,方法内部整个逻辑都是整个的事务范围,这个方法之内就没办法去查询了。所以我们通常都用编程式事务把事务的范围缩小。

编程式事务格式:

@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTest {

    @Autowired
    private RedisTemplate redisTemplate;
    // 编程式事务
  
    @Test
    public void testTransactional() {
        Object obj = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String redisKey = "test:tx";

                operations.multi();         // 启用事务

                operations.opsForSet().add(redisKey, "zhangsan");
                operations.opsForSet().add(redisKey, "lisi");
                operations.opsForSet().add(redisKey, "wangwu");

                System.out.println(operations.opsForSet().members(redisKey));

                return operations.exec();   // 提交事务
            }
        });
        System.out.println(obj);
    }
    /*
    结果:
        []
        [1, 1, 1, [zhangsan, wangwu, lisi]]
     */
}