SpringBoot整合Redis
Redis 远程连接配置
1[root@localhost ~]# vim /etc/redis/6379.conf
2bind 0.0.0.0
3port 6379
4daemonize yes
5protected-mode no
6requirepass 123
SpringBoot 整合 Redis
引入依赖
spring-boot-starter-data-redis 包含了 jedis,封装了一类 SpringBoot 与 Redis 的连接工具。
1<dependency
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-data-redis</artifactId>
4 </dependency>
编写 redisTemplate 的配置类,设置 redisConnectFactory
1import org.springframework.data.redis.connection.RedisConnectionFactory;
2import org.springframework.data.redis.core.RedisTemplate;
3
4@Configuration
5public class RedisConfig {
6 @Bean
7 public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
8 RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
9 redisTemplate.setConnectionFactory(factory);
10 return redisTemplate;
11 }
12}
引入配置文件 application.properties
1spring.redis.database=0
2spring.redis.host=192.168.31.220
3spring.redis.port=6379
4spring.redis.password=
编写测试类
1import org.springframework.data.redis.core.RedisTemplate;
2import org.springframework.web.bind.annotation.RequestMapping;
3import org.springframework.web.bind.annotation.ResponseBody;
4import org.springframework.web.bind.annotation.RestController;
5import javax.annotation.Resource;
6
7@RestController
8public class RedisController {
9 @Resource
10 private RedisTemplate redisTemplate;
11
12 @RequestMapping("/redis/setAndGet")
13 @ResponseBody
14 public String setAndGetValue(String name,String value){
15 //使用Redis设置键值对
16 redisTemplate.opsForValue().set(name,value);
17 //从Redis中获取指定的键值对
18 return (String) redisTemplate.opsForValue().get(name);
19 }
20}
测试结果
1http://localhost:8080/redis/setAndGet?name=soulboy&value=123
2123
封装 redisTemplate API
- opsForValue 操作 String,Key,Value,包含过期 key,setBit 位操作等
- opsForSet 操作 set
- opsForHash 操作 hash
- opsForZset 操作 SortSet
- opsForList 操作 list 队列
封装 redisTemplate
1package com.xdclass.mobile.xdclassmobileredis;
2
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.data.redis.core.*;
5import org.springframework.stereotype.Service;
6
7import java.io.Serializable;
8import java.util.List;
9import java.util.Set;
10import java.util.concurrent.TimeUnit;
11
12/**
13 * Created by Administrator on 2018/10/6.
14 */
15@Service
16public class RedisService {
17
18 @Autowired
19 private RedisTemplate redisTemplate;
20
21 private static double size = Math.pow(2, 32);
22
23
24 /**
25 * 写入缓存
26 *
27 * @param key
28 * @param offset 位 8Bit=1Byte
29 * @return
30 */
31 public boolean setBit(String key, long offset, boolean isShow) {
32 boolean result = false;
33 try {
34 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
35 operations.setBit(key, offset, isShow);
36 result = true;
37 } catch (Exception e) {
38 e.printStackTrace();
39 }
40 return result;
41 }
42
43 /**
44 * 写入缓存
45 *
46 * @param key
47 * @param offset
48 * @return
49 */
50 public boolean getBit(String key, long offset) {
51 boolean result = false;
52 try {
53 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
54 result = operations.getBit(key, offset);
55 } catch (Exception e) {
56 e.printStackTrace();
57 }
58 return result;
59 }
60
61
62 /**
63 * 写入缓存
64 *
65 * @param key
66 * @param value
67 * @return
68 */
69 public boolean set(final String key, Object value) {
70 boolean result = false;
71 try {
72 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
73 operations.set(key, value);
74 result = true;
75 } catch (Exception e) {
76 e.printStackTrace();
77 }
78 return result;
79 }
80
81 /**
82 * 写入缓存设置时效时间
83 *
84 * @param key
85 * @param value
86 * @return
87 */
88 public boolean set(final String key, Object value, Long expireTime) {
89 boolean result = false;
90 try {
91 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
92 operations.set(key, value);
93 redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
94 result = true;
95 } catch (Exception e) {
96 e.printStackTrace();
97 }
98 return result;
99 }
100
101 /**
102 * 批量删除对应的value
103 *
104 * @param keys
105 */
106 public void remove(final String... keys) {
107 for (String key : keys) {
108 remove(key);
109 }
110 }
111
112
113 /**
114 * 删除对应的value
115 *
116 * @param key
117 */
118 public void remove(final String key) {
119 if (exists(key)) {
120 redisTemplate.delete(key);
121 }
122 }
123
124 /**
125 * 判断缓存中是否有对应的value
126 *
127 * @param key
128 * @return
129 */
130 public boolean exists(final String key) {
131 return redisTemplate.hasKey(key);
132 }
133
134 /**
135 * 读取缓存
136 *
137 * @param key
138 * @return
139 */
140 public Object get(final String key) {
141 Object result = null;
142 ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
143 result = operations.get(key);
144 return result;
145 }
146
147 /**
148 * 哈希 添加
149 *
150 * @param key
151 * @param hashKey
152 * @param value
153 */
154 public void hmSet(String key, Object hashKey, Object value) {
155 HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
156 hash.put(key, hashKey, value);
157 }
158
159 /**
160 * 哈希获取数据
161 *
162 * @param key
163 * @param hashKey
164 * @return
165 */
166 public Object hmGet(String key, Object hashKey) {
167 HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
168 return hash.get(key, hashKey);
169 }
170
171 /**
172 * 列表添加
173 *
174 * @param k
175 * @param v
176 */
177 public void lPush(String k, Object v) {
178 ListOperations<String, Object> list = redisTemplate.opsForList();
179 list.rightPush(k, v);
180 }
181
182 /**
183 * 列表获取
184 *
185 * @param k
186 * @param l
187 * @param l1
188 * @return
189 */
190 public List<Object> lRange(String k, long l, long l1) {
191 ListOperations<String, Object> list = redisTemplate.opsForList();
192 return list.range(k, l, l1);
193 }
194
195 /**
196 * 集合添加
197 *
198 * @param key
199 * @param value
200 */
201 public void add(String key, Object value) {
202 SetOperations<String, Object> set = redisTemplate.opsForSet();
203 set.add(key, value);
204 }
205
206 /**
207 * 集合获取
208 *
209 * @param key
210 * @return
211 */
212 public Set<Object> setMembers(String key) {
213 SetOperations<String, Object> set = redisTemplate.opsForSet();
214 return set.members(key);
215 }
216
217 /**
218 * 有序集合添加
219 *
220 * @param key
221 * @param value
222 * @param scoure
223 */
224 public void zAdd(String key, Object value, double scoure) {
225 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
226 zset.add(key, value, scoure);
227 }
228
229 /**
230 * 有序集合获取
231 *
232 * @param key
233 * @param scoure
234 * @param scoure1
235 * @return
236 */
237 public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
238 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
239 redisTemplate.opsForValue();
240 return zset.rangeByScore(key, scoure, scoure1);
241 }
242
243
244 //第一次加载的时候将数据加载到redis中
245 public void saveDataToRedis(String name) {
246 double index = Math.abs(name.hashCode() % size);
247 long indexLong = new Double(index).longValue();
248 boolean availableUsers = setBit("availableUsers", indexLong, true);
249 }
250
251 //第一次加载的时候将数据加载到redis中
252 public boolean getDataToRedis(String name) {
253
254 double index = Math.abs(name.hashCode() % size);
255 long indexLong = new Double(index).longValue();
256 return getBit("availableUsers", indexLong);
257 }
258
259 /**
260 * 有序集合获取排名
261 *
262 * @param key 集合名称
263 * @param value 值
264 */
265 public Long zRank(String key, Object value) {
266 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
267 return zset.rank(key,value);
268 }
269
270
271 /**
272 * 有序集合获取排名
273 *
274 * @param key
275 */
276 public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start,long end) {
277 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
278 Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key,start,end);
279 return ret;
280 }
281
282 /**
283 * 有序集合添加
284 *
285 * @param key
286 * @param value
287 */
288 public Double zSetScore(String key, Object value) {
289 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
290 return zset.score(key,value);
291 }
292
293
294 /**
295 * 有序集合添加分数
296 *
297 * @param key
298 * @param value
299 * @param scoure
300 */
301 public void incrementScore(String key, Object value, double scoure) {
302 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
303 zset.incrementScore(key, value, scoure);
304 }
305
306
307 /**
308 * 有序集合获取排名
309 *
310 * @param key
311 */
312 public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start,long end) {
313 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
314 Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key,start,end);
315 return ret;
316 }
317
318 /**
319 * 有序集合获取排名
320 *
321 * @param key
322 */
323 public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {
324 ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
325 Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end);
326 return ret;
327 }
328
329}
测试 redisTemplate 工具类
1@RestController
2public class RedisController {
3 @Resource
4 private RedisService service;
5
6 @RequestMapping("/redis/setAndGet1")
7 @ResponseBody
8 public String setAndGetValueV2(String name,String value){
9 service.set(name,value);
10 return service.get(name).toString();
11 }
12}
SpringBoot 整合 MyBatis
引入依赖
1<dependency>
2 <groupId>mysql</groupId>
3 <artifactId>mysql-connector-java</artifactId>
4 </dependency>
5 <dependency>
6 <groupId>org.mybatis.spring.boot</groupId>
7 <artifactId>mybatis-spring-boot-starter</artifactId>
8 <version>1.3.0</version>
9 </dependency>
10 <dependency>
11 <groupId>org.mybatis.generator</groupId>
12 <artifactId>mybatis-generator-core</artifactId>
13 <scope>test</scope>
14 <version>1.3.2</version>
15 <optional>true</optional>
16 </dependency>
17 <dependency>
18 <groupId>org.springframework.boot</groupId>
19 <artifactId>spring-boot-starter-jdbc</artifactId>
20 </dependency>
21 <dependency>
22 <groupId>com.alibaba</groupId>
23 <artifactId>druid</artifactId>
24 <version>1.1.10</version>
25 </dependency>
26 <dependency>
27 <groupId>com.alibaba</groupId>
28 <artifactId>fastjson</artifactId>
29 <version>1.2.7</version>
30 </dependency>
引入配置文件 application.yml
这里采用 Druid 数据库连接池。
1spring:
2 datasource:
3 url: jdbc:mysql://192.168.31.220:33306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&connectTimeout=3000&socketTimeout=1000
4 username: root
5 password: 123
6 type: com.alibaba.druid.pool.DruidDataSource
7 driver-class-name: com.mysql.jdbc.Driver
8 minIdle: 5
9 maxActive: 100
10 initialSize: 10
11 maxWait: 60000
12 timeBetweenEvictionRunsMillis: 60000
13 minEvictableIdleTimeMillis: 300000
14 validationQuery: select 'x'
15 testWhileIdle: true
16 testOnBorrow: false
17 testOnReturn: false
18 poolPreparedStatements: true
19 maxPoolPreparedStatementPerConnectionSize: 50
20 removeAbandoned: true
21 filters: stat # ,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
22 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
23 useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
24 druidLoginName: wjf # 登录druid的账号
25 druidPassword: wjf # 登录druid的密码
26 cachePrepStmts: true # 开启二级缓存
27mybatis:
28 typeAliasesPackage: com.soulboy.mobile.soulboymobileredis.mapper # 需要学员自己修改填写
29 mapperLocations: classpath:/com/soulboy/mobile/soulboymobileredis/mapper/*.xml
30 mapperScanPackage: com.soulboy.mobile.soulboymobileredis.mapper
31 configLocation: classpath:/mybatis-config.xml
引入 mybatis-config.xml
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE configuration
3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-config.dtd">
5<configuration>
6 <settings>
7 <!-- 使全局的映射器启用或禁用缓存。 -->
8 <setting name="cacheEnabled" value="true" />
9 <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
10 <setting name="lazyLoadingEnabled" value="true" />
11 <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
12 <setting name="aggressiveLazyLoading" value="true"/>
13 <!-- 是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true -->
14 <setting name="multipleResultSetsEnabled" value="true" />
15 <!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
16 <setting name="useColumnLabel" value="true" />
17 <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false -->
18 <setting name="useGeneratedKeys" value="false" />
19 <!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分 FULL:全部 -->
20 <setting name="autoMappingBehavior" value="PARTIAL" />
21 <!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新) -->
22 <setting name="defaultExecutorType" value="SIMPLE" />
23
24 <setting name="defaultStatementTimeout" value="25" />
25
26 <setting name="defaultFetchSize" value="100" />
27
28 <setting name="safeRowBoundsEnabled" value="false" />
29 <!-- 使用驼峰命名法转换字段。 -->
30 <setting name="mapUnderscoreToCamelCase" value="true" />
31 <!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
32 <setting name="localCacheScope" value="SESSION" />
33 <!-- 默认为OTHER,为了解决oracle插入null报错的问题要设置为NULL -->
34 <setting name="jdbcTypeForNull" value="NULL" />
35 <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
36 </settings>
37
38</configuration>
引入 DataSourceConfig 配置
1import com.alibaba.druid.pool.DruidDataSource;
2import com.alibaba.druid.support.http.StatViewServlet;
3import com.alibaba.druid.support.http.WebStatFilter;
4import org.mybatis.spring.annotation.MapperScan;
5import org.slf4j.Logger;
6import org.slf4j.LoggerFactory;
7import org.springframework.beans.factory.annotation.Value;
8import org.springframework.boot.web.servlet.FilterRegistrationBean;
9import org.springframework.boot.web.servlet.ServletRegistrationBean;
10import org.springframework.context.annotation.Bean;
11import org.springframework.context.annotation.Configuration;
12import org.springframework.context.annotation.Primary;
13import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
14import javax.sql.DataSource;
15import java.sql.SQLException;
16
17@Configuration
18@MapperScan("com.xdclass.mobile.xdclassmobileredis.mapper")
19@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 50)
20public class DataSourceConfig {
21 private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
22
23 @Value("${spring.datasource.url}")
24 private String dbUrl;
25
26 @Value("${spring.datasource.type}")
27 private String dbType;
28
29 @Value("${spring.datasource.username}")
30 private String username;
31
32 @Value("${spring.datasource.password}")
33 private String password;
34
35 @Value("${spring.datasource.driver-class-name}")
36 private String driverClassName;
37
38 @Value("${spring.datasource.initialSize}")
39 private int initialSize;
40
41 @Value("${spring.datasource.minIdle}")
42 private int minIdle;
43
44 @Value("${spring.datasource.maxActive}")
45 private int maxActive;
46
47 @Value("${spring.datasource.maxWait}")
48 private int maxWait;
49
50 @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
51 private int timeBetweenEvictionRunsMillis;
52
53 @Value("${spring.datasource.minEvictableIdleTimeMillis}")
54 private int minEvictableIdleTimeMillis;
55
56 @Value("${spring.datasource.validationQuery}")
57 private String validationQuery;
58
59 @Value("${spring.datasource.testWhileIdle}")
60 private boolean testWhileIdle;
61
62 @Value("${spring.datasource.testOnBorrow}")
63 private boolean testOnBorrow;
64
65 @Value("${spring.datasource.testOnReturn}")
66 private boolean testOnReturn;
67
68 @Value("${spring.datasource.poolPreparedStatements}")
69 private boolean poolPreparedStatements;
70
71 @Value("${spring.datasource.filters}")
72 private String filters;
73
74 @Value("${spring.datasource.connectionProperties}")
75 private String connectionProperties;
76
77 @Value("${spring.datasource.useGlobalDataSourceStat}")
78 private boolean useGlobalDataSourceStat;
79
80 @Value("${spring.datasource.druidLoginName}")
81 private String druidLoginName;
82
83 @Value("${spring.datasource.druidPassword}")
84 private String druidPassword;
85
86 @Bean(name="dataSource",destroyMethod = "close", initMethod="init")
87 @Primary //不要漏了这
88 public DataSource dataSource(){
89 DruidDataSource datasource = new DruidDataSource();
90 try {
91 datasource.setUrl(this.dbUrl);
92 datasource.setDbType(dbType);
93 datasource.setUsername(username);
94 datasource.setPassword(password);
95 datasource.setDriverClassName(driverClassName);
96 datasource.setInitialSize(initialSize);
97 datasource.setMinIdle(minIdle);
98 datasource.setMaxActive(maxActive);
99 datasource.setMaxWait(maxWait);
100 datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
101 datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
102 datasource.setValidationQuery(validationQuery);
103 datasource.setTestWhileIdle(testWhileIdle);
104 datasource.setTestOnBorrow(testOnBorrow);
105 datasource.setTestOnReturn(testOnReturn);
106 datasource.setPoolPreparedStatements(poolPreparedStatements);
107 datasource.setFilters(filters);
108 } catch (SQLException e) {
109 logger.error("druid configuration initialization filter", e);
110 }
111 return datasource;
112 }
113
114 ///////// 下面是druid 监控访问的设置 /////////////////
115 @Bean
116 public ServletRegistrationBean druidServlet() {
117 ServletRegistrationBean reg = new ServletRegistrationBean();
118 reg.setServlet(new StatViewServlet());
119 reg.addUrlMappings("/druid/*"); //url 匹配
120 reg.addInitParameter("allow", "192.168.16.110,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
121 reg.addInitParameter("deny", "192.168.16.111"); //IP黑名单 (存在共同时,deny优先于allow)
122 reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
123 reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
124 reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
125 return reg;
126 }
127
128 @Bean(name="druidWebStatFilter")
129 public FilterRegistrationBean filterRegistrationBean() {
130 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
131 filterRegistrationBean.setFilter(new WebStatFilter());
132 filterRegistrationBean.addUrlPatterns("/*");
133 filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
134 filterRegistrationBean.addInitParameter("profileEnable", "true");
135 filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
136 filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
137 return filterRegistrationBean;
138 }
139}
编写 Mapper 类:注解方式
1@Mapper
2@Component
3
4public interface UserMapper {
5
6 @Insert("insert sys_user(id,user_name) values(#{id},#{userName})")
7 void insert(User u);
8
9 @Update("update sys_user set user_name = #{userName} where id=#{id} ")
10 void update(User u);
11
12 @Delete("delete from sys_user where id=#{id} ")
13 void delete(@Param("id") String id);
14
15 @Select("select id,user_name from sys_user where id=#{id} ")
16 User find(@Param("id") String id);
17
18 //注:方法名和要UserMapper.xml中的id一致
19 List<User> query(@Param("userName") String userName);
20
21 @Delete("delete from sys_user")
22 void deleteAll();
23}
编写 Mapper 类:XML 配置方式
1
测试类
1@RestController
2public class UserController {
3 @Resource
4 private UserMapper userMapper;
5
6 @RequestMapping("/getUser")
7 @ResponseBody
8 public User getUser(String id) {
9 User user = userMapper.find(id);
10 return user;
11 }
12}
测试
1// http://localhost:8080/getUser?id=1
2{
3 "id": "1",
4 "userName": "soulboy"
5}
MyBatis 整合 Redis(缓存)
请求第一次访问从 TRDB 中获取数据,并更新至 Redis,第二次访问时直接从 Redis 中获取数据。
1@RestController
2public class UserController {
3 private static final String key = "userCache_";
4
5 @Resource
6 private UserMapper userMapper;
7
8 @Resource
9 private RedisService redisService;
10
11 /**
12 * set值和get值的时候序列化方式必须保持一致
13 * @param id
14 * @return
15 */
16 @RequestMapping("/getUserCache")
17 @ResponseBody
18 public User getUseCache(String id) {
19
20 //step1 先从redis里面取值
21 User user = (User)redisService.get(key + id);
22//默认使用JDK序列化方式
23
24 //step2 如果拿不到则从DB取值
25 if (user == null) {
26 User userDB = userMapper.find(id);
27 System.out.println("fresh value from DB id:" + id);
28
29 //step3 DB非空情况刷新redis值
30 if (userDB != null) {
31 redisService.set(key + id, userDB);
32//默认使用JDK序列化方式
33 return userDB;
34 }
35 }
36 return user;
37 }
38
39}
测试
1// http://localhost:8080/getUserCache?id=1
2{
3 "id": "1",
4 "userName": "soulboy"
5}
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 依赖
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-cache</artifactId>
4 </dependency>
开启缓存注解: @EnableCaching
1@Configuration
2@EnableCaching
3public class RedisConfig {
4 @Bean
5 public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
6 RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
7 redisTemplate.setConnectionFactory(factory);
8 return redisTemplate;
9 }
10}
在 Service 层的方法上面加入 SpEL
1import org.springframework.beans.factory.annotation.Autowired;
2import org.springframework.cache.annotation.CacheConfig;
3import org.springframework.cache.annotation.CacheEvict;
4import org.springframework.cache.annotation.CachePut;
5import org.springframework.cache.annotation.Cacheable;
6import org.springframework.lang.Nullable;
7import org.springframework.stereotype.Service;
8import org.springframework.transaction.annotation.Propagation;
9import org.springframework.transaction.annotation.Transactional;
10import org.springframework.util.Assert;
11
12@Service
13@CacheConfig(cacheNames="userInfoCache") // 本类内方法指定使用缓存时,默认的名称就是userInfoCache
14@Transactional(propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class)
15public class UserService {
16
17 @Autowired
18 private UserMapper userMapper;
19
20 // 因为必须要有返回值,才能保存到数据库中,如果保存的对象的某些字段是需要数据库生成的,
21 //那保存对象进数据库的时候,就没必要放到缓存了
22 @CachePut(key="#p0.id") //#p0表示第一个参数
23 //必须要有返回值,否则没数据放到缓存中
24 public User insertUser(User u){
25 this.userMapper.insert(u);
26 //u对象中可能只有只几个有效字段,其他字段值靠数据库生成,比如id
27 return this.userMapper.find(u.getId());
28 }
29
30 @CachePut(key="#p0.id")
31 public User updateUser(User u){
32 this.userMapper.update(u);
33 //可能只是更新某几个字段而已,所以查次数据库把数据全部拿出来全部
34 return this.userMapper.find(u.getId());
35 }
36
37 @Nullable
38 @Cacheable(key="#p0") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
39;#p0代表第一个参数
40 public User findById(String id){
41 System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
42 Assert.notNull(id,"id不用为空");
43 return this.userMapper.find(id);
44 }
45
46 @CacheEvict(key="#p0") //删除缓存名称为userInfoCache,key等于指定的id对应的缓存
47 public void deleteById(String id){
48 this.userMapper.delete(id);
49 }
50
51 //清空缓存名称为userInfoCache(看类名上的注解)下的所有缓存
52 //如果数据失败了,缓存时不会清除的
53 @CacheEvict(allEntries = true)
54 public void deleteAll(){
55 this.userMapper.deleteAll();
56 }
57
58 @Nullable
59 @Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
60 public User findByIdTtl(String id){
61 System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
62 Assert.notNull(id,"id不用为空");
63 return this.userMapper.find(id);
64 }
65}
编写 Controller 层测试代码
1@RestController
2public class UserController {
3
4 @Resource
5 private UserService userService;
6
7 @RequestMapping("/getByCache")
8 @ResponseBody
9 public User getByCache(String id) {
10 User user = userService.findById(id);
11 return user;
12 }
13
14 @ResponseBody
15 @RequestMapping(value = "/getexpire", method = RequestMethod.GET)
16 public User findByIdTtl(String id) {
17 User u = new User();
18 try{
19 u = userService.findByIdTtl(id);
20 }catch (Exception e){
21 System.err.println(e.getMessage());
22 }
23 return u;
24 }
25}
测试结果
1// http://localhost:8080/getByCache?id=1
2{
3 "id": "1",
4 "userName": "soulboy"
5}
6根据id=1获取用户对象,从数据库中获取
7再次访问// http://localhost:8080/getByCache?id=1
8 发现控制台没有打印,此时证明数据是从redis缓存中获取。
SpringBoot cache 存在的问题
生成 key 过于简单
生成 key 过于简单,userCache::1 容易冲突。
无法设置过期时间,默认过期时间为永久不过期
默认过期时间是 -1 ,代表永不过期。
配置序列化方式,默认的是序列化 JDKSerialazable
JDKSerialazable 序列化方式对空值、日期的支持不好。
修改 RedisConfig 文件
1import com.fasterxml.jackson.annotation.JsonAutoDetect;
2import com.fasterxml.jackson.annotation.PropertyAccessor;
3import com.fasterxml.jackson.databind.ObjectMapper;
4import org.springframework.cache.CacheManager;
5import org.springframework.cache.annotation.EnableCaching;
6import org.springframework.cache.interceptor.KeyGenerator;
7import org.springframework.context.annotation.Bean;
8import org.springframework.context.annotation.Configuration;
9import org.springframework.data.redis.cache.RedisCacheConfiguration;
10import org.springframework.data.redis.cache.RedisCacheManager;
11import org.springframework.data.redis.cache.RedisCacheWriter;
12import org.springframework.data.redis.connection.RedisConnectionFactory;
13import org.springframework.data.redis.core.RedisTemplate;
14import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
15import org.springframework.data.redis.serializer.RedisSerializationContext;
16
17import javax.annotation.Resource;
18import java.time.Duration;
19import java.util.HashMap;
20import java.util.Map;
21
22@Configuration
23@EnableCaching
24public class RedisConfig {
25
26 @Bean
27 public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
28 RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
29 redisTemplate.setConnectionFactory(factory);
30 return redisTemplate;
31 }
32
33 /**
34 * 生成key过于简单,userCache::1容易冲突。
35 * @return
36 */
37 @Bean
38 public KeyGenerator simpleKeyGenerator() {
39 return (o, method, objects) -> {
40 StringBuilder stringBuilder = new StringBuilder();
41 stringBuilder.append(o.getClass().getSimpleName());
42 stringBuilder.append(".");
43 stringBuilder.append(method.getName());
44 stringBuilder.append("[");
45 for (Object obj : objects) {
46 stringBuilder.append(obj.toString());
47 }
48 stringBuilder.append("]");
49 return stringBuilder.toString();
50 };
51 }
52
53 /**
54 * 解决:默认过期时间为永久不过期的问题
55 * @param redisConnectionFactory
56 * @return
57 */
58 @Bean
59 public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
60 return new RedisCacheManager(
61 RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
62 this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个。
63 this.getRedisCacheConfigurationMap() // 指定 key 策略
64 );
65 }
66 //针对指定的key设置缓存时间,其他未配置的key则使用默认策略。
67 private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
68 Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
69 redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(100));
70 redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000));
71
72 return redisCacheConfigurationMap;
73 }
74
75 /**
76 * 修改默认序列化方式
77 * @param seconds
78 * @return
79 */
80 private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
81 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
82 ObjectMapper om = new ObjectMapper();
83 om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
84 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
85 jackson2JsonRedisSerializer.setObjectMapper(om);
86
87 RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
88 redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
89 RedisSerializationContext
90 .SerializationPair
91 .fromSerializer(jackson2JsonRedisSerializer)//添加jackson的序列化方式
92 ).entryTtl(Duration.ofSeconds(seconds));
93
94 return redisCacheConfiguration;
95 }
96}
Service 层代码
1import com.xdclass.mobile.xdclassmobileredis.domain.User;
2import com.xdclass.mobile.xdclassmobileredis.mapper.UserMapper;
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.cache.annotation.CacheConfig;
5import org.springframework.cache.annotation.CacheEvict;
6import org.springframework.cache.annotation.CachePut;
7import org.springframework.cache.annotation.Cacheable;
8import org.springframework.lang.Nullable;
9import org.springframework.stereotype.Service;
10import org.springframework.transaction.annotation.Propagation;
11import org.springframework.transaction.annotation.Transactional;
12import org.springframework.util.Assert;
13
14@Service
15@CacheConfig(cacheNames="userInfoCache") // 本类内方法指定使用缓存时,默认的名称就是userInfoCache
16@Transactional(propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class)
17public class UserService {
18
19 @Autowired
20 private UserMapper userMapper;
21
22 // 因为必须要有返回值,才能保存到数据库中,如果保存的对象的某些字段是需要数据库生成的,
23 //那保存对象进数据库的时候,就没必要放到缓存了
24 @CachePut(key="#p0.id") //#p0表示第一个参数
25 //必须要有返回值,否则没数据放到缓存中
26 public User insertUser(User u){
27 this.userMapper.insert(u);
28 //u对象中可能只有只几个有效字段,其他字段值靠数据库生成,比如id
29 return this.userMapper.find(u.getId());
30 }
31
32 @CachePut(key="#p0.id")
33 public User updateUser(User u){
34 this.userMapper.update(u);
35 //可能只是更新某几个字段而已,所以查次数据库把数据全部拿出来全部
36 return this.userMapper.find(u.getId());
37 }
38
39 @Nullable
40 //@Cacheable(key="#p0") // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法 #p0代表第一个参数
41 @Cacheable(keyGenerator = "simpleKeyGenerator")//使用自定义key生成策略,防止key的冲突
42 //key的格式:userInfoCache::UserService.findById[1]
43 public User findById(String id){
44 System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
45 Assert.notNull(id,"id不用为空");
46 return this.userMapper.find(id);
47 }
48
49 @CacheEvict(key="#p0") //删除缓存名称为userInfoCache,key等于指定的id对应的缓存
50 public void deleteById(String id){
51 this.userMapper.delete(id);
52 }
53
54 //清空缓存名称为userInfoCache(看类名上的注解)下的所有缓存
55 //如果数据失败了,缓存时不会清除的
56 @CacheEvict(allEntries = true)
57 public void deleteAll(){
58 this.userMapper.deleteAll();
59 }
60
61 @Nullable
62 // @Cacheable 会先查询缓存,如果缓存中存在,则不执行方法
63 //使用自定义key生成策略,防止key的冲突
64 @Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator")
65 //key的格式:UserInfoList::UserService.findByIdTtl[1]
66 public User findByIdTtl(String id){
67 System.err.println("根据id=" + id +"获取用户对象,从数据库中获取");
68 Assert.notNull(id,"id不用为空");
69 return this.userMapper.find(id);
70 }
71}
Controller 层代码
1@RestController
2public class UserController {
3 @Resource
4 private UserService userService;
5
6 @RequestMapping("/getByCache")
7 @ResponseBody
8 public User getByCache(String id) {
9 User user = userService.findById(id);
10 return user;
11 }
12
13 @ResponseBody
14 @RequestMapping(value = "/getexpire", method = RequestMethod.GET)
15 public User findByIdTtl(String id) {
16 User u = new User();
17 try{
18 u = userService.findByIdTtl(id);
19 }catch (Exception e){
20 System.err.println(e.getMessage());
21 }
22 return u;
23 }
24}
测试
1// http://localhost:8080/getexpire?id=1
2{
3 "id": "1",
4 "userName": "soulboy"
5}
压测 Redis cache VS TRDB
ab 是 Apache HTTP server benchmarking tool,可以用以测试 HTTP 请求的服务器性能
1# 安装ab测试工具
2[root@localhost ~]# yum install -y httpd-tools
3[root@localhost ~]# ab -V
4This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
5
6# ab测试工具命令参数含义
7-n #进行http请求的总个数
8-c #同时并发请求的Client数量,即请求的并发数
9qps #qps即每秒并发数,request per second,通过统计qps来测试性能。
10
11# 进行测试
12# Redis cache
13ab -n1000 -c10 http://192.168.31.230:8080/getByCache?id=1
14# TRDB
15ab -n1000 -c10 http://192.168.31.230:8080/getUser?id=1
16
17# 1000请求数 10并发
18Mysql: 1347.98 [#/sec] (mean)
19Redis cache: 1713.87 [#/sec] (mean)
20
21# 1000请求数 100并发
22Mysql: 2510.13 [#/sec] (mean)
23Redis cache: 4440.42 [#/sec] (mean)
24
25# 10000请求数 100并发
26Mysql: 2953.38 [#/sec] (mean)
27Redis cache: 5752.92 [#/sec] (mean)
28
29# 10000请求数 300并发
30Mysql: apr_socket_recv: Connection refused(服务过载,拒绝连接)
31Redis cache: 6241.05 [#/sec] (mean)
32
33# 10000请求数 800并发
34Mysql: apr_socket_recv: Connection refused(服务过载,拒绝连接)
35Redis 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 依赖
1<dependency>
2 <groupId>org.springframework.session</groupId>
3 <artifactId>spring-session-data-redis</artifactId>
4 </dependency>
开启 Redis session 缓存:@EnableRedisHttpSession
DataSourceConfig
- maxInactiveIntervalInSeconds 指定缓存的时间
1import com.alibaba.druid.pool.DruidDataSource;
2import com.alibaba.druid.support.http.StatViewServlet;
3import com.alibaba.druid.support.http.WebStatFilter;
4import org.mybatis.spring.annotation.MapperScan;
5import org.slf4j.Logger;
6import org.slf4j.LoggerFactory;
7import org.springframework.beans.factory.annotation.Value;
8import org.springframework.boot.web.servlet.FilterRegistrationBean;
9import org.springframework.boot.web.servlet.ServletRegistrationBean;
10import org.springframework.context.annotation.Bean;
11import org.springframework.context.annotation.Configuration;
12import org.springframework.context.annotation.Primary;
13import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
14import javax.sql.DataSource;
15import java.sql.SQLException;
16
17@Configuration
18@MapperScan("com.xdclass.mobile.xdclassmobileredis.mapper")
19@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 50)
20public class DataSourceConfig {
21 private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
22
23 @Value("${spring.datasource.url}")
24 private String dbUrl;
25
26 @Value("${spring.datasource.type}")
27 private String dbType;
28
29 @Value("${spring.datasource.username}")
30 private String username;
31
32 @Value("${spring.datasource.password}")
33 private String password;
34
35 @Value("${spring.datasource.driver-class-name}")
36 private String driverClassName;
37
38 @Value("${spring.datasource.initialSize}")
39 private int initialSize;
40
41 @Value("${spring.datasource.minIdle}")
42 private int minIdle;
43
44 @Value("${spring.datasource.maxActive}")
45 private int maxActive;
46
47 @Value("${spring.datasource.maxWait}")
48 private int maxWait;
49
50 @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
51 private int timeBetweenEvictionRunsMillis;
52
53 @Value("${spring.datasource.minEvictableIdleTimeMillis}")
54 private int minEvictableIdleTimeMillis;
55
56 @Value("${spring.datasource.validationQuery}")
57 private String validationQuery;
58
59 @Value("${spring.datasource.testWhileIdle}")
60 private boolean testWhileIdle;
61
62 @Value("${spring.datasource.testOnBorrow}")
63 private boolean testOnBorrow;
64
65 @Value("${spring.datasource.testOnReturn}")
66 private boolean testOnReturn;
67
68 @Value("${spring.datasource.poolPreparedStatements}")
69 private boolean poolPreparedStatements;
70
71 @Value("${spring.datasource.filters}")
72 private String filters;
73
74 @Value("${spring.datasource.connectionProperties}")
75 private String connectionProperties;
76
77 @Value("${spring.datasource.useGlobalDataSourceStat}")
78 private boolean useGlobalDataSourceStat;
79
80 @Value("${spring.datasource.druidLoginName}")
81 private String druidLoginName;
82
83 @Value("${spring.datasource.druidPassword}")
84 private String druidPassword;
85
86 @Bean(name="dataSource",destroyMethod = "close", initMethod="init")
87 @Primary //不要漏了这
88 public DataSource dataSource(){
89 DruidDataSource datasource = new DruidDataSource();
90 try {
91 datasource.setUrl(this.dbUrl);
92 datasource.setDbType(dbType);
93 datasource.setUsername(username);
94 datasource.setPassword(password);
95 datasource.setDriverClassName(driverClassName);
96 datasource.setInitialSize(initialSize);
97 datasource.setMinIdle(minIdle);
98 datasource.setMaxActive(maxActive);
99 datasource.setMaxWait(maxWait);
100 datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
101 datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
102 datasource.setValidationQuery(validationQuery);
103 datasource.setTestWhileIdle(testWhileIdle);
104 datasource.setTestOnBorrow(testOnBorrow);
105 datasource.setTestOnReturn(testOnReturn);
106 datasource.setPoolPreparedStatements(poolPreparedStatements);
107 datasource.setFilters(filters);
108 } catch (SQLException e) {
109 logger.error("druid configuration initialization filter", e);
110 }
111 return datasource;
112 }
113
114 ///////// 下面是druid 监控访问的设置 /////////////////
115 @Bean
116 public ServletRegistrationBean druidServlet() {
117 ServletRegistrationBean reg = new ServletRegistrationBean();
118 reg.setServlet(new StatViewServlet());
119 reg.addUrlMappings("/druid/*"); //url 匹配
120 reg.addInitParameter("allow", "192.168.16.110,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
121 reg.addInitParameter("deny", "192.168.16.111"); //IP黑名单 (存在共同时,deny优先于allow)
122 reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
123 reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
124 reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
125 return reg;
126 }
127
128 @Bean(name="druidWebStatFilter")
129 public FilterRegistrationBean filterRegistrationBean() {
130 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
131 filterRegistrationBean.setFilter(new WebStatFilter());
132 filterRegistrationBean.addUrlPatterns("/*");
133 filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
134 filterRegistrationBean.addInitParameter("profileEnable", "true");
135 filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
136 filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
137 return filterRegistrationBean;
138 }
139}
Controller 层代码
1import org.springframework.web.bind.annotation.RequestMapping;
2import org.springframework.web.bind.annotation.RequestMethod;
3import org.springframework.web.bind.annotation.RestController;
4import javax.servlet.http.HttpServletRequest;
5import java.util.HashMap;
6import java.util.Map;
7
8@RestController
9public class SessionController {
10
11 @RequestMapping(value = "/setSession", method = RequestMethod.GET)
12 public Map<String, Object> setSession (HttpServletRequest request){
13 Map<String, Object> map = new HashMap<>();
14 request.getSession().setAttribute("request Url", request.getRequestURL());
15 map.put("request Url", request.getRequestURL());
16 return map;
17 }
18
19 @RequestMapping(value = "/getSession", method = RequestMethod.GET)
20 public Object getSession (HttpServletRequest request){
21 Map<String, Object> map = new HashMap<>();
22 map.put("sessionIdUrl",request.getSession().getAttribute("request Url"));
23 //设置sessionIdUrl
24 map.put("sessionId", request.getSession().getId());
25 //设置sessionId
26 return map;
27 }
28}
测试
1// http://192.168.31.230:8080/setSession
2{
3 "request Url": "http://192.168.31.230:8080/setSession"
4}
5
6// http://192.168.31.230:8080/getSession
7{
8 "sessionIdUrl": "http://192.168.31.230:8080/setSession",
9 "sessionId": "2c950216-dbd1-4b3b-a748-67e71611a084"
10}
查询 Redis
1spring:session:sessions:expires:81d06c82-5312-41d7-8afe-4d08b77352d6