Spring Cache整合Redis完成服务层简单缓存
2017-09-23 20:44
543 查看
设计目标
Service→缓存→数据库初次查询数据来自数据库,再次查询来自缓存
有增删改,令缓存失效
项目结构
主要关注:service、jpadao、entity几个源码包和测试包
配置文件在resources下的-jpa文件和cache目录下的-redis文件
建表语句在
\showcase\src\test\resources\init-table-user.sql
日志配置在
\showcase\src\test\resources\logback-test.xml
Spring Data JPA 完成Dao的编写
依赖管理
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.11.7.RELEASE</version> </dependency> <!-- hibernate jpa--> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.3.7.Final</version> </dependency> <dependency> <groupId>javax.el</groupId> <artifactId>javax.el-api</artifactId> <version>2.2.4</version> </dependency>
当然还有其他依赖,如下面要用到的h2数据库,spring的context等,读者运行测试用例时缺什么包再依赖什么包吧。
实体类 org.lanqiao.showcase.entity.User
@Entity @Table(name = "t_user") public class User implements Serializable{ private Integer id; private String username; private String password; private Integer status; private Integer teamId; @Id @GeneratedValue(strategy =GenerationType.AUTO) public Integer getId() { return id; } @Column(name="team_id") public Integer getTeamId() { return teamId; } // 其余略
sql:init-table-user.sql
CREATE TABLE IF NOT EXISTS t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(100), password VARCHAR(100), status INT, team_id INT, PRIMARY KEY (`id`) ); INSERT INTO t_user (username, password, status,team_id) VALUES ('aaa','aaa',1,1); INSERT INTO t_user (username, password, status,team_id) VALUES ('bbb','aaa',1,1); INSERT INTO t_user (username, password, status,team_id) VALUES ('ccc','aaa',1,2); INSERT INTO t_user (username, password, status,team_id) VALUES ('ddd','aaa',1,2); CREATE TABLE t_team( id INT AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (`id`) ); INSERT INTO t_team (name) VALUES ('黑山老妖'); INSERT INTO t_team (name) VALUES ('天山童母');
spring xml applicationContext-jpa.xml
<!-- Jpa Entity Manager 配置 entityManager是crud的API--> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/> <!--扫描实体@Entity--> <property name="packagesToScan" value="org.lanqiao.showcase.entity"/> <property name="jpaProperties"> <props> <!-- 命名规则 My_NAME->MyName --> <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop> </props> </property> </bean> <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <!--设置方言--> <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/> </bean> <!-- Spring Data Jpa配置 查找和装配DAO--> <jpa:repositories base-package="org.lanqiao.showcase.jpadao" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/> <!-- Jpa 事务配置 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <!-- 使用annotation定义事务 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:init-table-user.sql"/> </jdbc:embedded-database> <bean id="userService" class="org.lanqiao.showcase.service.UserService"/>
DAO的编写 org.lanqiao.showcase.jpadao.UserDao
import org.lanqiao.showcase.entity.User; import org.springframework.data.repository.CrudRepository; public interface UserDao extends CrudRepository<User,Integer> { }
单元测试 org.lanqiao.showcase.jpadao.UserDaoTest
package org.lanqiao.showcase.jpadao; import org.lanqiao.showcase.entity.User; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.web2017.test.data.RandomData; import static org.assertj.core.api.Assertions.assertThat; @ContextConfiguration("classpath:applicationContext-jpa.xml") public class UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests { @Autowired private UserDao userDao; @Test public void testFindAll() { save(); // 初始化4条,上面又增一条 assertThat(userDao.findAll().iterator()).hasSize(5); } private void save(){ final User entity = new User(); entity.setUsername(RandomData.randomName("username")); entity.setPassword(RandomData.randomName("password")); userDao.save(entity); } }
顺便贴下logback-test.xml(test/resources目录下)
用来查看sql打印:<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> </appender> <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>/tmp/logs/fullstack.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>/tmp/logs/fullstack.%d{yyyy-MM-dd}.log </fileNamePattern> </rollingPolicy> <encoder> <pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> </appender> <!-- project default level --> <logger name="org.lanqiao" level="debug" /> <logger name="org.web2017" level="debug" /> <logger name="org.hibernate.SQL" level="debug"/> <root level="warn"> <appender-ref ref="console"/> <!-- 生产环境取消下行注释 <appender-ref ref="rollingFile" level="error"/> --> </root> </configuration>
主要是这一行
<logger name="org.hibernate.SQL" level="debug"/>
小结
单元测试能跑通,说明持久层数据库的访问是没有问题的Spring Cache整合Redis完成服务层缓存
redis连接信息 cache/redis-config.properties
# Redis settings # server IP redis.host=your_redis_ip # server port redis.port=63799 # server pass redis.pass=your_redis_secret # use dbIndex redis.database=0 # 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例 redis.maxIdle=300 # 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间(毫秒),则直接抛出JedisConnectionException; redis.maxWait=3000 # 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的 redis.testOnBorrow=true
配置redisCacheManager并开启cache注解驱动:cache/applicationContext-redis.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:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:cache/redis-config.properties"/> <!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 --> <cache:annotation-driven cache-manager="redisCacheManager"/> <!-- redis 相关配置 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.maxIdle}"/> <property name="maxWaitMillis" value="${redis.maxWait}"/> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean> <bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="JedisConnectionFactory"/> <property name="keySerializer" > <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="valueSerializer" > <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> </bean> <!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value --> <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" c:redisOperations-ref="redisTemplate" p:usePrefix="true" > <property name="cacheNames"> <list> <value>userCache</value> <value>teamCache</value> </list> </property> <property name="cachePrefix"> <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix"/> </property> </bean> </beans>
这个网上都找得到,需要说明的是:
redisTemplate中为了便于到redis服务器去查验数据,最好将key的序列化设置为字符串序列化:
<property name="keySerializer" > <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property>
value序列化用JDK自带对象序列化,所以要求我们的实体类实现
Serializable接口
编写Service完成简单逻辑并使用Spring @Cache×××注解:
package org.lanqiao.showcase.service; import org.lanqiao.showcase.entity.User; import org.lanqiao.showcase.jpadao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; 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.Transactional; @Service @Transactional @CacheConfig(cacheNames = {/*"demoCache",*/"userCache"},cacheManager = "redisCacheManager") public class UserService { @Autowired private UserDao userDao; public Iterable<User> findAll(){ return userDao.findAll(); } @CacheEvict(key = "'User_'+#user.id") public void save(User user){ userDao.save(user); } // 默认以参数为key,查询缓存,命中则返回;未命中执行方法的逻辑,最终将参数和返回值作为k-v存储在缓存中 @Cacheable(key = "'User_'+#id") public User findById(Integer id){ return userDao.findOne(id); } @CacheEvict(key = "'User_'+#id") public void delete(Integer id){ userDao.delete(id); } }
注意,cacheName、cacheManager都要和你写的spring配置文件中的一致。
注解的含义请自行百度。
Service的单元测试
package org.lanqiao.showcase.service; import org.junit.Test; import org.lanqiao.showcase.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.web2017.test.data.RandomData; @ContextConfiguration({"classpath:applicationContext-jpa.xml","classpath:cache/applicationContext-redis.xml"}) public class UserServiceTest extends AbstractJUnit4SpringContextTests { @Autowired private UserService userService; @Test public void find1() throws Exception { //final User entity = new User(); //entity.setId(1); //entity.setUsername(RandomData.randomName("username")); //entity.setPassword(RandomData.randomName("password")); //entity.setTeamId(2); //userService.save(entity); User user1 = userService.findById(1); System.out.println(user1.getUsername()); System.out.println("======-====开启缓存后,下面将不执行sql============="); user1 = userService.findById(1); System.out.println(user1.getUsername()); } }
从打印日志看,第一次查询从数据库命中并存缓存,第二次就可以缓存命中。反复执行测试用例,第三次至第N次也不会去查数据库。
如果把存/改一个User实例的代码取消注释,效果是,先查询数据库,再修改数据库,令缓存失效,下面第一次查询执行sql,第二次查询将不执行sql而从缓存命中。
日志如下:
20:37:53.811 [main] DEBUG org.hibernate.SQL - select user0_.id as id1_1_0_, user0_.password as password2_1_0_, user0_.status as status3_1_0_, user0_.team_id as team_id4_1_0_, user0_.username as username5_1_0_ from t_user user0_ where user0_.id=? 20:37:53.919 [main] DEBUG org.hibernate.SQL - update t_user set password=?, status=?, team_id=?, username=? where id=? 20:37:53.928 [main] DEBUG org.hibernate.SQL - select user0_.id as id1_1_0_, user0_.password as password2_1_0_, user0_.status as status3_1_0_, user0_.team_id as team_id4_1_0_, user0_.username as username5_1_0_ from t_user user0_ where user0_.id=? username3952 ======-====开启缓存后,下面将不执行sql============= username3952
项目地址
web2017参考
http://blog.csdn.net/zhu_tianwei/article/details/49077835
http://blog.csdn.net/qq13398600329/article/details/52225737
http://blog.csdn.net/aqsunkai/article/details/51758900
同时web2017项目中大量采用了spring-side的工具类,一并鸣谢!
相关文章推荐
- 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合
- 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合
- 【Spring】17、spring cache 与redis缓存整合
- 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合
- 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合 PHP解决抢购、秒杀
- 简单示例:Spring4 整合 单个Redis服务
- 【Spring】17、spring cache 与redis缓存整合
- redis队列缓存+sql批量入库+php离线整合(高并发简单解决方案)
- 【高并发简单解决方案】redis队列缓存 + 批量入库 + php离线整合
- 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合
- 【高并发简单解决方案】redis队列缓存 + 批量入库 + php离线整合
- Redis缓存服务搭建及实现数据读写
- SpringBoot整合Spring-data-redis实现集中式缓存
- spring-boot整合redis作为缓存(2)——spring-boot的缓存
- SpringBoot整合redis哨兵主从服务
- redis缓存服务实践一(表的存储与查询)
- redis整合spring实现对数据的缓存
- spring整合redis简单demo
- java的redis整合包jar访问外网服务器上面的redis缓存问题
- redis(四)redis与Mybatis的无缝整合让MyBatis透明的管理缓存