javascript
Redis-20Spring缓存机制整合Redis
文章目錄
- 概述
- Redis 和數(shù)據(jù)庫讀
- Redis和數(shù)據(jù)庫寫
- 使用 Spring 緩存機(jī)制整合 Redis
- 工程結(jié)構(gòu)
- pom.xml
- DB Script & Redis Address
- POJO類
- 搭建MyBatis環(huán)境
- RoleMapper.xml
- RoleDao接口
- Service層接口
- 基于Java類的配置定義數(shù)據(jù)庫和相關(guān)的掃描內(nèi)容
- Spring的緩存管理器
- Service層整合緩存
概述
這里用一個示例通過注解的方式整合 Spring 和 Redis,要特別注意 Redis 緩存和數(shù)據(jù)庫一致性的問題。
Redis 和數(shù)據(jù)庫讀
數(shù)據(jù)緩存往往會在 Redis 上設(shè)置超時時間,當(dāng)設(shè)置 Redis 的數(shù)據(jù)超時后, Redis 就沒法讀出數(shù)據(jù)了 , 這個時候就會觸發(fā)程序讀取數(shù)據(jù)庫 , 然后將讀取的數(shù)據(jù)庫數(shù)據(jù)寫入 Redis (此時會給 Redis 重設(shè)超時時間 ),這樣程序在讀取的過程中就能按一定的時間間隔刷新數(shù)據(jù)了。
流程圖大致如下:
偽代碼大致如下
public Data getData(args){ // 從Redis中獲取數(shù)據(jù) Data data = getDataFromRedis(key); // 如果redis中沒有數(shù)據(jù),從db中加載并寫入redis if( data = null)// 從數(shù)據(jù)庫中讀取數(shù)據(jù)data = getDataFromDataBase();// 重新寫入 Redis,以便后續(xù)從Redis中讀取write2Redis(key ,data);// 設(shè)置 Re dis 的超時時間為 5 分鐘setRedisExpireTime(5);} }上面的偽代碼完成了上圖所描述的過程。這樣每當(dāng)讀取 Redis 數(shù)據(jù)超過 5 分鐘, Redis就不能讀到超時數(shù)據(jù)了,只能重新從 Redis 中讀取,保證了一定的實時性,也避免了多次訪問數(shù)據(jù)庫造成的系統(tǒng)性能低下的情況。
Redis和數(shù)據(jù)庫寫
寫操作要考慮數(shù)據(jù)一致的問題,尤其是那些重要的業(yè)務(wù)數(shù)據(jù),所以首先應(yīng)該考慮從數(shù)據(jù)庫中讀取最新的數(shù)據(jù),然后對數(shù)據(jù)進(jìn)行操作,最后把數(shù)據(jù)寫入 Redis 緩存中.
流程大致如下
寫入業(yè)務(wù)數(shù)據(jù),先從數(shù)據(jù)庫中讀取最新數(shù)據(jù),然后進(jìn)行業(yè)務(wù)操作,更新業(yè)務(wù)數(shù)據(jù)到數(shù)據(jù)庫后,再將數(shù)據(jù)刷新到 Redis 緩存中,這樣就完成了一次寫操作。這樣的操作就能避免將臟數(shù)據(jù)寫入數(shù)據(jù)庫中,這類問題在操作時要注意。
偽代碼大致如下
public void writeData(args){//從數(shù)據(jù)庫里讀取最新數(shù)據(jù)DataObject dataObject = getFromDataBase(args);//執(zhí)行業(yè)務(wù)邏輯execLogic(dataObject);//更新數(shù)據(jù)庫數(shù)據(jù)updateDataBase(dataObject );// 刷新 Red is 緩存updateRedisData(dataObject ) ; }上面的偽代碼完成了上圖所描述的過程。首先,從數(shù)據(jù)庫中讀取最新的數(shù)據(jù),以規(guī)避緩存中的臟數(shù)據(jù)問題,執(zhí)行了邏輯,修改了部分業(yè)務(wù)數(shù)據(jù)。然后,把這些數(shù)據(jù)保存到數(shù)據(jù)庫里,最后,刷新這些數(shù)據(jù)到 Redis 中。
使用 Spring 緩存機(jī)制整合 Redis
工程結(jié)構(gòu)
用到了
- 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.日志 --><!-- 實現(xiàn)slf4j接口并整合 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.1</version></dependency><!-- 2.數(shù)據(jù)庫 --><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模塊都使用統(tǒng)一的版本. 添加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也部署在這臺主機(jī)上,單節(jié)點。
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;}@Overridepublic String toString() {return "Role [id=" + id + ", roleName=" + roleName + ", note=" + note + "]";}}該類實現(xiàn)了 Serializable 接口,這說明這個類支持序列化,這樣就可以通過 Spring的序列化器,將其保存為對應(yīng)的編碼,緩存到 Redis 中,也可以通過 Redis 讀回那些編碼,反序列化為對應(yīng)的 Java 對象。
搭建MyBatis環(huán)境
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 asroleName, 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 表示它是一個持久層的接口。通過掃描和注解聯(lián)合定義 DAO 層,就完成了mappe的內(nèi)容。
Service層接口
定義角色服務(wù)接口( RoleService ),因為要在接口實現(xiàn)類中加入 Spring 緩存注解,以驅(qū)動不同的行為,所里這里僅僅先將接口定義出來
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類的配置定義數(shù)據(jù)庫和相關(guān)的掃描內(nèi)容
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*") // 使用事務(wù)驅(qū)動管理器 @EnableTransactionManagement // 實現(xiàn)接口TransactionManagementConfigurer,這樣可以配置注解驅(qū)動事務(wù) public class RootConfig implements TransactionManagementConfigurer {private DataSource dataSource = null;/*** * * @Title: initDataSource* * @Description: 配置數(shù)據(jù)庫* * * @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: 通過自動掃描,發(fā)現(xiàn)MyBatis Mapper接口* * * @return: MapperScannerConfigurer Mapper掃描器*/@Beanpublic MapperScannerConfigurer initMapperScannerConfigurer() {MapperScannerConfigurer msc = new MapperScannerConfigurer();// 掃描包msc.setBasePackage("com.artisan.ssm_redis");msc.setSqlSessionFactoryBeanName("sqlSessionFactory");// 區(qū)分注解掃描msc.setAnnotationClass(Repository.class);return msc;}/*** 實現(xiàn)接口方法,注冊注解事務(wù),當(dāng)@Transactional 使用的時候產(chǎn)生數(shù)據(jù)庫事務(wù)*/@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 的 定義中引入了關(guān)于 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>寫到這里就可以開始進(jìn)行Dao和Service層的單元測試了,比較簡單這里省略了先。。。
因為我們主要是整合Spring Cache和 Redis. 所以Spring Cache的基礎(chǔ)知識 請參閱我的專欄 Spring-Cache手札
Spring的緩存管理器
在 Spring 項目 中它提供了接口 CacheManager 來定義緩存管理器 , 這樣各個不同 的緩存就可以實現(xiàn)它來提供管理器的功能了,而在 spring-data-redis.jar 包中 實現(xiàn) CacheManager接口的則是 RedisCacheManager, 因此要定義 RedisCacheManager 的 Bean , 不過在此之前要先定義 RedisTemplate。
下面使用注解驅(qū)動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();// 最大空閑數(shù)poolConfig.setMaxIdle(50);// 最大連接數(shù)poolConfig.setMaxTotal(100);// 最大等待毫秒數(shù)poolConfig.setMaxWaitMillis(20000);// 創(chuàng)建Jedis連接工廠JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);connectionFactory.setHostName("192.168.31.66");connectionFactory.setPort(6379);// 調(diào)用后初始化方法,沒有它將拋出異常Cannot get Jedis connectionconnectionFactory.afterPropertiesSet();// 自定Redis序列化器RedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();RedisSerializer stringRedisSerializer = new StringRedisSerializer();// 定義RedisTemplate,并設(shè)置連接工程RedisTemplate redisTemplate = new RedisTemplate();redisTemplate.setConnectionFactory(connectionFactory);// 設(shè)置序列化器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);// 設(shè)置超時時間為10分鐘,單位為秒cacheManager.setDefaultExpiration(600);// 設(shè)置緩存名稱List<String> cacheNames = new ArrayList<String>();cacheNames.add("redisCacheManager");cacheManager.setCacheNames(cacheNames);return cacheManager;} }@EnableCaching 表示 Spring IoC 容器啟動了緩存機(jī)制。
對于 RedisTemplate 的定義實例和 XML 的方式差不多。
注意,在創(chuàng)建 Jedis 連接工廠(JedisConnectionFactory )后,要自己調(diào)用其 afterPropertiesSet 方法 , 因為這里不是單獨自定義一個 Spring Bean,而是在 XML方式中是單獨 自定義的 .這個類實現(xiàn)了 InitializingBean 接口,按照 Spring Bean 的生命周期,它會被 Spring IoC 容器自己調(diào)用,而這里的注解方式?jīng)]有定義 Spring Bean,因此需要自己調(diào)用.
字符串定義了 key (包括 hash 數(shù)據(jù)結(jié)構(gòu)),而值則使用了序列化,這樣就能夠保存 Java對象了。緩存管理器 RedisCacheManager 定義了默認(rèn)的超時時間為 10 分鐘,這樣就可以在一定的時間間隔后重新從數(shù)據(jù)庫中讀取數(shù)據(jù)了,而名稱則定義為 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 {// 自動注入@Autowiredprivate RoleDao roleDao;/*** 使用@Cacheable定義緩存策略 當(dāng)緩存中有值,則返回緩存數(shù)據(jù),否則訪問方法得到數(shù)據(jù) 通過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則表示無論如何都會執(zhí)行方法,最后將方法的返回值再保存到緩存中* 使用在插入數(shù)據(jù)的地方,則表示保存到數(shù)據(jù)庫后,會同期插入到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,表示更新數(shù)據(jù)庫數(shù)據(jù)的同時,也會同步更新緩存* * @param role* 角色對象* @return 影響條數(shù)*/@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 的調(diào)用中,它就會先查詢 Redis , 看看是否存在對應(yīng)的值,那么采用什么 key 去查詢呢?注解中的key 屬性,它配置的'redis_role_'+#id, 這樣 Spring EL 就會計算返回 一個 key ,比如參數(shù)id 為 1L , 其 key 計算結(jié)果就為 redis_role_1。以一個 key 去訪問 Redis ,如果有返回值,則不再執(zhí)行方法,如果沒有則訪問方法 , 返回角色信息,然后通過 key 去保存數(shù)據(jù)到 Redis 中 。
先執(zhí)行 insertRole 方法才能把對應(yīng)的信息保存到 Redis 中,所以采用的是注解@CachePut。由于主鍵是由數(shù)據(jù)庫生成,所以無法從參數(shù)中讀取,但是可以從結(jié)果中讀取,那么 #result.id 的寫法就會返回方法返回的角色 id。而這個角色 id 是通過數(shù)據(jù)庫生成,然后由 MyBatis 進(jìn)行回填得到的 ,這樣就可以在 Redis 中新增一個 key , 然后保存對應(yīng)的對象了。
對于 updateRole 方法而言,采用的是注解@CachePut,由于對象有所更新,所以要在方法之后更新 Redis 的數(shù)據(jù),以保證數(shù)據(jù)的一致性。這里直接讀取參數(shù)的 id ,所以表達(dá)式寫#role.id,這樣就可以引入角色參數(shù)的 id 了 。在方法結(jié)束后,它就會去更新 Redis 對應(yīng)的 key 的值了。
為此可以提供一個 log4j .properties 文件來監(jiān)控整個過程:
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);// 獲取角色服務(wù)類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());}}這里將關(guān)于數(shù)據(jù)庫和 Redis 的相關(guān)配置通過注解 Spring IoC 容器加載進(jìn)來 , 這樣就可以用 Spring 操作這些資源了,然后執(zhí)行插入 、 獲取 、 更新角色的方法 ,日志如下
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 語旬的執(zhí)行,跟著可以看到Redis 連接的打開和關(guān)閉, Spring 將值保存到 Redis 中 。
對于 getRole 方法,則沒有看到 SQL的執(zhí)行,因為使用@Cacheable 注解后,它先在 Redis 上查找,找到數(shù)據(jù)就返回了,所以這里中斷了我們本可以看到的 Redis 連接的閉合。
對于 updateRole 方法而言,則是先去執(zhí)行SQL , 更新數(shù)據(jù)后 , 再執(zhí)行 Redis 的命令,這樣更新到數(shù)據(jù)庫的數(shù)據(jù)就和 Redis 的數(shù)據(jù)同步了。
因為在緩存管理器中設(shè)置了超時時間為 10 分鐘,所以如果10 分鐘后再用相同的 id去調(diào)用 getRole 方法,它就會通過調(diào)用方法將數(shù)據(jù)從數(shù)據(jù)庫中取回了。 這里可自行驗證。
繼續(xù)看刪除和其他的方法
/*** 使用@CacheEvict刪除緩存對應(yīng)的key* * @param id* 角色編號* @return 返回刪除記錄數(shù)*/@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);}在方法執(zhí)行完成后會移除對應(yīng)的緩存,也就是還可以從方法內(nèi)讀取到緩存服務(wù)器中的數(shù)據(jù)。如果屬性 beforelnvocation 聲明為 true,則在方法前刪除緩存數(shù)據(jù),這樣就不能在方法中讀取緩存數(shù)據(jù)了,只是這個值的默認(rèn)值為 false,所以默認(rèn)的情況下只會在方法后執(zhí)行刪除緩存。
不適用續(xù)存的方法:
@Override@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)public List<Role> findRoles(String roleName, String note) {return roleDao.findRoles(roleName, note);}findRoles方法我們這里沒加緩存,使用緩存的前提一一高命中率,由于這里根據(jù)角色名稱和備注查找角色信息,該方法的返回值會根據(jù)查詢條件而多樣化,導(dǎo)致其不確定和命中率低下,對于這樣的場景,使用緩存并不能有效提高性能,所以這樣的場景,就不再使用緩存了。
自調(diào)用失效問題:
@Override@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public int insertRoles(List<Role> roleList) {for (Role role : roleList) {// 同一類的方法調(diào)用自己方法,產(chǎn)生自調(diào)用[插入:失效]問題this.insertRole(role);}return roleList.size();}在 insertRoles 方法中調(diào)用了同一個類中帶有注解@CachePut 的 insertRole 方法,然而 Spring 并沒有把對應(yīng)新增的角色保存到 Redis 緩存上 ,因為緩存注解也是基于 SpringAOP 實現(xiàn)的 ,對于 SpringAOP 的基礎(chǔ)是動態(tài)代理技術(shù),也就是只有代理對象的相互調(diào)用,AOP 才有攔截的功能,才能執(zhí)行緩存注解提供的功能。而這里的自調(diào)用是沒有代理對象存在的 ,所以其注解功能也就失效了 。
總結(jié)
以上是生活随笔為你收集整理的Redis-20Spring缓存机制整合Redis的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis-18Redis主从同步
- 下一篇: Spring Cache-缓存注解(二)