Redis-20Spring缓存机制整合Redis

199 阅读11分钟

文章目录

概述

这里用一个示例通过注解的方式整合 Spring 和 Redis,要特别注意 Redis 缓存和数据库一致性的问题。


Redis 和数据库读

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

流程图大致如下:

在这里插入图片描述

伪代码大致如下

public Data  getData(args){
// 从Redis中获取数据
Data data = getDataFromRedis(key);
// 如果redis中没有数据,从db中加载并写入redis
if( data = null)
    // 从数据库中读取数据
	data = getDataFromDataBase();
    // 重新写入 Redis,以便后续从Redis中读取
	write2Redis(key ,data);
	// 设置 Re dis 的超时时间为 5 分钟
    setRedisExpireTime(5);
	}
}

上面的伪代码完成了上图所描述的过程。这样每当读取 Redis 数据超过 5 分钟, Redis就不能读到超时数据了,只能重新从 Redis 中读取,保证了一定的实时性,也避免了多次访问数据库造成的系统性能低下的情况。


Redis和数据库写

写操作要考虑数据一致的问题,尤其是那些重要的业务数据,所以首先应该考虑从数据库中读取最新的数据,然后对数据进行操作,最后把数据写入 Redis 缓存中.

流程大致如下

在这里插入图片描述

写入业务数据,先从数据库中读取最新数据,然后进行业务操作,更新业务数据到数据库后,再将数据刷新到 Redis 缓存中,这样就完成了一次写操作。这样的操作就能避免将脏数据写入数据库中,这类问题在操作时要注意。

伪代码大致如下

public  void writeData(args){
	//从数据库里读取最新数据
   DataObject dataObject = getFromDataBase(args);
	//执行业务逻辑
   execLogic(dataObject);
 	//更新数据库数据
	updateDataBase(dataObject );
	// 刷新 Red is 缓存
	updateRedisData(dataObject ) ;
}

上面的伪代码完成了上图所描述的过程。首先,从数据库中读取最新的数据,以规避缓存中的脏数据问题,执行了逻辑,修改了部分业务数据。然后,把这些数据保存到数据库里,最后,刷新这些数据到 Redis 中。


使用 Spring 缓存机制整合 Redis

工程结构

在这里插入图片描述

用到了

  • Spring
  • Spring Cache
  • Mybatis
  • Redis

需要将上述3者整合起来


pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>ssm_anno_redis</groupId>
	<artifactId>ssm_anno_redis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>ssm_anno_redis</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<!-- 单元测试 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

		<!-- 1.日志 -->
		<!-- 实现slf4j接口并整合 -->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.1</version>
		</dependency>

		<!-- 2.数据库 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.38</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.5.0</version>
		</dependency>

		<!-- DAO: MyBatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.4.2</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.3.1</version>
		</dependency>


		<!-- 4.Spring -->
		<!-- 1)Spring核心 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
		</dependency>
		<!-- 2)Spring DAO层 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
		</dependency>
	

		<!-- redis客户端:Jedis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.9.0</version>
		</dependency>

		<!-- spring-data-redis -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.8.15.RELEASE</version>
		</dependency>

	</dependencies>


	<!-- 在dependencyManagement中引入spring-framework-bom来确保所有的spring模块都使用统一的版本. 
		添加spring-framework-bom后,就不需要配置每个依赖的版本号了,方便管理与升级 -->
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework</groupId>
				<artifactId>spring-framework-bom</artifactId>
				<version>4.3.9.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<finalName>ssm_anno_redis</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.7.0</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
					<encoding>UTF8</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

DB Script & Redis Address

mysql 部署在CentOS6.5 ,版本为5.7 , 地址为192.168.31.66 。 同样的Redis也部署在这台主机上,单节点。

drop database artisan;
create database artisan;

use artisan;

create table t_role (
id int(12) auto_increment,
role_name varchar(60) not null,
note varchar(256) null,
primary key(id)
);

在这里插入图片描述


POJO类

package com.artisan.ssm_redis.domain;

import java.io.Serializable;

public class Role implements Serializable {

	
	private static final long serialVersionUID = -4381384997344901377L;

	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;
	}

	@Override
	public String toString() {
		return "Role [id=" + id + ", roleName=" + roleName + ", note=" + note + "]";
	}

}

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


搭建MyBatis环境

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="com.artisan.ssm_redis.dao.RoleDao">

	<select id="getRole" resultType="com.artisan.ssm_redis.domain.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="com.artisan.ssm_redis.domain.Role"
		useGeneratedKeys="true" keyProperty="id">
		insert into t_role (role_name, note) values(#{roleName}, #{note})
	</insert>

	<update id="updateRole" parameterType="com.artisan.ssm_redis.domain.Role">
		update t_role set role_name = #{roleName}, note = #{note}
		where id = #{id}
	</update>
	<select id="findRoles" resultType="com.artisan.ssm_redis.domain.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>  

RoleDao接口

Mapper映射文件完成后,需要一个 MyBatis 角色接口 , 以便使用这样的一个映射文件,

package com.artisan.ssm_redis.dao;

import java.util.List;

import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import com.artisan.ssm_redis.domain.Role;

@Repository
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);
}

注解@Repository 表示它是一个持久层的接口。通过扫描和注解联合定义 DAO 层,就完成了mappe的内容。


Service层接口

定义角色服务接口( RoleService ),因为要在接口实现类中加入 Spring 缓存注解,以驱动不同的行为,所里这里仅仅先将接口定义出来

package com.artisan.ssm_redis.service;

import java.util.List;

import com.artisan.ssm_redis.domain.Role;

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

	public int deleteRole(Long id);

	public Role insertRole(Role role);

	public int updateRole(Role role);

	public List<Role> findRoles(String roleName, String note);
	
	public int insertRoles(List<Role> roleList);
}

基于Java类的配置定义数据库和相关的扫描内容

RootConfig.java

package com.artisan.ssm_redis.config;

import java.io.IOException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration
// 定义Spring 扫描的包
@ComponentScan("com.artisan.ssm_redis*")
// 使用事务驱动管理器
@EnableTransactionManagement
// 实现接口TransactionManagementConfigurer,这样可以配置注解驱动事务
public class RootConfig implements TransactionManagementConfigurer {

	private DataSource dataSource = null;

	/**
	 * 
	 * 
	 * @Title: initDataSource
	 * 
	 * @Description: 配置数据库
	 * 
	 * 
	 * @return: DataSource
	 * @throws IOException
	 */
	@Bean(name = "dataSource")
	public DataSource initDataSource() throws IOException {
		if (dataSource != null) {
			return dataSource;
		}
		Properties props = new Properties();
		props.load(RootConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"));
		props.setProperty("driverClassName", props.getProperty("jdbc.driver"));
		props.setProperty("url", props.getProperty("jdbc.url"));
		props.setProperty("username", props.getProperty("jdbc.username"));
		props.setProperty("password", props.getProperty("jdbc.password"));
		try {
			dataSource = BasicDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return dataSource;
	}

	/**
	 * 
	 * 
	 * @Title: initSqlSessionFactory
	 * 
	 * @Description: 配置SqlSessionFactoryBean
	 * 
	 * 
	 * @return: SqlSessionFactoryBean
	 * @throws IOException
	 */
	@Bean(name = "sqlSessionFactory")
	public SqlSessionFactoryBean initSqlSessionFactory() throws IOException {
		SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
		sqlSessionFactory.setDataSource(initDataSource());
		// 配置MyBatis配置文件
		Resource resource = new ClassPathResource("mybatis/mybatis-config.xml");
		sqlSessionFactory.setConfigLocation(resource);
		return sqlSessionFactory;
	}

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

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

}

在 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>  
    <mappers>  
        <mapper resource="mapper/RoleMapper.xml"/>  
    </mappers>
</configuration>

写到这里就可以开始进行Dao和Service层的单元测试了,比较简单这里省略了先。。。

因为我们主要是整合Spring Cache和 Redis. 所以Spring Cache的基础知识 请参阅我的专栏 Spring-Cache手札


Spring的缓存管理器

在 Spring 项目 中它提供了接口 CacheManager 来定义缓存管理器 , 这样各个不同 的缓存就可以实现它来提供管理器的功能了,而在 spring-data-redis.jar 包中 实现 CacheManager接口的则是 RedisCacheManager, 因此要定义 RedisCacheManager 的 Bean , 不过在此之前要先定义 RedisTemplate。

下面使用注解驱动RedisCacheManager 定义

package com.artisan.ssm_redis.config;

import java.util.ArrayList;
import java.util.List;

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.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import redis.clients.jedis.JedisPoolConfig;

/**** imports ****/
@Configuration
@EnableCaching
public class RedisConfig {

	@Bean(name = "redisTemplate")
	public RedisTemplate initRedisTemplate() {
		JedisPoolConfig poolConfig = new JedisPoolConfig();
		// 最大空闲数
		poolConfig.setMaxIdle(50);
		// 最大连接数
		poolConfig.setMaxTotal(100);
		// 最大等待毫秒数
		poolConfig.setMaxWaitMillis(20000);
		// 创建Jedis连接工厂
		JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
		connectionFactory.setHostName("192.168.31.66");
		connectionFactory.setPort(6379);
		// 调用后初始化方法,没有它将抛出异常Cannot get Jedis connection
		connectionFactory.afterPropertiesSet();
		// 自定Redis序列化器
		RedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
		RedisSerializer stringRedisSerializer = new StringRedisSerializer();
		// 定义RedisTemplate,并设置连接工程
		RedisTemplate redisTemplate = new RedisTemplate();
		redisTemplate.setConnectionFactory(connectionFactory);
		// 设置序列化器
		redisTemplate.setDefaultSerializer(stringRedisSerializer);
		redisTemplate.setKeySerializer(stringRedisSerializer);
		redisTemplate.setValueSerializer(jdkSerializationRedisSerializer);
		redisTemplate.setHashKeySerializer(stringRedisSerializer);
		redisTemplate.setHashValueSerializer(jdkSerializationRedisSerializer);
		return redisTemplate;
	}

	@Bean(name = "redisCacheManager")
	public CacheManager initRedisCacheManager(@Autowired RedisTemplate redisTempate) {
		RedisCacheManager cacheManager = new RedisCacheManager(redisTempate);
		// 设置超时时间为10分钟,单位为秒
		cacheManager.setDefaultExpiration(600);
		// 设置缓存名称
		List<String> cacheNames = new ArrayList<String>();
		cacheNames.add("redisCacheManager");
		cacheManager.setCacheNames(cacheNames);
		return cacheManager;
	}
}

@EnableCaching 表示 Spring IoC 容器启动了缓存机制。

对于 RedisTemplate 的定义实例和 XML 的方式差不多。

注意,在创建 Jedis 连接工厂(JedisConnectionFactory )后,要自己调用其 afterPropertiesSet 方法 , 因为这里不是单独自定义一个 Spring Bean,而是在 XML方式中是单独 自定义的 .这个类实现了 InitializingBean 接口,按照 Spring Bean 的生命周期,它会被 Spring IoC 容器自己调用,而这里的注解方式没有定义 Spring Bean,因此需要自己调用.

字符串定义了 key (包括 hash 数据结构),而值则使用了序列化,这样就能够保存 Java对象了。缓存管理器 RedisCacheManager 定义了默认的超时时间为 10 分钟,这样就可以在一定的时间间隔后重新从数据库中读取数据了,而名称则定义为 redisCacheManager, 名称是为了方便后面注解引用的 。


Service层整合缓存

package com.artisan.ssm_redis.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.artisan.ssm_redis.dao.RoleDao;
import com.artisan.ssm_redis.domain.Role;
import com.artisan.ssm_redis.service.RoleService;


@Service
public class RoleServiceImpl implements RoleService {

	// 自动注入
	@Autowired
	private RoleDao roleDao;

	/**
	 * 使用@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 int updateRole(Role role) {
		return roleDao.updateRole(role);
	}


}

因为 getRole 方法是一个查询方法,所以使用@Cacheable 注解,这样在 Spring 的调用中,它就会先查询 Redis , 看看是否存在对应的值,那么采用什么 key 去查询呢?注解中的key 属性,它配置的'redis_role_'+#id, 这样 Spring EL 就会计算返回 一个 key ,比如参数id 为 1L , 其 key 计算结果就为 redis_role_1。以一个 key 去访问 Redis ,如果有返回值,则不再执行方法,如果没有则访问方法 , 返回角色信息,然后通过 key 去保存数据到 Redis 中 。

先执行 insertRole 方法才能把对应的信息保存到 Redis 中,所以采用的是注解@CachePut。由于主键是由数据库生成,所以无法从参数中读取,但是可以从结果中读取,那么 #result.id 的写法就会返回方法返回的角色 id。而这个角色 id 是通过数据库生成,然后由 MyBatis 进行回填得到的 ,这样就可以在 Redis 中新增一个 key , 然后保存对应的对象了。

对于 updateRole 方法而言,采用的是注解@CachePut,由于对象有所更新,所以要在方法之后更新 Redis 的数据,以保证数据的一致性。这里直接读取参数的 id ,所以表达式写#role.id,这样就可以引入角色参数的 id 了 。在方法结束后,它就会去更新 Redis 对应的 key 的值了。

为此可以提供一个 log4j .properties 文件来监控整个过程:

log4j.rootLogger=DEBUG , stdout
log4j.logger.org.mybatis=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p %d %C: %m%n

写下单元测试来验证下

package com.artisan.ssm_redis.service;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.artisan.ssm_redis.config.RedisConfig;
import com.artisan.ssm_redis.config.RootConfig;
import com.artisan.ssm_redis.domain.Role;

public class RoleServiceTest {

	public static void main(String[] args) {
		// 使用注解Spring IoC容器
		ApplicationContext ctx = new AnnotationConfigApplicationContext(RootConfig.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 role2 = roleService.getRole(role.getId());
		System.out.println(role2.toString());
		// 更新角色
		role2.setNote("role_note_1_update");
		roleService.updateRole(role2);
		System.out.println(role2.toString());
		// 删除角色
		// roleService.deleteRole(role2.getId());
	}

}

这里将关于数据库和 Redis 的相关配置通过注解 Spring IoC 容器加载进来 , 这样就可以用 Spring 操作这些资源了,然后执行插入 、 获取 、 更新角色的方法 ,日志如下

23:59:14.151 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
23:59:14.161 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7674b62c]
23:59:14.171 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [1168924571, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] will be managed by Spring
23:59:14.171 [main] DEBUG com.artisan.ssm_redis.dao.RoleDao.insertRole - ==>  Preparing: insert into t_role (role_name, note) values(?, ?) 
23:59:14.221 [main] DEBUG com.artisan.ssm_redis.dao.RoleDao.insertRole - ==> Parameters: role_name_1(String), role_note_1(String)
23:59:14.221 [main] DEBUG com.artisan.ssm_redis.dao.RoleDao.insertRole - <==    Updates: 1
23:59:14.231 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7674b62c]
23:59:14.321 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Opening RedisConnection
23:59:14.464 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Closing Redis Connection
23:59:14.464 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7674b62c]
23:59:14.464 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7674b62c]
23:59:14.474 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7674b62c]
23:59:14.474 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
23:59:14.474 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [1168924571, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java]
23:59:14.474 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Resetting isolation level of JDBC Connection [1168924571, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to 4
23:59:14.484 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [1168924571, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] after transaction
23:59:14.484 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
23:59:14.484 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.artisan.ssm_redis.service.impl.RoleServiceImpl.getRole]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
23:59:14.484 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [1280429864, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] for JDBC transaction
23:59:14.484 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Changing isolation level of JDBC Connection [1280429864, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to 2
23:59:14.484 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [1280429864, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to manual commit
23:59:14.484 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Opening RedisConnection
23:59:14.484 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Closing Redis Connection
23:59:14.484 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Opening RedisConnection
23:59:14.494 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Closing Redis Connection
23:59:14.584 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
23:59:14.584 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [1280429864, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java]
23:59:14.594 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Resetting isolation level of JDBC Connection [1280429864, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to 4
23:59:14.594 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [1280429864, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] after transaction
23:59:14.594 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
Role [id=30, roleName=role_name_1, note=role_note_1]
23:59:14.594 [main] DEBUG org.springframework.transaction.annotation.AnnotationTransactionAttributeSource - Adding transactional method 'com.artisan.ssm_redis.service.impl.RoleServiceImpl.updateRole' with attribute: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
23:59:14.594 [main] DEBUG org.springframework.cache.annotation.AnnotationCacheOperationSource - Adding cacheable method 'updateRole' with attribute: [Builder[public int com.artisan.ssm_redis.service.impl.RoleServiceImpl.updateRole(com.artisan.ssm_redis.domain.Role)] caches=[redisCacheManager] | key=''redis_role_'+#role.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='']
23:59:14.594 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name [com.artisan.ssm_redis.service.impl.RoleServiceImpl.updateRole]: PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED; ''
23:59:14.594 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Acquired Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] for JDBC transaction
23:59:14.594 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Changing isolation level of JDBC Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to 2
23:59:14.604 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Switching JDBC Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to manual commit
23:59:14.604 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
23:59:14.604 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@737a135b]
23:59:14.604 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] will be managed by Spring
23:59:14.604 [main] DEBUG com.artisan.ssm_redis.dao.RoleDao.updateRole - ==>  Preparing: update t_role set role_name = ?, note = ? where id = ? 
23:59:14.604 [main] DEBUG com.artisan.ssm_redis.dao.RoleDao.updateRole - ==> Parameters: role_name_1(String), role_note_1_update(String), 30(Long)
23:59:14.604 [main] DEBUG com.artisan.ssm_redis.dao.RoleDao.updateRole - <==    Updates: 1
23:59:14.604 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@737a135b]
23:59:14.604 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Opening RedisConnection
23:59:14.614 [main] DEBUG org.springframework.data.redis.core.RedisConnectionUtils - Closing Redis Connection
23:59:14.614 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@737a135b]
23:59:14.614 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@737a135b]
23:59:14.614 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@737a135b]
23:59:14.614 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction commit
23:59:14.614 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Committing JDBC transaction on Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java]
23:59:14.614 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Resetting isolation level of JDBC Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] to 4
23:59:14.614 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [90346768, URL=jdbc:mysql://192.168.31.66:3306/artisan?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT, UserName=root@192.168.31.137, MySQL Connector Java] after transaction
23:59:14.614 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
Role [id=30, roleName=role_name_1, note=role_note_1_update]

从日志可以看到,先插入了一个角色对象,所以有 insert 语旬的执行,跟着可以看到Redis 连接的打开和关闭, Spring 将值保存到 Redis 中 。

对于 getRole 方法,则没有看到 SQL的执行,因为使用@Cacheable 注解后,它先在 Redis 上查找,找到数据就返回了,所以这里中断了我们本可以看到的 Redis 连接的闭合。

对于 updateRole 方法而言,则是先去执行SQL , 更新数据后 , 再执行 Redis 的命令,这样更新到数据库的数据就和 Redis 的数据同步了。

因为在缓存管理器中设置了超时时间为 10 分钟,所以如果10 分钟后再用相同的 id去调用 getRole 方法,它就会通过调用方法将数据从数据库中取回了。 这里可自行验证。


继续看删除和其他的方法

	/**
	 * 使用@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);
	}

在方法执行完成后会移除对应的缓存,也就是还可以从方法内读取到缓存服务器中的数据。如果属性 beforelnvocation 声明为 true,则在方法前删除缓存数据,这样就不能在方法中读取缓存数据了,只是这个值的默认值为 false,所以默认的情况下只会在方法后执行删除缓存。


不适用续存的方法:

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

findRoles方法我们这里没加缓存,使用缓存的前提一一高命中率,由于这里根据角色名称和备注查找角色信息,该方法的返回值会根据查询条件而多样化,导致其不确定和命中率低下,对于这样的场景,使用缓存并不能有效提高性能,所以这样的场景,就不再使用缓存了。



自调用失效问题:

	@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 缓存上 ,因为缓存注解也是基于 SpringAOP 实现的 ,对于 SpringAOP 的基础是动态代理技术,也就是只有代理对象的相互调用,AOP 才有拦截的功能,才能执行缓存注解提供的功能。而这里的自调用是没有代理对象存在的 ,所以其注解功能也就失效了 。