javascript
Spring整合Redis详解
用注解驅動的方式來使用 Redis。和數據庫事務一樣,Spring 提供了緩存的管理器和相關的注解來支持類似于 Redis 這樣的鍵值對緩存。
準備測試環境
首先,定義一個簡單的角色 POJO,代碼如下所示。
package com.pojo; import java.io.Serializable; public class Role implements Serializable {private static final long serialVersionUID = 3447499459461375642L;private long id;private String roleName;private String note;// 省略setter和getter方法 }注意:該類實現了 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.dao.RoleDao"><select id="getRole" resultType="com.pojo.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.pojo.Role"useGeneratedKeys="true" keyProperty="id">insert into t_role (role_name,note) values(#{roleName}, #{note})</insert><update id="updateRole" parameterType="com.pojo.Role">update t_role setrole_name = #{roleName}, note = #{note} where id = #{id}</update><select id="findRoles" resultType="com.pojo.Role">select id, role_name as roleName, note from t_role<where><if test="roleName != null">role_name like concat('%', #{roleName}, '%')</if><if test="note != null">note like concat ('%', #{note},'%')</if></where></select> </mapper>然后,需要一個 MyBatis 角色接口,以便使用這樣的一個映射文件,代碼如下所示。
package com.dao;import java.util.List; import org.apache.ibatis.annotations.Param; import com.pojo.Role;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 層,就完成了映射器方面的內容。定義角色服務接口(RoleService),代碼如下所示,不過服務接口實現類會在后面談起,因為它需要加入 Spring 緩存注解,以驅動不同的行為。
package com.service;import java.util.List; import com.pojo.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); }通過 Java 配置定義數據庫和相關的掃描內容,代碼如下所示。
package com.config;import java.util.Properties;import javax.sql.DataSource;import org.apache.commons.dbcp.BasicDataSourceFactory; import org.aspectj.apache.bcel.Repository; 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.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.TransactionManagementConfigurer;@Configuration // 定義Spring掃描的包 @ComponentScan("com.*") // 使用事務驅動管理器 @EnableTransactionManagement // 實現接口 TransactionManagementConfigurer,這樣可以配置注解驅動事務 public class RootConfig implements TransactionManagementConfigurer {private DataSource dataSource = null;/*** 配置數據庫** @return數據連接池*/@Bean(name = "dataSource")public DataSource initDataSource() {if (dataSource != null) {return dataSource;}Properties props = new Properties();props.setProperty("driverClassName", "com.mysql.jdbc.Driver");props.setProperty("url", "jdbc:mysql://localhost:3306/redis");props.setProperty("username", "root");props.setProperty("password", "1128");try {dataSource = BasicDataSourceFactory.createDataSource(props);} catch (Exception e) {e.printStackTrace();}return dataSource;}/*** 配置 SqlSessionFactoryBean** @return SqlSessionFactoryBean*/@Bean(name = "SqlSessionFactory")public SqlSessionFactoryBean initSqlSessionFactory() {SqlSessionFactoryBean SqlSessionFactory = new SqlSessionFactoryBean();SqlSessionFactory.setDataSource(initDataSource());// 配置MyBatis配置文件Resource resource = new ClassPathResource("mybatis/mybatis-config.xml");SqlSessionFactory.setConfigLocation(resource);return SqlSessionFactory;}/*** 通過自動掃描,發現MyBatis Mapper接口** @return Mapper 掃描器*/@Beanpublic MapperScannerConfigurer initMapperScannerConfigurer() {MapperScannerConfigurer msc = new MapperScannerConfigurer();// 掃描包msc.setBasePackage("com.*");msc.setSqlSessionFactoryBeanName("SqlSessionFactory");// 區分注解掃描msc.setAnnotationClass(Repository.class);return msc;}/*** 實現接口方法,注冊注解事務,當@Transactional使用的時候產生數據庫事務*/@Override@Bean(name = "annotationDrivenTransactionManager")public PlatformTransactionManager annotationDrivenTransactionManager() {DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(initDataSource());return transactionManager;} }在 SqlSessionFactoryBean 的定義中引入了關于 MyBatis 的一個配置文件——mybatis-config.xml,它放在源碼的 mybatis 目錄之下,它的作用是引入 RoleMapper.xml,我放在目錄 com/dao 下,代碼如下所示。
<?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="com/dao/RoleMapper.xml" /></mappers> </configuration>這樣測試只要一個 RoleService 實現類就可以了,這個類的實現就是我們后面所需要討論的主要的內容,不過在此之前要先了解 Spring 的緩存管理器。
Spring 的緩存管理器
在 Spring 項目中它提供了接口 CacheManager 來定義緩存管理器,這樣各個不同的緩存就可以實現它來提供管理器的功能了,而在 spring-data-redis.jar 包中實現 CacheManager 接口的則是 RedisCacheManager,因此要定義 RedisCacheManager 的 Bean,不過在此之前要先定義 RedisTemplate。
下面使用注解驅動 RedisCacheManager 定義,代碼如下所示。
package com.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;@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 = JedisConnectionFactory(poolConfig);connectionFactory.setHostName("localhost");connectionFactory.setPort(6379);// 調用后初始化方法,沒有它將拋出異常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 容器自己調用,而這里的注解方式沒有定義 SpringBean,因此需要自己調用。
字符串定義了 key(包括 hash 數據結構),而值則使用了序列化,這樣就能夠保存 Java 對象了。
緩存管理器 RedisCacheManager 定義了默認的超時時間為 10 分鐘,這樣就可以在一定的時間間隔后重新從數據庫中讀取數據了,而名稱則定義為 redisCacheManager,名稱是為了方便后面注解引用的。
這里只定義 RedisCacheManager,使用 XML 定義緩存管理器代碼如下所示。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:cache="http://www.springframework.org/schema/cache"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/cachehttp://www.springframework.org/schema/cache/spring-cache.5.0.xsd"><!--使用注解驅動,其中屬性cache-manager默認值為cacheManager, 所以如果你的緩存管理器名稱也是cacheManager則無需重新定義 --><cache:annotation-driven cache-manager="redisCacheManager" /><!-- 定義緩存管理器,如果你使用id="cacheManager",則驅動不需要顯式配置cache-manager屬性 --><bean id="redisCacheManager"class="org.springframework.data.redis.cache.RedisCacheManager"><!--通過構造方法注入RedisTemplate --><constructor-arg index="0" ref="redisTemplate" /><!-- 定義默認超時時間,單位秒 --><property name="defaultExpiration" value="600" /><!--緩存管理器名稱 --><property name="cacheNames"><list><value>redisCacheManager</value></list></property></bean> </beans>這樣也可以配置好對應的緩存管理器。
緩存注解簡介
配置了緩存管理器之后,Spring 就允許用注解的方式使用緩存了,這里的注解有 4 個。XML 也可以使用它們,但是用得不多,我們就不再介紹了,還是以注解為主。首先簡介一下緩存注解,如下表所示。
緩存注解
| @Cacheable | 表明在進入方法之前,Spring 會先去緩存服務器中査找對應 key 的緩存值,如果找到緩存值,那么 Spring 將不會再調用方法,而是將緩存值讀出,返回給調用者;如果沒有找到緩存值,那么 Spring 就會執行你的方法,將最后的結果通過 key 保存到緩存服務器中 |
| @CachePut | Spring 會將該方法返回的值緩存到緩存服務器中,這里需要注意的是,Spring 不會事先去緩存服務器中查找,而是直接執行方法,然后緩存。換句話說,該方法始終會被 Spring 所調用 |
| @CacheEvict | 移除緩存對應的 key 的值 |
| @Caching | 這是個分組注解,它能夠同時應用于其他緩存的注解 |
注解 @Cacheable 和 @CachePut 都可以保存緩存鍵值對,只是它們的方式略有不同,請注意二者的區別,它們只能運用于有返回值的方法中,而刪除緩存 key 的 @CacheEvict 則可以用在 void 的方法上,因為它并不需要去保存任何值。
上述注解都能標注到類或者方法之上,如果放到類上,則對所有的方法都有效;如果放到方法上,則只是對方法有效。在大部分情況下,會放置到方法上。因為 @Cacheable 和 @CachePut 可以配置的屬性接近,所以把它們歸為一類去介紹,而 @Caching 不常用。
一般而言,對于查詢,我們會考慮使用 @Cacheable;對于插入和修改,我們會考慮使用 @CachePut;對于刪除操作,我們會考慮使用 @CacheEvict。
注解@Cacheable和@CachePut
因為 @Cacheable 和 @CachePut 兩個注解的配置項比較接近,所以這里就將這兩個注解一并介紹了,它們的屬性,如下表所示。
@Cacheable 和 @CachePut 配置屬性
其中,因為 value 和 key 這兩個屬性使用得最多,所以先來討論這兩個屬性。value 是一個數組,可以引用多個緩存管理器。
比如使用注解驅動 RedisCacheManager 定義代碼中所定義的 RedisCacheManager,就可以引用它了,而對于 key 則是緩存中的鍵,它支持 Spring 表達式,通過 Spring 表達式就可以自定義緩存的 key。編寫剩下的 RoleService 接口的實現類——RoleServiceImpl 的方法。
先了解一些 Spring 表達式和緩存注解之間的約定,通過這些約定去引用方法的參數和返回值的內容,使得其注入 key 所定義的 Spring 表達式的結果中,表達式值的引用如下表所示。
表達式值的引用
這樣就方便使用對應的參數或者返回值作為緩存的 key 了。
RoleService 接口的實現類——RoleServiceImpl,它有 3 個方法,使用這個緩存可以啟動緩存管理器來保存數據,代碼如下所示。
package com.service;import java.util.List;import org.springframework.beans.factory.annotation.Autowired; 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.dao.RoleDao; import com.pojo.Role;@Service public class RoleServiceImpl implements RoleService { //角色DAO,方便執行SQL@Autowiredprivate RoleDao roleDao = null;/*** 使用@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_'+#resuIt.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 Role updateRole(Role role) {roleDao.updateRole(role);return 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 文件來監控整個過程:
# Global logging configuration log4j.rootLogger=DEBUG,stdout # MyBatis logging configuration... log4j.logger.com.mybatis=DEBUG # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n然后通過以下代碼來測試緩存注解。
// 使用注解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 getRole = roleService.getRole(role.getId()); getRole.setNote("role_note_1_update"); // 更新角色 roleService.updateRole(getRole);將關于數據庫和 Redis 的相關配置通過注解 Spring IoC 容器加載進來,這樣就可以用 Spring 操作這些資源了,然后執行插入、獲取、更新角色的方法。
總結
以上是生活随笔為你收集整理的Spring整合Redis详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jsoup获取所有链接
- 下一篇: C语言二级指针