目录

1.Redis和数据库读操作2.使用Spring缓存机制整合Redis
3.缓存注解简介
4.RedisTemplate实例

1.Redis和数据库读结合

1.1 读操作

数据缓存往往会在Redis上设置超时时间,当设置Redis的数据超时后,Redis就没法读出数据了,这个时候触发程序读取数据库,然后将读取的数据库数据写入Redis,这样就能按一定的时间间隔刷新数据了

redis删除缓存为什么会失败_Redis


1.2 写操作写操作要考虑数据一致的问题,尤其是重要的业务数据

所以先应该考虑从数据库中读取最新的数据,然后对数据进行操作,最后把数据写入Redis缓存中

redis删除缓存为什么会失败_Redis_02

2.使用Spring缓存机制整合Redis

实例目录图

redis删除缓存为什么会失败_spring_03


文件作用

文件

作用

备注

AppTest

测试类

RoleMapper.java

Mapper接口文件

Role.java

POJO类文件

POJO实体

RoleService.java

角色服务接口

RoleServiceImpl

角色服务实现类

RoleMapper.xml

MyBatis映射文件

beans

Spring配置文件

jdbc.properties

数据库配置信息

log4j.properties

Log4j配置文件

mybatis-config.xml

MyBatis配置文件

角色Role

// 实现序列化,可以通过Spring序列化器保存为对应的编码,缓存到Redis
public class Role implements Serializable {
    private Long id;
    private String roleName;
    private String note;
    /****setter and getter *****/
}

pojo类实现了Serializable接口,这样可以通过Spring序列化器将其保存为对应的编码,缓存到Redis中,也可以通过Redis读回那些编码,反序列化为对应的Java对象。

RoleMapper.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="cn.whc.mapper.RoleMapper">
    <select id="getRole" resultType="cn.whc.pojo.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="cn.whc.pojo.Role" useGeneratedKeys="true" keyProperty="id">
        insert into t_role (role_name, note) values (#{roleName}, #{note})
    </insert>

    <update id="updateRole" parameterType="cn.whc.pojo.Role">
        update t_role set role_name = #{roleName}, note = #{note}
        where id = #{id}
    </update>

    <select id="findRoles" resultType="cn.whc.pojo.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>

其中insertRole方法,需要设置useGeneratedKeys,进行主键回填

beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

<!--开启自动扫描-->
    <context:component-scan base-package="cn.whc"/>

    <!--加载properties配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据源-->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driverClassName}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>

    <!--集成MyBatis-->
    <!--加载MyBatis全局配置文件,生成SqlSessionFactory对象-->
    <bean name="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--数据库-->
        <property name="dataSource" ref="dataSource"/>
        <!--MyBatis全局配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--Mapper映射器配置路径-->
        <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
    </bean>

    <!--通过扫描配置Mapper的类-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.whc.mapper"/>
        <!--指定在Spring中定义SqlSessionFactory的Bean名称-->
        <property name="SqlSessionFactoryBeanName" value="SqlSessionFactory"/>
        <!--指定标注才扫描成为Mapper-->
        <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>

    <!--配置数据源事务管理器-->
    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--启动使用注解实现声明式事务管理的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"/>


    <!--配置Redis连接池-->
    <bean name="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--最大空闲数-->
        <property name="maxIdle" value="50"/>
        <!--最大连接数-->
        <property name="maxTotal" value="100"/>
        <!--最大等待时间3s-->
        <property name="maxWaitMillis" value="3000"/>
    </bean>

    <!--jdk序列化器,可保存对象-->
    <bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>

    <!--String序列化器-->
    <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>

    <!--连接池配置-->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="localhost"/>
        <property name="port" value="6379"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

    <!--配置RedisTemplate-->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="keySerializer" ref="stringRedisSerializer"/>
        <property name="valueSerializer" ref="jdkSerializationRedisSerializer"/>
        <property name="defaultSerializer" ref="stringRedisSerializer"/>
        <property name="hashKeySerializer" ref="stringRedisSerializer"/>
        <property name="hashValueSerializer" ref="jdkSerializationRedisSerializer"/>
    </bean>
    
    <!--使用注解驱动-->
    <cache:annotation-driven cache-manager="redisCacheManager"/>

    <!--定义缓存管理器-->
    <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <!--通过构造方法注入RedisTemplate-->
        <constructor-arg index="0" ref="redisTemplate"/>
        <!--定义默认的超时时间-->
        <property name="defaultExpiration" value="600"/>
        <!--缓存管理器名称-->
        <property name="cacheNames">
            <list>
                <value>redisCacheManager</value>
            </list>
        </property>
    </bean>
</beans>

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>

    <!--开启懒加载机制-->
    <settings>
        <!--全局的映射器启动缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--允许JDBC支持生成的键-->
        <setting name="useGeneratedKeys" value="true"/>
        <!--配置默认的执行器, REUSE执行器重用预处理语句-->
        <setting name="defaultExecutorType" value="REUSE"/>
        <!--全局启动延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--关闭层级加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--设置超时时间,它决定驱动等待一个数据库响应的时间-->
        <setting name="defaultStatementTimeout" value="25000"/>
    </settings>

</configuration>

重点设置Spring的缓存管理器: 先定义好RedisTemplate,然后定义RedisCacheManager

pom.xml

<properties>
    <spring-version>4.0.0.RELEASE</spring-version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>${spring-version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring-version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>${spring-version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring-version}</version>
    </dependency>


    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.2</version>
    </dependency>


    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.2</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>


      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>2.9.0</version>
      </dependency>
      <!-- spring-redis 整合包 -->
      <dependency>
          <groupId>org.springframework.data</groupId>
          <artifactId>spring-data-redis</artifactId>
          <version>1.7.2.RELEASE</version>
      </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.21</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>

  </dependencies>

注意: jedis和spring-data-redisd的版本~~,否则报错

3.缓存注解简介

3.1 缓存注解

注解

描述

@Cacheable

在进入方法之前,Spring会先去缓存服务器中查找对应key的缓存值,如果找到缓存值,那么Spring就不会再调用方法,而是将缓存读出,返回给调用者;如果没有找到缓存值,那么Spring就会执行方法,将最后的结果通过key保存到缓存服务器中

@CachePut

Spring会将该方法返回的值缓存到缓存服务器中,注意,Spring不会事先去缓存服务器中查找,而是直接执行方法,然后缓存。换句话说,该方法始终会被Spring所调用

@CacheEvict

移除缓存对应的key的值

@Cacheable和@CachePut都可以保存缓存键值对,它们只能运用于有返回值的方法中;
而删除缓存key的@CacheEvict可以用在void方法上,并不需要去保存任何值

3.2 @Cacheable和@CachePut配置属性

属性

配置类型

描述

value

String[]

使用缓存的名称

key

String

String表达式,可以通过它来计算对应缓存的key

key是缓存中的键,支持Spring表达式,通过Spring表达式可以自定义缓存的key

3.3 表达式值的引用

表达式

描述

备注

#result

方法返回结果值,还可以使用Spring表达式进一步读取其属性

该表达式不能用于注解@Cacheable,因为该注解的方法可能不会被执行

#Argument

任意方法的参数,可以通过方法本身的名称或者下标去定义

比如getRole(Long id)方法,想读取id参数,可以写成#id,或者#a0等,建议写成id,可读性高

3.4 例子

@Service
public class RoleServiceImpl implements RoleService {
    @Autowired
    private RoleMapper roleMapper = null;

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


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

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

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

    /**
     * 不使用缓存,使用缓存的前提-高命中率,这里返回值会根据查询条件多样化,导致其不确定和命中率低下
     * @param roleName
     * @param note
     * @return
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public List<Role> findRoles(String roleName, String note) {
        return roleMapper.findRoles(roleName, note);
    }

	// 自调用失效
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int insertRoles(List<Role> roleList) {
        for (Role role : roleList) {
            this.insertRole(role);
        }
        return roleList.size();
    }
}

测试缓存注解

@Test
    public void test(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        RoleService roleService = ctx.getBean(RoleService.class);
        Role role = new Role();
        role.setRoleName("role_name_2");
        role.setNote("role_note_2");
        // 插入角色
        roleService.insertRole(role);
        // 获取角色
        Role getRole = roleService.getRole(role.getId());
        getRole.setNote("role_note_1_update");
        // 更新角色
        roleService.updateRole(getRole);
      /*  // 删除角色(redis会参数对应的键值对)
        roleService.deleteRole(getRole.getId());*/
    }

redis删除缓存为什么会失败_Redis_04


对于getRole方法,没有看到SQL的执行,因为使用@Cacheable注解后,它先在Redis中上查找,找到数据就返回了

关于自调用失效问题
因为缓存注解基于Spring AOP实现的,对于Spring AOP的基础是动态代理技术,也就是只有代理对象的相互调用,AOP才有拦截的功能,才能执行缓存注解提供的功能。这里的自调用是没有代理对象存在的,所以其注解功能也就失效了,和数据库事务一样需要注意,避免这种情况发生。

4.RedisTemplate实例

RedisTemplateService接口

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

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

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

RedisTemplateServiceImpl实现类

@Service
public class RedisTemplateServiceImpl implements RedisTemplateService {

    @Autowired
    private RedisTemplate redisTemplate = null;

    /**
     * 使用SessionCallback接口实现多个命令在一个Redis连接中执行
     */

    @Override
    public void execMultiCommand() {
        Object obj = redisTemplate.execute((RedisOperations ops) -> {
            ops.boundValueOps("key1").set("abc");
            ops.boundHashOps("hash").put("hash-key-1", "hash-value-1");
            return ops.boundValueOps("key1").get();
        });
        System.err.println(obj);
    }

    /**
     * 使用SessionCallback接口实现事务在一个Redis连接中执行
     */
    @Override
    public void execTransaction() {
        List list = (List) redisTemplate.execute((RedisOperations ops) -> {
            // 监控
            ops.watch("key1");
            // 开启事务
            ops.multi();
            // 开启事务后,命令会放到Redis队列中
            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;
        });
        System.err.println(list);
    }

    /**
     * 执行流水线,将多个命令一次性发送给Redis服务器
     */
    @Override
    public void execPipeline() {
        // 使用匿名类实现
        List list = redisTemplate.executePipelined(new SessionCallback<Object>() {

            @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;
            }
        });
        System.err.println(list);
    }
}

测试类

@Test
    public void test2(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        RedisTemplateService redisTemplateService = ctx.getBean(RedisTemplateService.class);
        redisTemplateService.execPipeline();
    }

redis删除缓存为什么会失败_redis删除缓存为什么会失败_05

在保证数据一致性的情况下,使用事务。
在需要执行多个命令时,可以使用流水线,让命令缓存到一个队列,然后一次性发给Redis服务器执行,从而提高性能。