您的位置:首页 > 数据库 > Redis

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的工具类,一并鸣谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息