SpringBoot整合Redis
Redis 远程连接配置
[root@localhost ~]# vim /etc/redis/6379.conf
bind 0.0.0.0
port 6379
daemonize yes
protected-mode no
requirepass 123
SpringBoot 整合 Redis
引入依赖
spring-boot-starter-data-redis 包含了 jedis,封装了一类 SpringBoot 与 Redis 的连接工具。
<dependency
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
编写 redisTemplate 的配置类,设置 redisConnectFactory
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
引入配置文件 application.properties
spring.redis.database=0
spring.redis.host=192.168.31.220
spring.redis.port=6379
spring.redis.password=
编写测试类
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class RedisController {
@Resource
private RedisTemplate redisTemplate;
@RequestMapping("/redis/setAndGet")
@ResponseBody
public String setAndGetValue(String name,String value){
//使用Redis设置键值对
redisTemplate.opsForValue().set(name,value);
//从Redis中获取指定的键值对
return (String) redisTemplate.opsForValue().get(name);
}
}
测试结果
http://localhost:8080/redis/setAndGet?name=soulboy&value=123
123
封装 redisTemplate API
- opsForValue 操作 String,Key,Value,包含过期 key,setBit 位操作等
- opsForSet 操作 set
- opsForHash 操作 hash
- opsForZset 操作 SortSet
- opsForList 操作 list 队列
封装 redisTemplate
package com.xdclass.mobile.xdclassmobileredis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Created by Administrator on 2018/10/6.
*/
@Service
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
private static double size = Math.pow(2, 32);
/**
* 写入缓存
*
* @param key
* @param offset 位 8Bit=1Byte
* @return
*/
public boolean setBit(String key, long offset, boolean isShow) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.setBit(key, offset, isShow);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存
*
* @param key
* @param offset
* @return
*/
public boolean getBit(String key, long offset) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.getBit(key, offset);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存设置时效时间
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 哈希 添加
*
* @param key
* @param hashKey
* @param value
*/
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希获取数据
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 列表添加
*
* @param k
* @param v
*/
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表获取
*
* @param k
* @param l
* @param l1
* @return
*/
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
}
/**
* 集合添加
*
* @param key
* @param value
*/
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合获取
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* 有序集合获取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
redisTemplate.opsForValue();
return zset.rangeByScore(key, scoure, scoure1);
}
//第一次加载的时候将数据加载到redis中
public void saveDataToRedis(String name) {
double index = Math.abs(name.hashCode() % size);
long indexLong = new Double(index).longValue();
boolean availableUsers = setBit("availableUsers", indexLong, true);
}
//第一次加载的时候将数据加载到redis中
public boolean getDataToRedis(String name) {
double index = Math.abs(name.hashCode() % size);
long indexLong = new Double(index).longValue();
return getBit("availableUsers", indexLong);
}
/**
* 有序集合获取排名
*
* @param key 集合名称
* @param value 值
*/
public Long zRank(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rank(key,value);
}
/**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start,long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key,start,end);
return ret;
}
/**
* 有序集合添加
*
* @param key
* @param value
*/
public Double zSetScore(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.score(key,value);
}
/**
* 有序集合添加分数
*
* @param key
* @param value
* @param scoure
*/
public void incrementScore(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.incrementScore(key, value, scoure);
}
/**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start,long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key,start,end);
return ret;
}
/**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end);
return ret;
}
}
测试 redisTemplate 工具类
@RestController
public class RedisController {
@Resource
private RedisService service;
@RequestMapping("/redis/setAndGet1")
@ResponseBody
public String setAndGetValueV2(String name,String value){
service.set(name,value);
return service.get(name).toString();
}
}
SpringBoot 整合 MyBatis
引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<scope>test</scope>
<version>1.3.2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
引入配置文件 application.yml
这里采用 Druid 数据库连接池。
spring:
datasource:
url: jdbc:mysql://192.168.31.220:33306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&connectTimeout=3000&socketTimeout=1000
username: root
password: 123
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
minIdle: 5
maxActive: 100
initialSize: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
removeAbandoned: true
filters: stat # ,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
druidLoginName: wjf # 登录druid的账号
druidPassword: wjf # 登录druid的密码
cachePrepStmts: true # 开启二级缓存
mybatis:
typeAliasesPackage: com.soulboy.mobile.soulboymobileredis.mapper # 需要学员自己修改填写
mapperLocations: classpath:/com/soulboy/mobile/soulboymobileredis/mapper/*.xml
mapperScanPackage: com.soulboy.mobile.soulboymobileredis.mapper
configLocation: classpath:/mybatis-config.xml
引入 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>
<settings>
<!-- 使全局的映射器启用或禁用缓存。 -->
<setting name="cacheEnabled" value="true" />
<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
<setting name="aggressiveLazyLoading" value="true"/>
<!-- 是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true -->
<setting name="multipleResultSetsEnabled" value="true" />
<!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
<setting name="useColumnLabel" value="true" />
<!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false -->
<setting name="useGeneratedKeys" value="false" />
<!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分 FULL:全部 -->
<setting name="autoMappingBehavior" value="PARTIAL" />
<!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新) -->
<setting name="defaultExecutorType" value="SIMPLE" />
<setting name="defaultStatementTimeout" value="25" />
<setting name="defaultFetchSize" value="100" />
<setting name="safeRowBoundsEnabled" value="false" />
<!-- 使用驼峰命名法转换字段。 -->
<setting name="mapUnderscoreToCamelCase" value="true" />
<!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
<setting name="localCacheScope" value="SESSION" />
<!-- 默认为OTHER,为了解决oracle插入null报错的问题要设置为NULL -->
<setting name="jdbcTypeForNull" value="NULL" />
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
</settings>
</configuration>
引入 DataSourceConfig 配置
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan("com.xdclass.mobile.xdclassmobileredis.mapper")
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 50)
public class DataSourceConfig {
private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.type}")
private String dbType;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Value("${spring.datasource.useGlobalDataSourceStat}")
private boolean useGlobalDataSourceStat;
@Value("${spring.datasource.druidLoginName}")
private String druidLoginName;
@Value("${spring.datasource.druidPassword}")
private String druidPassword;
@Bean(name="dataSource",destroyMethod = "close", initMethod="init")
@Primary //不要漏了这
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
try {
datasource.setUrl(this.dbUrl);
datasource.setDbType(dbType);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setFilters(filters);
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
return datasource;
}
///////// 下面是druid 监控访问的设置 /////////////////
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*"); //url 匹配
reg.addInitParameter("allow", "192.168.16.110,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
reg.addInitParameter("deny", "192.168.16.111"); //IP黑名单 (存在共同时,deny优先于allow)
reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
return reg;
}
@Bean(name="druidWebStatFilter")
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
return filterRegistrationBean;
}
}
编写 Mapper 类:注解方式
@Mapper
@Component
public interface UserMapper {
@Insert("insert sys_user(id,user_name) values(#{id},#{userName})")
void insert(User u);
@Update("update sys_user set user_name = #{userName} where id=#{id} ")
void update(User u);
@Delete("delete from sys_user where id=#{id} ")
void delete(@Param("id") String id);
@Select("select id,user_name from sys_user where id=#{id} ")
User find(@Param("id") String id);
//注:方法名和要UserMapper.xml中的id一致
List<User> query(@Param("userName") String userName);
@Delete("delete from sys_user")
void deleteAll();
}
编写 Mapper 类:XML 配置方式
测试类
@RestController
public class UserController {
@Resource
private UserMapper userMapper;
@RequestMapping("/getUser")
@ResponseBody
public User getUser(String id) {
User user = userMapper.find(id);
return user;
}
}
测试
// http://localhost:8080/getUser?id=1
{
"id": "1",
"userName": "soulboy"
}
MyBatis 整合 Redis(缓存)
请求第一次访问从 TRDB 中获取数据,并更新至 Redis,第二次访问时直接从 Redis 中获取数据。
@RestController
public class UserController {
private static final String key = "userCache_";
@Resource
private UserMapper userMapper;
@Resource
private RedisService redisService;
/**
* set值和get值的时候序列化方式必须保持一致
* @param id
* @return
*/
@RequestMapping("/getUserCache")
@ResponseBody
public User getUseCache(String id) {
//step1 先从redis里面取值
User user = (User)redisService.get(key + id);
//默认使用JDK序列化方式
//step2 如果拿不到则从DB取值
if (user == null) {
User userDB = userMapper.find(id);
System.out.println("fresh value from DB id:" + id);
//step3 DB非空情况刷新redis值
if (userDB != null) {
redisService.set(key + id, userDB);
//默认使用JDK序列化方式
return userDB;
}
}
return user;
}
}
测试
// http://localhost:8080/getUserCache?id=1
{
"id": "1",
"userName": "soulboy"
}
Redis Desktop Manager 查看数据

SpringBoot cache
上述示例中的 step1、step2、step3 包含大量的重复逻辑,为了消除代码的容易,专业于业务代码,可以使用 SpringBoot cache 注解。SpringBoot cache 可以结合 Redis、ehcache 等缓存。一级缓存是:sqlSession,SQL 建立连接到关闭连接的数据缓存; 二级缓存是:全局。
- @CacheConfig(cacheNames="userInfoCache") 在同个 Redis 里面必须唯一
- @Cacheable(查) : 来划分可缓存的方法 - 即,结果存储在缓存中的方法,以便在后续调用(具有相同的参数)时,返回缓存中的值而不必实际执行该方法
- @CachePut(修改、增加) : 当需要更新缓存而不干扰方法执行时,可以使用@CachePut 注释。也就是说,始终执行该方法并将其结果放入缓存中(根据@CachePut 选项)
- @CacheEvict(删除) : 对于从缓存中删除陈旧或未使用的数据非常有用,指示缓存范围内的驱逐是否需要执行而不仅仅是一个条目驱逐
引入 Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
开启缓存注解: @EnableCaching
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
在 Service 层的方法上面加入 SpEL
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.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@Service
@CacheConfig(cacheNames="userInfoCache") // 本类内方法指定使用缓存时,默认的名称就是userInfoCache
@Transactional(propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class)
public class UserService {
@Autowired
private UserMapper userMapper;
// 因为必须要有返回值,才能保存到数据库中,如果保存的对象的某些字段是需要数据库生成的,
//那保存对象进数据库的时候,就没必要放到缓存了
@CachePut(key="#p0.id") //#p0表示第一个参数
//必须要有返回值,否则没数据放到缓存中
public User insertUser(User u){
this.userMapper.insert(u);
//u对象中可能只有只几个有效字段,其他字段值靠数据库生成,比如id
return this.userMapper.find(u.getId());
}
@CachePut(key="#p0.id")
public User updateUser(User u){
this.userMapper.update(u);
//可能只是更新某几个字段而已,所以查次数据库把数据全部拿出来全部
return this.userMapper.find(u.getId());
}
@Nullable
@Cacheable(key="#p0") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
;#p0代表第一个参数
public User findById(String id){
System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
Assert.notNull(id,"id不用为空");
return this.userMapper.find(id);
}
@CacheEvict(key="#p0") //删除缓存名称为userInfoCache,key等于指定的id对应的缓存
public void deleteById(String id){
this.userMapper.delete(id);
}
//清空缓存名称为userInfoCache(看类名上的注解)下的所有缓存
//如果数据失败了,缓存时不会清除的
@CacheEvict(allEntries = true)
public void deleteAll(){
this.userMapper.deleteAll();
}
@Nullable
@Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
public User findByIdTtl(String id){
System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
Assert.notNull(id,"id不用为空");
return this.userMapper.find(id);
}
}
编写 Controller 层测试代码
@RestController
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/getByCache")
@ResponseBody
public User getByCache(String id) {
User user = userService.findById(id);
return user;
}
@ResponseBody
@RequestMapping(value = "/getexpire", method = RequestMethod.GET)
public User findByIdTtl(String id) {
User u = new User();
try{
u = userService.findByIdTtl(id);
}catch (Exception e){
System.err.println(e.getMessage());
}
return u;
}
}
测试结果
// http://localhost:8080/getByCache?id=1
{
"id": "1",
"userName": "soulboy"
}
根据id=1获取用户对象,从数据库中获取
再次访问// http://localhost:8080/getByCache?id=1
发现控制台没有打印,此时证明数据是从redis缓存中获取。
SpringBoot cache 存在的问题
生成 key 过于简单
生成 key 过于简单,userCache::1 容易冲突。
无法设置过期时间,默认过期时间为永久不过期
默认过期时间是 -1 ,代表永不过期。
配置序列化方式,默认的是序列化 JDKSerialazable
JDKSerialazable 序列化方式对空值、日期的支持不好。
修改 RedisConfig 文件
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
/**
* 生成key过于简单,userCache::1容易冲突。
* @return
*/
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
/**
* 解决:默认过期时间为永久不过期的问题
* @param redisConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个。
this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
//针对指定的key设置缓存时间,其他未配置的key则使用默认策略。
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(100));
redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000));
return redisCacheConfigurationMap;
}
/**
* 修改默认序列化方式
* @param seconds
* @return
*/
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)//添加jackson的序列化方式
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
Service 层代码
import com.xdclass.mobile.xdclassmobileredis.domain.User;
import com.xdclass.mobile.xdclassmobileredis.mapper.UserMapper;
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.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@Service
@CacheConfig(cacheNames="userInfoCache") // 本类内方法指定使用缓存时,默认的名称就是userInfoCache
@Transactional(propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class)
public class UserService {
@Autowired
private UserMapper userMapper;
// 因为必须要有返回值,才能保存到数据库中,如果保存的对象的某些字段是需要数据库生成的,
//那保存对象进数据库的时候,就没必要放到缓存了
@CachePut(key="#p0.id") //#p0表示第一个参数
//必须要有返回值,否则没数据放到缓存中
public User insertUser(User u){
this.userMapper.insert(u);
//u对象中可能只有只几个有效字段,其他字段值靠数据库生成,比如id
return this.userMapper.find(u.getId());
}
@CachePut(key="#p0.id")
public User updateUser(User u){
this.userMapper.update(u);
//可能只是更新某几个字段而已,所以查次数据库把数据全部拿出来全部
return this.userMapper.find(u.getId());
}
@Nullable
//@Cacheable(key="#p0") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法 #p0代表第一个参数
@Cacheable(keyGenerator = "simpleKeyGenerator")//使用自定义key生成策略,防止key的冲突
//key的格式:userInfoCache::UserService.findById[1]
public User findById(String id){
System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
Assert.notNull(id,"id不用为空");
return this.userMapper.find(id);
}
@CacheEvict(key="#p0") //删除缓存名称为userInfoCache,key等于指定的id对应的缓存
public void deleteById(String id){
this.userMapper.delete(id);
}
//清空缓存名称为userInfoCache(看类名上的注解)下的所有缓存
//如果数据失败了,缓存时不会清除的
@CacheEvict(allEntries = true)
public void deleteAll(){
this.userMapper.deleteAll();
}
@Nullable
// @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
//使用自定义key生成策略,防止key的冲突
@Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator")
//key的格式:UserInfoList::UserService.findByIdTtl[1]
public User findByIdTtl(String id){
System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
Assert.notNull(id,"id不用为空");
return this.userMapper.find(id);
}
}
Controller 层代码
@RestController
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/getByCache")
@ResponseBody
public User getByCache(String id) {
User user = userService.findById(id);
return user;
}
@ResponseBody
@RequestMapping(value = "/getexpire", method = RequestMethod.GET)
public User findByIdTtl(String id) {
User u = new User();
try{
u = userService.findByIdTtl(id);
}catch (Exception e){
System.err.println(e.getMessage());
}
return u;
}
}
测试
// http://localhost:8080/getexpire?id=1
{
"id": "1",
"userName": "soulboy"
}

压测 Redis cache VS TRDB
ab 是 Apache HTTP server benchmarking tool,可以用以测试 HTTP 请求的服务器性能
# 安装ab测试工具
[root@localhost ~]# yum install -y httpd-tools
[root@localhost ~]# ab -V
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
# ab测试工具命令参数含义
-n #进行http请求的总个数
-c #同时并发请求的Client数量,即请求的并发数
qps #qps即每秒并发数,request per second,通过统计qps来测试性能。
# 进行测试
# Redis cache
ab -n1000 -c10 http://192.168.31.230:8080/getByCache?id=1
# TRDB
ab -n1000 -c10 http://192.168.31.230:8080/getUser?id=1
# 1000请求数 10并发
Mysql: 1347.98 [#/sec] (mean)
Redis cache: 1713.87 [#/sec] (mean)
# 1000请求数 100并发
Mysql: 2510.13 [#/sec] (mean)
Redis cache: 4440.42 [#/sec] (mean)
# 10000请求数 100并发
Mysql: 2953.38 [#/sec] (mean)
Redis cache: 5752.92 [#/sec] (mean)
# 10000请求数 300并发
Mysql: apr_socket_recv: Connection refused(服务过载,拒绝连接)
Redis cache: 6241.05 [#/sec] (mean)
# 10000请求数 800并发
Mysql: apr_socket_recv: Connection refused(服务过载,拒绝连接)
Redis cache: 4831.11 [#/sec] (mean)
Redis 实现分布式集群环境下的 session 共享
机器部署同一套服务(代码),性能更好,更承受更高的用户并发,集群之间可以通过 Redis 来共享 session。
- Cookie: Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息,我们可以看到在服务器写的 cookie,会通过响应头 Set-Cookie 的方式写入到浏览器。
- Session:HTTP 协议是无状态的,并非 TCP 一样进行三次握手,对于一个浏览器发出的多次请求,Web 服务器无法区分是不是来源于同一个浏览器。所以服务器为了区分这个过程会通过一个 sessionid 来区分请求,而 sessionid 保存在 cookie 中。引入 Maven 依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
开启 Redis session 缓存:@EnableRedisHttpSession
DataSourceConfig
- maxInactiveIntervalInSeconds 指定缓存的时间
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan("com.xdclass.mobile.xdclassmobileredis.mapper")
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 50)
public class DataSourceConfig {
private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.type}")
private String dbType;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Value("${spring.datasource.useGlobalDataSourceStat}")
private boolean useGlobalDataSourceStat;
@Value("${spring.datasource.druidLoginName}")
private String druidLoginName;
@Value("${spring.datasource.druidPassword}")
private String druidPassword;
@Bean(name="dataSource",destroyMethod = "close", initMethod="init")
@Primary //不要漏了这
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
try {
datasource.setUrl(this.dbUrl);
datasource.setDbType(dbType);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setFilters(filters);
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
return datasource;
}
///////// 下面是druid 监控访问的设置 /////////////////
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*"); //url 匹配
reg.addInitParameter("allow", "192.168.16.110,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
reg.addInitParameter("deny", "192.168.16.111"); //IP黑名单 (存在共同时,deny优先于allow)
reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
return reg;
}
@Bean(name="druidWebStatFilter")
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
return filterRegistrationBean;
}
}
Controller 层代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
public class SessionController {
@RequestMapping(value = "/setSession", method = RequestMethod.GET)
public Map<String, Object> setSession (HttpServletRequest request){
Map<String, Object> map = new HashMap<>();
request.getSession().setAttribute("request Url", request.getRequestURL());
map.put("request Url", request.getRequestURL());
return map;
}
@RequestMapping(value = "/getSession", method = RequestMethod.GET)
public Object getSession (HttpServletRequest request){
Map<String, Object> map = new HashMap<>();
map.put("sessionIdUrl",request.getSession().getAttribute("request Url"));
//设置sessionIdUrl
map.put("sessionId", request.getSession().getId());
//设置sessionId
return map;
}
}
测试
// http://192.168.31.230:8080/setSession
{
"request Url": "http://192.168.31.230:8080/setSession"
}
// http://192.168.31.230:8080/getSession
{
"sessionIdUrl": "http://192.168.31.230:8080/setSession",
"sessionId": "2c950216-dbd1-4b3b-a748-67e71611a084"
}
查询 Redis
spring:session:sessions:expires:81d06c82-5312-41d7-8afe-4d08b77352d6
