互联网应用主流框架整合之Spring缓存机制和Redis结合

news2024/9/20 7:59:04

Redis和数据库的结合

在实际的商用软件使用中,通常都是Redis和关系型数据配置使用,单纯使用Redis来存数据成本太高,并且其持久化和计算能力偏差,这两块无法和关系型数据相比较,而Redis和关系型数据库共存的场景就会带来另一个问题,就是在两者之间的数据一致性的问题,有太多的情况会导致Redis中的数据和关系型数据中的数据不一致,比如关系型数据库事务是完善的,Redis的事务没那么严格出现异常回滚,再比如Redis数据更新了但还没有像关系型数据库同步完成,再比如两个业务线的Redis都会同步关系型数据库数据,一边更新了而另一边就变成了脏数据等等

Redis和数据库读操作

从业务角度而言缓存不应该是永久的,这样极其容易造成脏数据的产生,而Redis也需要时进行垃圾回收给新数据腾出空间,因此一般来说应该加入一个超时时间,这样一旦数据超时,Redis就没法读出超时数据,这时候就会触发程序读取数据库,同步刷新缓存数据并设置超时时间,这样就完成了读操作,如下图所示
在这里插入图片描述
伪代码可以这样写

public DataObject readMethod(args){
	//尝试从Redis中读取数据
	DataObject data = getFromRedis(key);
	if (data != null){
		return data;
	}
	//从Redis读入不成功,从数据库中获取
	data = getFromDataBase();
	//写入Redis
	writeRedis(key, data);
	//设置key的超时时间为5分钟
	setRedisExprie(key,5);
	return data;
}

Redis和数据库写操作

写操作要考虑数据一致性的问题,所以首先应该从数据库中读取最新数据,然后对数据进行操作
在这里插入图片描述
写入数据是不能信任缓存的,从数据库中读取最新数据,然后进行业务操作,更新业务数据到数据库,再用新数据刷新Redis缓存,这样就完成了写操作,伪代码可以这样写

public DataObject writeMethod(args){
	//从数据库中读取最新数据
	DataObject dataObject = getFromDataBase(args);
	//执行业务逻辑
	execLogic(dataObject);
	//更新数据库数据
	updateDataBase(dataObject);
	//刷新缓存
	updateRedisData(key, dataObject);
	//设置超时时间
	setRedisExpire(key,5);
}

使用Spring的缓存机制整合Redis

首先建个项目的基本结构和必要基础数据,如下所示


  <dependencies>
    <!-- Spring核心包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- Spring Bean包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- Spring Context包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- Spring Context支持包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- Spring 表达式包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- Spring面向切面(AOP)包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- Spring JDBC包 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>

    <!-- dbcp2包 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-dbcp2</artifactId>
      <version>2.7.0</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.9.5</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.3</version>
    </dependency>
    <!-- MyBatis包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.3</version>
    </dependency>
    <!-- MySQL驱动包 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.29</version>
    </dependency>
    <!-- 实现slf4j接口并整合 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.26</version>
    </dependency>
    <!-- Spring Web 和 MVC -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <!-- POJO的验证 -->
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.1.0.Final</version>
    </dependency>
    <!-- EXCEL -->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>4.1.1</version>
    </dependency>


    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>


    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.1.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.10.1</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.1</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.10.1</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.26</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.11.2</version>
    </dependency>
    <!-- Spring Data Redis -->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>2.2.4.RELEASE</version>
    </dependency>
    <!-- Jedis -->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>3.2.0</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
package com.sr.pojo;

import java.io.Serializable;

import org.apache.ibatis.type.Alias;

// MyBatis别名
@Alias(value = "role")
public class Role implements Serializable {
    // 序列号
    private static final long serialVersionUID = 5107424510097186591L;

    private Long id;
    private String roleName;
    private String note;

    /**** setter and getter ****/
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

}

这个POJO实现了序列化Serializable接口,则可以将这个POJO通过Spring的序列化器保存为对应的编码,用于存储在Redis中,然后使用它的时候可以从Redis中读取,再进行反序列化;类上加了别名注解,MyBatis扫描机制会识别到它,然后定义Mapper用于操作数据库

<?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.sr.dao.RoleDao">

    <select id="getRole" resultType="role">
        select id, role_name as
            roleName, note from t_role where id = #{id}
    </select>

    <delete id="deleteRole">
        delete from t_role where id=#{id}
    </delete>

    <insert id="insertRole" parameterType="role" useGeneratedKeys="true" keyProperty="id">
        insert into t_role (role_name, note) values(#{roleName}, #{note})
    </insert>

    <update id="updateRole" parameterType="role">
        update t_role set role_name = #{roleName}, note = #{note}
        where id = #{id}
    </update>

    <select id="findRoles" resultType="role">
        select id, role_name as roleName, note from t_role
        <where>
            <if test="roleName != null">
                role_name like concat('%', #{roleName}, '%')
            </if>
            <if test="note != null">
                note like concat('%', #{note}, '%')
            </if>
        </where>
    </select>

</mapper>

然后需要一个MyBatis角色接口,以便使用Mapper文件

package com.sr.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.sr.pojo.Role;

@Mapper
public interface RoleDao {
    public Role getRole(Long id);

    public int deleteRole(Long id);

    public int insertRole(Role role);

    public int updateRole(Role role);
    
    public List<Role> findRoles(@Param("roleName") String roleName, @Param("note") String note);
}

@Mapper表示它是一个持久层的接口,未来可以通过MyBatis扫描机制将其装配到SpringIoC中,至此Dao层就完成了,接下来是角色服务接口RoleService ,如下所示

package com.sr.service;
import java.util.List;

import com.sr.pojo.Role;

public interface RoleService {
    public Role getRole(Long id);

    public int deleteRole(Long id);

    public Role insertRole(Role role);

    public Role updateRole(Role role);

    public List<Role> findRoles(String roleName, String note);

    int insertRoles(List<Role> roleList);
}

接下来配置数据库和MyBatis相关内容,如下所示

package com.sr.config;


import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;


@ComponentScan("com.sr")
// 配置MyBatis映射器扫描规则
@MapperScan(
        basePackages = "com.sr",
        annotationClass = Mapper.class,
        sqlSessionFactoryRef = "sqlSessionFactory"
)
//使用事务驱动管理器
@EnableTransactionManagement
public class DataBaseConfig implements TransactionManagementConfigurer {

    DataSource dataSource = null;
    /**
     * 配置数据库
     *
     * @return 数据连接池
     */
    @Bean(name = "dataSource")
    public DataSource initDataSource() {
        if (dataSource != null) {
            return dataSource;
        }
        Properties props = new Properties();
        props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql://localhost:3306/ssm");
        props.setProperty("username", "root");
        props.setProperty("password", "xxxxxx");
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    /**
     * * 配置SqlSessionFactoryBean
     *
     * @return SqlSessionFactoryBean
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactoryBean initSqlSessionFactory(@Autowired DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        // 配置MyBatis配置文件
        Resource resource = new ClassPathResource("mybatis-config.xml");
        sqlSessionFactory.setConfigLocation(resource);
        return sqlSessionFactory;
    }

    /**
     * * 通过自动扫描,发现MyBatis Mapper接口
     *
     * @return Mapper扫描器
     */
    @Bean
    public MapperScannerConfigurer initMapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        // 扫描包
        msc.setBasePackage("com.*");
        msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
        // 区分注解扫描
        msc.setAnnotationClass(Mapper.class);
        return msc;
    }

    /**
     * 实现接口方法,注册注解事务,当@Transactional 使用的时候产生数据库事务
     */
    @Override
    @Bean(name = "annotationDrivenTransactionManager")
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(initDataSource());
        return transactionManager;
    }

}

代码中在initSqlSessionFactory方法中,通过SqlSessionFactoryBean引入MyBatis的配置文件mybatis-config.xml,该文件内容如下所示

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 定义别名 -->
    <typeAliases>
        <package name="com.sr.pojo"/>
    </typeAliases>

    <!-- 引入映射文件 -->
    <mappers>
        <mapper resource="RoleMapper.xml" />
    </mappers>

</configuration>

Spring缓存管理器

在Spring中提供了CacheManager接口来定义缓存管理器,不同的缓存可以通过实现这个接口来完成该对缓存的管理,在spring-data.redis.jar包中实现CacheManager接口的是类RedisCacheManager,因此需要在SpringIoC容器中装配它的实例,在Spring5.x版本这个实例是通过构造器模式来完成实例构建的,如下代码所示

package com.sr.config;


import java.time.Duration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;

import redis.clients.jedis.JedisPoolConfig;

@Configuration
// 驱动缓存工作
@EnableCaching
public class RedisConfig {
    @Bean("redisPoolConfig")
    public JedisPoolConfig poolConfig() {
        JedisPoolConfig poolCfg = new JedisPoolConfig();
        // 最大空闲数
        poolCfg.setMaxIdle(50);
        // 最大连接数
        poolCfg.setMaxTotal(100);
        // 最大等待毫秒数
        poolCfg.setMaxWaitMillis(20000);
        return poolCfg;
    }
    /**
     * 创建Jedis连接工厂
     * @param jedisPoolConfig
     * @return 连接工厂
     */
    @Bean("redisConnectionFactory")
    public RedisConnectionFactory redisConnectionFactory(@Autowired JedisPoolConfig jedisPoolConfig) {
        // 独立Jedis配置
        RedisStandaloneConfiguration rsc = new RedisStandaloneConfiguration();
        // 设置Redis服务器
        rsc.setHostName("192.168.80.130");
        // 如需要密码,设置密码
        rsc.setPassword("abcdefg");
        // 端口
        rsc.setPort(6379);
        // 获得默认的连接池构造器
        JedisClientConfigurationBuilder jpcb = JedisClientConfiguration.builder();
        // 设置Redis连接池
        jpcb.usePooling().poolConfig(jedisPoolConfig);
        // 获取构建器
        JedisClientConfiguration jedisClientConfiguration = jpcb.build();
        // 创建连接工厂
        return new JedisConnectionFactory(rsc, jedisClientConfiguration);
    }
    /**
     * 创建RedisTemplate
     * @param connectionFactory Redis连接工厂
     * @return RedisTemplate对象
     */
    @Bean("redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory connectionFactory) {
        // 创建RedisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 字符串和JDK序列化器
        RedisSerializer<String> strSerializer = RedisSerializer.string();
        RedisSerializer<Object> jdkSerializer = RedisSerializer.java();
        // 设置键值序列化器
        redisTemplate.setKeySerializer(strSerializer);
        redisTemplate.setValueSerializer(jdkSerializer);
        // 设置哈希字段和值序列化器
        redisTemplate.setHashKeySerializer(strSerializer);
        redisTemplate.setHashValueSerializer(jdkSerializer);
        // 给RedisTemplate设置连接工厂
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
    /**
     * 创建StringRedisTemplate
     * @param connectionFactory 连接工厂
     * @return StringRedisTemplate对象
     */
    @Bean("stringRedisTemplate")
    public StringRedisTemplate  stringRedisTemplate(@Autowired RedisConnectionFactory connectionFactory) {
        // 创建StringRedisTemplate对象
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        // 设置连接工厂
        stringRedisTemplate.setConnectionFactory(connectionFactory);
        return stringRedisTemplate;
    }

	@Bean(name = "redisCacheManager")
  	public CacheManager initRedisCacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {
        // 获取Redis缓存默认配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 构建Redis缓存管理器
        RedisCacheManager cacheManager = RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory)
                // 定义缓存管理器名称和配置,这样可以后续进行引用
                .withCacheConfiguration("redisCacheManager", config)
                .build();
        return cacheManager;
    }
}

注解@EnableCaching表示将启动Spring缓存机制, @Bean(name = "redisCacheManager")则是定义缓存管理器,配置了缓存管理器后,Spring允许用注解的方式使用缓存(XML也可以使用缓存管理器但用的不多,避免XML泛滥),有几个比较重要的注解,如下表格所示
在这里插入图片描述
@Cacheable@CachePut都可以缓存键值对,方式略有不同,他们只能运用于有返回值的方法中;删除缓存Key的@CacheEvict注解则可以用到void方法上,上述注解都能被标注到类和方法上,如果标注在类上则对所有方法生效,如果标注到方法上则对被标注的方法生效;一般而言对于查询更多使用注解@Cacheable,对于插入和修改使用注解@CachePut,对于删除则是CacheEvict

@Cacheable和@CachePut

在这里插入图片描述
@Cacheable@CachePut两个注解的配置项比较接近,如上表所示其中value和key最为常用,value是个数组可以引用多个缓存管理器,key则是缓存中的键,它只是Spring表达式,通过Spring表达式可以自定义缓存的key,为了自定义key则需要知道Spring表达式和缓存注解之间的约定,如下表所示
在这里插入图片描述
通过这些约定引用方法的参数和返回值内容,使其能够注入key定义的Spring表达式的结果中,这样就能使用对应的参数和返回值作为缓存的Key了,然后再看一下实际使用这些内容实现RoleService服务接口的实现类,如下所示

package com.sr.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.sr.dao.RoleDao;
import com.sr.pojo.Role;
import com.sr.service.RoleService;

@Service
public class RoleServiceImpl implements RoleService {

    // 角色DAO,方便执行SQL
    @Autowired
    private RoleDao roleDao = null;

    /**
     * 使用@Cacheable定义缓存策略 当缓存中有值,则返回缓存数据,
     * 否则访问方法得到数据 通过value引用缓存管理器,通过key定义键
     * @param id 角色编号
     * @return 角色
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    @Cacheable(value = "redisCacheManager", key = "'redis_role_'+#id")
    public Role getRole(Long id) {
        return roleDao.getRole(id);
    }

    /**
     * 使用@CachePut则表示无论如何都会执行方法,最后将方法的返回值再保存到缓存中
     * 使用在插入数据的地方,则表示保存到数据库后,会同期插入Redis缓存中
     * @param role 角色对象
     * @return 角色对象(会回填主键)
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    @CachePut(value = "redisCacheManager",key = "'redis_role_'+#result.id")
    public Role insertRole(Role role) {
        roleDao.insertRole(role);
        return role;
    }

    /**
     * 使用@CachePut,表示更新数据库数据的同时,也会同步更新缓存
     * @param role 角色对象
     * @return 影响条数
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    @CachePut(value = "redisCacheManager",key = "'redis_role_'+#role.id")
    public Role updateRole(Role role) {
        roleDao.updateRole(role);
        return role;
    }

    /**
     * 使用@CacheEvict删除缓存对应的key
     * @param id 角色编号
     * @return  返回删除记录数
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    @CacheEvict(value = "redisCacheManager", key = "'redis_role_'+#id")
    public int deleteRole(Long id) {
        return roleDao.deleteRole(id);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public List<Role> findRoles(String roleName, String note) {
        return roleDao.findRoles(roleName, note);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public int insertRoles(List<Role> roleList) {
        for (Role role : roleList) {
            // 同一类方法调用自己的方法,产生自调用失效问题
            this.insertRole(role);
        }
        return roleList.size();
    }
}
  • getRole方法:因为它是一个查询方法,所以使用注解@Cacheable这样在Spring的调用,就会先查询Redis,看看是否存在对应的值,那么采用什么key查询呢? 是由注解中的key属性定义的,它配置的是'redis_role_'+#id, Spring EL会计算返回一个key,以参数id为1L为例,此时key计算结果为redis_role_1,可以以它为key访问Redis,如果Redis存在数据,那么就返回,不再执行getRole方法,否则就继续执行getRole方法,最后返回值再以redis_role_1为key保存到Redis中,以后通过这个key访问Redis数据
  • insertRole方法:这里需要先执行方法,最后才能把返回的信息保存到Redis中,所以采用的是@CachePut,由于主键由数据库生成,所以无法从参数中读取,但是可以从结果中读取,#result.id的写法就是获取方法返回的角色id,而这个id是通过数据库生成然后由MyBatis回填得到的,这样就可以在Redis中新增一个key然后保存对应的对象了
  • udpateRole方法:采用注解@CachePut,由于对象有所更新,所以要在方法之后更新Redis数据,以保证数据一致性,直接读取参数id,表达式写成#role.id,可以引入角色参数的id,在方法结束后它会更新Redis对应的key的值

测试代码如下所示

package com.sr.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.sr.config.DataBaseConfig;
import com.sr.config.RedisConfig;
import com.sr.pojo.Role;
import com.sr.service.RedisTemplateService;
import com.sr.service.RoleService;

public class SrMain {

    public static void main(String[] args) {
        testCache1();
    }

    public static void testCache1() {
        // 使用注解Spring IoC容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(DataBaseConfig.class, RedisConfig.class);
        // 获取角色服务类
        RoleService roleService = ctx.getBean(RoleService.class);
        Role role = new Role();
        role.setRoleName("role_name_1");
        role.setNote("role_note_1");
        // 插入角色
        roleService.insertRole(role);
        // 获取角色
        Role getRole = roleService.getRole(role.getId());
        getRole.setNote("role_note_1_update");
        // 更新角色
        roleService.updateRole(getRole);
        System.out.println("id = " + getRole.getId());
    }
}
@CacheEvict

注解@CacheEvict主要用于删除缓存对应的键值对,其属性配置如下表所示
在这里插入图片描述
其中value和key与之前的注解@Cacheable@CachePut是一致的,属性allEntries要求删除缓存服务器中所有的缓存,这个时候指定的key是不会生效的,beforeInvocation属性指定在方法前或者方法后删除缓存

    /**
     * 使用@CacheEvict删除缓存对应的key
     * @param id 角色编号
     * @return  返回删除记录数
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    @CacheEvict(value = "redisCacheManager", key = "'redis_role_'+#id")
    public int deleteRole(Long id) {
        return roleDao.deleteRole(id);
    }

如代码所示,它在方法执行完成后删除缓存,也就是说它可以从方法内读取到缓存服务器中的数据,如果将注解@CacheEvict的属性声明为true,则在方法前删除缓存数据,这样就不能在方法中读取缓存数据了,只是这个属性默认为false,所以只会在方法执行完成后删除缓存

不适用缓存的方法
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public List<Role> findRoles(String roleName, String note) {
        return roleDao.findRoles(roleName, note);
    }

这里根据角色和备注查询角色信息,所以该方法的返回值具有不确定性,并且命中率很低,对于这样的场景使用缓存并不能有效的提高性能,此外返回的结果写的概率大于读的概率也没必要使用缓存,还有如果返回的结果严重消耗内存,也需要考虑是否使用缓存

自调用失效问题
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public int insertRoles(List<Role> roleList) {
        for (Role role : roleList) {
            // 同一类方法调用自己的方法,产生自调用失效问题
            this.insertRole(role);
        }
        return roleList.size();
    }
}

insertRoles方法中调用了同一个类中带有注解@CachePut的insertRole方法,但是当方法执行后,Spring并没有把对应的新角色写入Redis中,也就是缓存注解失效了,因为缓存注解是基于Spring AOP实现的,而Spring AOP的基础是动态代理技术,也就是只有被代理对象调用,AOP才有拦截的功能,才能执行缓存注解提供的功能,而这里的自调用是没有代理对象的,所以注解功能失效

Redis缓存管理器配置RedisCacheConfiguration

之前的代码使用默认的配置来创建RedisCacheConfiguration对象的,这样存在一个问题,必须序列化器、超时时间等也都是默认的,RedisCacheConfiguration这个配置类源码中的构造函数如下所示

/**
 * 构造函数,用于初始化Redis缓存配置
 * 
 * @param ttl 缓存过期时间,null表示永不过期
 * @param cacheNullValues 是否缓存空值,有助于减少某些场景下的数据库查询次数
 * @param usePrefix 是否使用键前缀,可以增强缓存键的可读性和维护性
 * @param keyPrefix 缓存键的前缀,用于区分不同业务的缓存
 * @param keySerializationPair 键的序列化对,指定如何序列化和反序列化缓存键
 * @param valueSerializationPair 值的序列化对,指定如何序列化和反序列化缓存值
 * @param conversionService 转换服务,用于在序列化和反序列化过程中转换数据类型
 */
private RedisCacheConfiguration(Duration ttl, Boolean cacheNullValues, Boolean usePrefix, CacheKeyPrefix keyPrefix, RedisSerializationContext.SerializationPair<String> keySerializationPair, RedisSerializationContext.SerializationPair<?> valueSerializationPair, ConversionService conversionService) {
    this.ttl = ttl;
    this.cacheNullValues = cacheNullValues;
    this.usePrefix = usePrefix;
    this.keyPrefix = keyPrefix;
    this.keySerializationPair = keySerializationPair;
    this.valueSerializationPair = valueSerializationPair;
    this.conversionService = conversionService;
}

该构造函数使用private修饰,则我们不能用new来创建对象,RedisCacheConfiguration提供了静态方法,如下代码所示

    @Bean(name = "redisCacheManager")
    public CacheManager initRedisCacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {
        // 创建两个序列化器对
        SerializationPair<String> strSerializer = SerializationPair.fromSerializer(RedisSerializer.string());
        SerializationPair<Object> jdkSerializer = SerializationPair.fromSerializer(RedisSerializer.java());
        RedisCacheConfiguration config = RedisCacheConfiguration
                // 获取默认配置
                .defaultCacheConfig()
                // 设置超时时间
                .entryTtl(Duration.ofMinutes(30L))
                // 禁用前缀
                .disableKeyPrefix()
                // 自定义前缀
                // .prefixKeysWith("prefix")
                // 设置key序列化器
                .serializeKeysWith(strSerializer)
                // 设置value序列化器
                .serializeValuesWith(jdkSerializer)
                // 不缓冲空值
                .disableCachingNullValues();
        // 构建Redis缓存管理器
        RedisCacheManager cacheManager = RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory)
                        // 定义缓存管理器名称和配置,这样可以后续进行引用
                        .withCacheConfiguration("redisCacheManager", config)
                        .build();
        return cacheManager;
    }

一条方法链构建了一个新的RedisCacheConfiguration对象,如此便解决了默认配置的问题

RedisTemplate实例

很多时候,我们需要用一些更为高级的缓存服务器的API,例如Redis流水线、事务、和Lua脚本等,则可以使用如下方式,首先定义一个接口,代码如下

package com.sr.service;

public interface RedisTemplateService {
    /**
     * 执行多个命令
     */
    public void execMultiCommand();

    /**
     * 执行Redis事务
     */
    public void execTransaction();

    /**
     * 执行Redis流水线
     */
    public void execPipeline();
}

实现类如下所示

package com.sr.service.impl;


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.sr.service.RedisTemplateService;

@Service
public class RedisTemplateServiceImpl implements RedisTemplateService {

    @Autowired
    private StringRedisTemplate redisTemplate = null;

    /**
     * 使用SessionCallback接口实现多个命令在一个Redis连接中执行
     */
    @Override
    public void execMultiCommand() {
        // 使用Java 8 lambda表达式
        SessionCallback session = ops -> {
            ops.boundValueOps("key1").set("abc");
            ops.boundHashOps("hash").put("hash-key-1", "hash-value-1");
            return ops.boundValueOps("key1").get();
        };

        String value = (String) redisTemplate.execute(session);
        System.out.println(value);
    }

    /**
     * 使用SessionCallback接口实现事务在一个Redis连接中执行
     */
    @Override
    public void execTransaction() {
        // 使用Java 8 lambda表达式
        SessionCallback session = ops -> {
            // 监控
            ops.watch("key1");
            // 开启事务
            ops.multi();
            // 注意,命令都不会被马上执行,只会放到Redis的队列中,只会返回为null
            ops.boundValueOps("key1").set("abc");
            ops.boundHashOps("hash").put("hash-key-1", "hash-value-1");
            ops.opsForValue().get("key1");
            // 执行exec方法后会触发事务执行,返回结果,存放到list中
            List result = ops.exec();
            return result;
        };
        List list = (List) redisTemplate.execute(session);
        System.out.println(list);
    }

    /**
     * 执行流水线,将多个命令一次性发送给Redis服务器
     */
    @Override
    public void execPipeline() {
        // 使用匿名类实现
        SessionCallback session = new SessionCallback() {
            @Override
            public Object execute(RedisOperations ops)
                    throws DataAccessException {
                // 在流水线下,命令不会马上返回结果,结果是一次性执行后返回的
                ops.opsForValue().set("key1", "value1");
                ops.opsForHash().put("hash", "key-hash-1", "value-hash-1");
                ops.opsForValue().get("key1");
                return null;
            };
        };
        List list = redisTemplate.executePipelined(session);
        System.out.println(list);
    }

}

测试方法如下

    public static void redistemplateservice() {
        // 使用注解Spring IoC容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(DataBaseConfig.class, RedisConfig.class);
        RedisTemplateService redisTemplateService = ctx.getBean(RedisTemplateService.class);
        redisTemplateService.execMultiCommand();
        redisTemplateService.execPipeline();
        redisTemplateService.execPipeline();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2080184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++必修:set与map的模拟实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. set与map的结构 我们知道STL中的set与map底层就是一颗红黑树&#xff0c;接下…

如何在Spring中为`@Value`注解设置默认值

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

如何处理在学校Linux连接不上服务器

一、问题描述 当我们在周末在图书馆背着室友偷偷学习时&#xff0c;准备好好学习Linux&#xff0c;争取在日后大展拳脚时&#xff0c;却突然尴尬的发现&#xff0c;连接不上服务器&#xff0c;总是出现以下画面&#xff1a; 那么&#xff0c;我们该如何解决问题呢&#xff1f; …

螺杆支撑座与滚珠丝杆的精准适配!

螺杆支撑座与滚珠丝杆的适配是确保机械系统的稳定性、精度和耐用性的关键&#xff0c;其适配方法主要包括螺纹连接、联轴器连接、锁紧连接。 螺杆支撑座种类多样&#xff0c;每种类型都有其特定的适用范围和性能特点。因此&#xff0c;根据滚珠丝杆的规格和应用需求&#xff0c…

Python接口测试之如何使用requests发起请求例子解析

在Python中&#xff0c;使用requests库发起HTTP请求是一种常见的接口测试方法。以下是一些使用requests库的基本示例&#xff0c;涵盖了GET、POST、PUT、DELETE等HTTP方法。 安装requests库 首先&#xff0c;确保你已经安装了requests库。如果未安装&#xff0c;可以通过以下…

【系统分析师】-案例篇-数据库

1、分布式数据库 1&#xff09;请用300字以内的文字简述分布式数据库跟集中式数据库相比的优点。 &#xff08;1&#xff09;坚固性好。由于分布式数据库系统在个别结点或个别通信链路发生故障的情况下&#xff0c;它仍然可以降低级别继续工作&#xff0c;系统的坚固性好&…

线程:线程创建pthread_create,线程间的同步与互斥

线程的创建 线程的创建是通过调用pthread_create函数来实现的。该函数的原型如下&#xff1a; int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);参数说明&#xff1a; thread&#xff1a;指向pthread_t类型…

开源word文档相似度对比 软件WinMerge

WinMerge 官网下载 &#xff1a;GitHub - WinMerge/winmerge: WinMerge is an Open Source differencing and merging tool for Windows. WinMerge can compare both folders and files, presenting differences in a visual text format that is easy to understand and hand…

ros2_python编程_多个文件python打包_目录拷贝_解决import错误问题ModuleNotFoundError

1.问题 ros2 python编写程序, 有多个python文件 如何打包多个python文件?解决import错误问题如何打包 有python目录结构的工程 1.ros2 多个python文件示例 代码目录结构, gitee 在线代码 tree 7_multi_file_setup/ 7_multi_file_setup/ ├── file1.py ├── main_node.…

飞书怎么关联任意两段话

最近开始用飞书记文档&#xff0c;体验实在是非常的丝滑&#xff0c;对我来说感觉没有找到更好的竞品了。废话不多说&#xff0c;接下来简单介绍一下怎么关联任意两段话吧。 首先说明&#xff0c;关联可以单向&#xff0c;也可以双向。 直接举例。 我想要将蓝字关联到最下面的…

国标GB28181视频监控EasyCVR视频汇聚平台国标注册被陌生IP入侵如何处理?

GB28181国标/GA/T1400协议/安防综合管理系统EasyCVR视频汇聚平台能在复杂的网络环境中&#xff0c;将前端设备统一集中接入与汇聚管理。智慧安防/视频存储/视频监控/视频汇聚EasyCVR平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级…

Java基础(包装类)

文章目录 前言 一、包装类的概述 二、自动拆装箱 三、128陷阱&#xff08;面试重点&#xff09; 四、自动拆装箱例题分析 前言 该篇文章创作时参考查阅了如下文章 Java种的包装类 Java包装类&#xff08;自动拆装箱&#xff09; Java--自动拆箱/装箱/实例化顺序/缓存…

第三期书生大模型实战营之茴香豆工具实践

文章目录 基础任务作业记录1. 环境准备2. 模型准备3. 修改配置文件4. 知识库创建6. 启动茴香豆webui 基础任务 在 InternStudio 中利用 Internlm2-7b 搭建标准版茴香豆知识助手&#xff0c;并使用 Gradio 界面完成 2 轮问答&#xff08;问题不可与教程重复&#xff0c;作业截图…

IDEA2023版本创建SSM项目框架

按图中红色数字顺序&#xff0c;先点击Maven&#xff0c;设置该项目为maven构建管理的项目&#xff0c;然后点击create进行项目创建 配置该项目的相关maven信息&#xff0c;按下图顺序进入到maven配置页面后进行本地maven相关信息配置。 创建web模块依次按下图中顺序进行点击 配…

朴世龙院士团队《Global Change Biology 》精确量化全球植被生产力对极端温度的响应阈值!

本文首发于“生态学者”微信公众号&#xff01; 随着全球气候变暖的加剧&#xff0c;极端温度事件对陆地生态系统的影响日益显著。植被作为生态系统的重要组成部分&#xff0c;其生产力对温度变化的响应尤为敏感。然而&#xff0c;关于极端温度如何以及在何种程度上影响植被生产…

TCP三次握手过程详解

三次握手过程&#xff1a; 客户端视角&#xff1a; 1.客户端调用connect&#xff0c;开启计时器&#xff0c;发送SYN包&#xff0c;如果重传超时&#xff0c;认为连接失败 2.如果收到服务端的ACK&#xff0c;则进入ESTABLISHED状态 3.清除重传计时器&#xff0c;发送ACK&…

windows权限维持汇总

Windows 权限维持 一、文件层面 1&#xff09;attrib 使用 Attrib s a h r 命令 s&#xff1a;设置系统属性&#xff08;System&#xff09; a&#xff1a;设置存档属性&#xff08;Archive&#xff09; h&#xff1a;设置隐藏属性&#xff08;Hidden&#xff09; r&#…

深度学习基础--11个专题带你入门Pytorch上

目的 本专栏更新深度学习基础&#xff0c;包括pytorch、tensorflow的使用以及CNN、RNN、yolo、GAN、LSTM、Transformer等神经网络的理论基础 前言 Pytorch是最常用的深度学习框架&#xff0c;里面包含了大量关于构建神经网络及其运算的APIPytorch基础入门分为上下两篇文章&am…

基于事件总线EventBus实现邮件推送功能

什么是事件总线 事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制&#xff0c;允许不同的组件之间进行彼此通信而又不需要相互依赖&#xff0c;达到一种解耦的目的。 关于这个概念&#xff0c;网上有很多讲解的&#xff0c;这里我推荐一个讲的比较好的&#x…

光伏设计时要画出哪些模型?

在光伏系统的设计中&#xff0c;为了确保项目的顺利实施与高效运行&#xff0c;设计师需要绘制多种模型来综合考虑各种因素&#xff0c;包括参照物、障碍物以及楼顶配房等。这些模型不仅有助于预测光伏系统的发电效率&#xff0c;还能帮助规划最佳的安装布局&#xff0c;减少阴…