javascript
Redis-20Spring缓存机制整合Redis
文章目錄
- 概述
- Redis 和數據庫讀
- Redis和數據庫寫
- 使用 Spring 緩存機制整合 Redis
- 工程結構
- pom.xml
- DB Script & Redis Address
- POJO類
- 搭建MyBatis環境
- RoleMapper.xml
- RoleDao接口
- Service層接口
- 基于Java類的配置定義數據庫和相關的掃描內容
- Spring的緩存管理器
- Service層整合緩存
概述
這里用一個示例通過注解的方式整合 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;}@Overridepublic 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 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 表示它是一個持久層的接口。通過掃描和注解聯合定義 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掃描器*/@Beanpublic 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 connectionconnectionFactory.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 {// 自動注入@Autowiredprivate 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 才有攔截的功能,才能執行緩存注解提供的功能。而這里的自調用是沒有代理對象存在的 ,所以其注解功能也就失效了 。
總結
以上是生活随笔為你收集整理的Redis-20Spring缓存机制整合Redis的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis-18Redis主从同步
- 下一篇: Spring Cache-缓存注解(二)