目录
1.Redis和数据库读操作2.使用Spring缓存机制整合Redis
3.缓存注解简介
4.RedisTemplate实例
1.Redis和数据库读结合
1.1 读操作
数据缓存往往会在Redis上设置超时时间,当设置Redis的数据超时后,Redis就没法读出数据了,这个时候触发程序读取数据库,然后将读取的数据库数据写入Redis,这样就能按一定的时间间隔刷新数据了
1.2 写操作写操作要考虑数据一致的问题,尤其是重要的业务数据
所以先应该考虑从数据库中读取最新的数据,然后对数据进行操作,最后把数据写入Redis缓存中
2.使用Spring缓存机制整合Redis
实例目录图
文件作用
文件 | 作用 | 备注 |
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());*/
}
对于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服务器执行,从而提高性能。