目录
RedisTemplate应用
简介
RedisTemplate为SpringBoot工程中操作redis数据库的一个Java对象,此对象封装了对redis的一些基本操作。
准备工作
添加依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
准备工作
第一步:创建工程配置文件application.yml,其内容如下:
spring:
redis: # redis-cli -c -h 192.168.126.129 -p 8010
host: 192.168.126.129
port: 6379
datasource: #mysql连接
url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
#日志配置
logging:
level:
com.jt: debug
第二步:创建工程启动类,例如:
package com.jt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class,args);
}
}
快速入门实现
StringRedisTemplate 应用
StringRedisTemplate 是一个专门用于操作redis字符串类型数据的一个对象,其应用方式如下:
@SpringBootTest
public class StringRedisTemplateTests {
/**
* 此对象为操作redis的一个客户端对象,这个对象
* 对key/value采用了字符串的序列化(StringRedisSerialize)
* 方式进行,redis
* 数据的读写操作
* */
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void testHashOper01(){
//1.获取hash操作对象
HashOperations<String,Object,Object> vo =stringRedisTemplate.opsForHash();
//2.读写redis数据
//2.1存储一个对象
vo.put("user","id","100");
vo.put("user","username","tony");
vo.put("user","status","1");
//2.2获取一个对象
Object status = vo.get("user","status" );
System.out.println(status);
List<Object> user = vo.values("user");
System.out.println(user);
}
@Test
void testStringOper02() throws JsonProcessingException {
//1.获取字符串操作对象
ValueOperations<String,String> vo = stringRedisTemplate.opsForValue();
//2.读写redis中的数据
Map<String,String> map = new HashMap<>();
map.put("id","100");
map.put("title","StringRedisTemplate");
String jsonStr = new ObjectMapper().writeValueAsString(map);
vo.set("blog",jsonStr);
jsonStr = vo.get("blog");
System.out.println(jsonStr);
map = new ObjectMapper().readValue(jsonStr,Map.class);
System.out.println(map);
}
@Test
void testStringOper01(){
//1.获取字符串操作对象
ValueOperations<String,String> vo = stringRedisTemplate.opsForValue();
//2.读写redis中的数据
vo.set("xx","100");
vo.increment("xx");
vo.set("yy","200",1, TimeUnit.SECONDS);
String xx = vo.get("xx");
String yy = vo.get("yy");
System.out.println("xx:"+xx);
System.out.println("yy:"+yy);
}
@Test
void testGetConnection(){
RedisConnection connection =
stringRedisTemplate.getConnectionFactory().getConnection();
String ping = connection.ping();
System.out.println(ping);
}
}
RedisTemplate 应用
RedisTemplate是一个专门用于实现对远端redis中复杂数据的操作的对应,应用案例如下:
@SpringBootTest
public class RedisTemplateTests {
//这个对象在springboot工程的RedisAutoConfiguration类中已经做了配置
//次对象在基于redis存取数据时
@Autowired
private RedisTemplate redisTemplate;
@Test
void testSetOper01(){
//1.获取操作set类型数据的操作对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
SetOperations so = redisTemplate.opsForSet();
//2.读写redis数据set类型数据
so.add("set1","a","b","c","c");
Set set1 = so.members("set1");
System.out.println(set1);
}
@Test
void testListOper01(){
//1.获取操作List类型数据的操作对象
ListOperations lo = redisTemplate.opsForList();
//2.读写redis数据hash类型数据
lo.leftPush("list1","A");
lo.leftPushAll("list1","B","C","D");
lo.leftPop("list1");
List list = lo.range("list1", 0, -1);
System.out.println(list);
}
@Test
void testHashOper01(){
//1.获取操作hash类型数据的操作对象
HashOperations ho = redisTemplate.opsForHash();
//2.读写redis数据hash类型数据
Map<String,Object> map = new HashMap<>();
map.put("id",100);
map.put("title","spring boot");
//直接存储一个map
ho.putAll("blog",map);// hash 存储。序列化
//存储单个字段,值
ho.put("blog","content","spring boot redis");
//取blog对象中id属性的值
Object o = ho.get("blog", "id");
System.out.println(o);
//获取整个blog对象所有的属性和值
Map blog = ho.entries("blog");// 反序列化
System.out.println(blog);
}
@Test
void testStringOper01(){
//自己指定key/value序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
ValueOperations vo = redisTemplate.opsForValue();
//key和value默认会采用JDK的序列化方式进行存储
vo.set("token", UUID.randomUUID().toString());
Object token = vo.get("token");
System.out.println(token);
}
@Test
void testGetConnection(){
RedisConnection connection =redisTemplate.getConnectionFactory().getConnection();
String ping = connection.ping();
System.out.println(ping);
}
}
定制RedisTemplate对象(拓展)
对于系统默认的RedisTemplate默认采用的是JDK的序列化机制,假如我们不希望实用JDK的序列化,可以采用的定制RedisTemplate,并采用自己指定的的序列化方式,例如:
package com.jt.redis.config;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//1.构建RedisTemplate对象
RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>();
//2.设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//3.定义序列化方式(在这里选择jackson)
Jackson2JsonRedisSerializer redisSerializer= new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
//设置要序列化的域(属性)
//any表示任意级别访问修饰符修饰的属性 private,public,protected
objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
//启动输入域检查(类不能是final修饰的)
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
redisSerializer.setObjectMapper(objectMapper);
//4.设置RedisTemplate的序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(redisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer);
//spring规范中假如修改bean对象的默认特性,建议调用一下afterPropertiesSet()
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
创建Blog对象,然后基于RedisTemplate进行序列化实践,Blog代码如下
package com.jt.redis.pojo;
import java.io.Serializable;
public class Blog implements Serializable {//{"id":10,"title":"redis"}
private static final long serialVersionUID = -6721670401642138021L;
private Integer id;
private String title;
public Blog(){
System.out.println("Blog()");
}
public Blog(Integer id,String title){
this.id=id;
this.title=title;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", title='" + title + '\'' +
'}';
}
}
在RedisTemplateTests类中添加如下单元测试方法,进行测试,例如:
@Test
void testJsonOper() throws JsonProcessingException {
ValueOperations valueOperations = redisTemplate.opsForValue();
Blog blog=new Blog(10,"study redis");
valueOperations.set("blog",blog);//序列化
blog=(Blog)valueOperations.get("blog");//反序列化
System.out.println("blog="+blog);
}
业务描述
从一个博客数据库中查询所有的文章标签,然后存储到缓存(Cache),后续查询时可从缓存获取。提高其查询性能。
准备工作
初始化数据
初始化数据库中数据,SQL脚本如下:
DROP DATABASE IF EXISTS `blog`;
CREATE DATABASE `blog` DEFAULT character set utf8mb4;
SET names utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
USE `blog`;
CREATE TABLE `tb_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) NOT NULL COMMENT 'data_id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tb_tag';
insert into `tb_tag` values (null,"mysql"),(null,"redis");
添加项目依赖
在jt-template工程的原有依赖基础上添加mysql数据库访问依赖,例如:
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
添加数据库访问配置
在项目的配置文件(例如application.yml)中添加数据库访问配置,例如:
spring:
datasource:
url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
业务逻辑代码设计及实现
Domain对象设计
创建一个Tag类,基于此类型的对象存储Tag(标签信息),代码如下:
package com.jt.blog.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
/**
* 标签类的设计
*/
@TableName("tb_tag")
public class Tag implements Serializable {
private static final long serialVersionUID = 4504013456197711455L;
/**标签id*/
@TableId(type = IdType.AUTO)
private Long id;
/**标签名*/
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Tag{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Dao 逻辑对象设计
创建Tag信息的数据访问接口,代码如下:
package com.jt.blog.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jt.blog.domain.Tag;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TagMapper
extends BaseMapper<Tag> {
}
创建单元测试类,TagMapper中的相关方法进行单元测试,例如:
package com.jt.blog.dao;
import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagMapperTests {
@Autowired
private TagMapper tagMapper;
@Test
void testSelectList(){
List<Tag> tags =
tagMapper.selectList(null);
for(Tag t:tags){
System.out.println(t);
//System.out.println(t.getId()+"/"+t.getName());
}
}
}
Service 逻辑对象设计
设计TagService接口及实现类,定义Tag(标签)业务逻辑。
第一步:定义TagService接口,代码如下:
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import java.util.List;
public interface TagService {
/**
* 查询所有的标签
* @return
*/
List<Tag> selectTags();
}
第二步:定义TagServiceImpl类,代码如下:
package com.jt.blog.service.impl;
import com.jt.blog.dao.TagMapper;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TagServiceImpl implements TagService {
//RedisAutoConfiguration 类中做的RedisTemplate的配置
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private TagMapper tagMapper;
@Override
public List<Tag> selectTags() {
//1.从redis查询Tag信息,redis有则直接返回
ValueOperations<String,List<Tag>> valueOperations =
redisTemplate.opsForValue();
List<Tag> tags=valueOperations.get("tags");
if(tags!=null&&!tags.isEmpty())return tags;
//2.从redis没有获取tag信息,查询mysql
tags = tagMapper.selectList(null);
//3.将从mysql查询到tag信息存储到redis
valueOperations.set("tags", tags);
//4.返回查询结果
return tags;
}
}
说明,假如将List存储到redis,此时Tag必须实现Serializable接口。
第三步:定义TagServiceTests单元测试类并进行单元测试,代码如下:
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagServiceTests {
@Autowired
private TagService tagService;
@Test
void testSelectTags(){
List<Tag> tags=
tagService.selectTags();
System.out.println(tags);
}
}
Controller逻辑对象设计
创建Tag控制逻辑对象,用于处理请求和响应逻辑,代码如下:
package com.jt.blog.controller;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/tag")
public class TagController {
@Autowired
private TagService tagService;
@GetMapping
public List<Tag> doSelectTags(){
return tagService.selectTags());//1.redis,2.mysql
}
}
启动服务,打开浏览器进行访问测试。同时思考,我们是否可以在这个层加一个本地cache。
业务逻辑代码优化
定制RedisTemplate对象
RedisTemplate默认采用的是JDK的序列化方式,假如对系统对序列化做一些调整,可以自己定义RedisTemplate对象,例如:
package com.jt;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.StringRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisCacheConfig {
//代码定制参考RedisAutoConfiguration类
@Bean
public RedisTemplate<Object,Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
System.out.println("===redisTemplate===");
RedisTemplate<Object,Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//定义redisTemplate对象的序列化方式
//1.定义key的序列化方式
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
//2.定义Value的序列化方式
Jackson2JsonRedisSerializer jsonRedisSerializer=
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper=new ObjectMapper();
objectMapper.setVisibility(
PropertyAccessor.GETTER,
JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
jsonRedisSerializer.setObjectMapper(objectMapper);
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
//3.redisTemplate默认特性设置(除了序列化,其它原有特性不丢)
template.afterPropertiesSet();
return template;
}
}
Service中缓存应用优化
目标:简化缓存代码的编写
解决方案:基于AOP(面向切面编程)方式实现缓存应用
实践步骤:
第一步:在启动上类添加@EnableCaching注解(开启AOP方式的缓存配置),例如:
@EnableCaching //启动AOP方式的缓存配置
@SpringBootApplication
public class RedisApplication {
....
}
第二步:重构TagServiceImpl中的selectTags()方法,方法上使用@Cacheable注解,例如:
@Cacheable(value = "tagCache")
@Override
public List<Tag> selectTags() {
return tagMapper.selectList(null);
}
其中,@Cacheable描述的方法为AOP中的一个切入点方法,访问这个方法时,系统底层会通过一个拦截器,检查缓存中是否有你要的数据,假如有则直接返回,没有则执行方法从数据库查询数据.
我们还可以定义Redis中key和value的序列化方式,修改key的生成策略,例如:
package com.jt.blog;
import org.springframework.cache.annotation.CachingConfigurerSupport;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
/**
* 定义缓存key生成器,不定义也可以使用默认的。
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append("::");
sb.append(method.getName());
for(Object param: params){
sb.append(param);
}
return sb.toString();
}
};
}
/**
* 自定义Cache管理器对象,不定义也可以,有默认的,但假如希望基于AOP
* 方式实现Redis的操作时,按照指定的序列化方式进行序列化,
* 可以对CacheManager进行自定义。
* @param connectionFactory
* @return
*/
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60)) // 60s缓存失效
// 设置key的序列化方式
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
// 设置value的序列化方式
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(
new Jackson2JsonRedisSerializer<Object>(Object.class)))
// 不缓存null值
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
.....
}
其中,写好这个配置类后,可以进行单元测试,检测redis中数据的存储.
第三步:进行单元测试,检测redis中数据的存储.
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagServiceTests {
@Autowired
private TagService tagService;
@Test
void testSelectTags(){
List<Tag> tags=
tagService.selectTags();
System.out.println(tags);
}
}
Service中缓存一致性分析
当我们从数据库查询数据以后,假如将数据存入到了缓存,后续更新了数据库的数据,但假如没有更新缓存就会出现缓存数据与数据库数据不一致的这样的现象,对于这样问题,有时允许在一定时间范围之内存在。假如我们希望在更新了数据库数据以后要更新缓存,如何实现呢?接下来通过一个案例,来演示和解决一下这个问题.
第一步:修改TagService接口,添加相关方法,例如:
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import java.util.List;
public interface TagService {
/**
* 查询所有的标签
* @return
*/
List<Tag> selectTags();
/**
* 创建一个新的tag对象
* @param tag
*/
void insertTag(Tag tag);
/**
* 更新tag对象
* @param tag
* @return
*/
Tag updateTag(Tag tag);
/**
* 基于id查询tag信息
* @param id
* @return
*/
Tag selectById(Long id);
}
第二步:修改TagServiceImpl类,在类中重写TagService接口方法,例如:
package com.jt.blog.service.impl;
import com.jt.blog.dao.TagMapper;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagMapper tagMapper;
/*@Cacheable注解描述的方法为缓存切入点方法
*访问此方法时系统底层会先从缓存查找数据,假如缓存缓存没有,
*会查询mysql数据库,这个注解假如想生效需要在启动类或者配置
*类上添加@EnableCaching注解.
*其中,这里的value用于指定一个key前缀,
*没有指定key属性,则默认会使用 KeyGenerator对象创建key
*/
@Cacheable(value = "tagCache")
@Override
public List<Tag> selectTags() {
return tagMapper.selectList(null);
}
/**
* @CacheEvict注解的作用是定义缓存切入点方法,执行此注解描述的方法
* 时,底层通过AOP方式执行缓存数据的清除操作.
* 其中,allEntries表示清除指定key所有数据,beforeInvocation用于定义
* 在何时清除缓存数据,是更新数据库之后还是之前,false表示之后
*/
@CacheEvict(value = "tagCache",allEntries = true,beforeInvocation = false)
@Override
public void insertTag(Tag tag) {
tagMapper.insert(tag);
}
/**
* 缓存数据时,可以自己指定key,key的值为spring中的el表达式,语法可以打开@Cacheable注解源码进行查看,
* 这里的#id表示基于id的值作为key
*/
@Cacheable(value="tagCache",key="#id")
@Override
public Tag selectById(Long id){
return tagMapper.selectById(id);
}
/** @CachePut注解描述的方法为缓存切入点方法,系统底层会在执行此方法后,更新缓存数据,
* 这里更新完数据以后,key为tag对象的id值,值为方法的返回值.
*/
@CachePut(value = "tagCache",key="#tag.id")
@Override
public Tag updateTag(Tag tag){
tagMapper.updateById(tag);
return tag;
}
}
第三步:修改单元测试类,测试缓存数据一致性.
package com.jt.blog.service;
import com.jt.blog.domain.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class TagServiceTests {
@Autowired
private TagService tagService;
@Test
void testSelectTags(){
List<Tag> tags=
tagService.selectTags();
System.out.println(tags);
}
@Test
void testInsertTag(){
Tag tag=new Tag();
tag.setName("Oracle1");
tagService.insertTag(tag);
}
@Test
void testSelectById(){
Tag tag = tagService.selectById(1L);
System.out.println(tag);
}
@Test
void testUpdateTag(){
Tag tag=new Tag();
tag.setId(1L);
tag.setName("mysql8.0");
tagService.updateTag(tag);
}
}
Controller中添加本地缓存
在Controller中添加一个本地缓存,减少对远程redis缓存的访问,例如:
package com.jt.blog.controller;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@RestController
@RequestMapping("/tag")
public class TagController {
@Autowired
private TagService tagService;
//此对象存哪了?(JVM)
private List<Tag> tags=new CopyOnWriteArrayList<>();//本地 cache
@GetMapping
public List<Tag> doSelectTags(){
if(tags.isEmpty()) {
synchronized (tags) {
if(tags.isEmpty()) {
tags.addAll(tagService.selectTags());//1.redis,2.mysql
}
}
}
return tags;
}
}
Controller中本地缓存一致性分析
此次项目案例中,我们在Controller层添加了本地缓存,这个缓存我们也需要考虑其缓存一致性,其相关代码实现如下:
package com.jt.blog.controller;
import com.jt.blog.domain.Tag;
import com.jt.blog.service.TagService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
@RestController
@RequestMapping("/tag")
public class TagController {
@Autowired
private TagService tagService;
//private List<Tag> tags=new ArrayList<>();
private List<Tag> tags=new CopyOnWriteArrayList<>();//本地 cache
@GetMapping("/{id}")
public Tag doSelectById(@PathVariable("id") Long id){
//查询本地缓存
for(Tag t:tags){
if(t.getId().equals(id)) return t;
}
//查询redis,mysql
return tagService.selectById(id);
}
@PostMapping
public String doInsertTag(Tag tag){
//向数据库写入数据
tagService.insertTag(tag);//A
//更新本地缓存
tags.add(tag);
return "insert ok";
}
@PutMapping
public String doUpdateTag(Tag tag){//id=1,name=mysql 8.9
//向数据库写入数据
tagService.updateTag(tag);
//更新本地缓存
for(Tag t:tags){
if(t.getId().equals(tag.getId())){
t.setName(tag.getName());
}
}
return "update ok";
}
@GetMapping
public List<Tag> doSelectTags(){//B
if(tags.isEmpty()) {
synchronized (tags) {
if(tags.isEmpty()) {
tags.addAll(tagService.selectTags());//1.redis,2.mysql
}
}
}
return tags;
}
/**Spring中Bean对象的生命周期方法,对象初始化时执行此方法*/
@PostConstruct
public void doInit(){
doTimerRefreshTask();
}
/**Spring中Bean对象的生命周期方法,Bean对象初始化时执行此方法*/
@PreDestroy
public void doDestory(){
//退出定时任务
timer.cancel();
}
private Timer timer;
//定义刷新任务
private void doTimerRefreshTask(){
//构建一个定时任务调度对象
timer=new Timer();
//构建一个任务对象
TimerTask task=new TimerTask() {
@Override
public void run() {
System.out.println("refresh cache");
tags.clear();
}
};
//执行任务对象(每隔5秒执行一次)
timer.schedule(task, 5000, 5000);
}
}
Redis集群链接配置实践
修改项目中的application.yml配置文件,修改redis配置,采用集群方式进行实现,例如:
spring:
datasource: #默认配置的是HikariDataSource,应用的是HikariCP链接池(HikariPool)
url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
#redis 集群配置
redis:
cluster: #redis 集群配置
nodes: 192.168.126.129:8010,192.168.126.129:8011,192.168.126.129:8012,192.168.126.129:8013,192.168.126.129:8014,192.168.126.129:8015
max-redirects: 3 #最大跳转次数
timeout: 5000 #超时时间
database: 0
jedis: #连接池
pool:
max-idle: 8
max-wait: 0
#日志配置
logging:
level:
com.jt: debug