一,业务描述
从一个博客数据库中查询所有的文章标签,然后存储到缓存(Cache),后续查询时可从缓存获取。提高其查询性能。
二,准备工作
1.初始化数据
初始化数据库中数据,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");
2.添加项目依赖
在jt-template-plus工程的原有依赖基础上添加mysql数据库访问依赖,例如:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
2.添加数据库访问配置
在项目的配置文件(例如application.yml)中添加数据库访问配置,例如:
spring:
datasource:
url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root
三,业务逻辑代码设计及实现
1.Domain对象设计
创建一个Tag类,基于此类型的对象存储Tag(标签信息),代码如下:
package com.cy.redis.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 = 8475327896912193269L;
@TableId(type = IdType.AUTO)
private Long id;
private String name;
public static long getSerialVersionUID() {
return serialVersionUID;
}
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 + '\'' +
'}';
}
}
2.Dao 逻辑对象设计
创建Tag信息的数据访问接口,代码如下:
package com.cy.redis.blog.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cy.redis.blog.domain.Tag;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TagMapper extends BaseMapper<Tag> {
}
创建单元测试类,TagMapper中的相关方法进行单元测试,例如:
package com.cy.redis;
import com.cy.redis.blog.dao.TagMapper;
import com.cy.redis.blog.domain.Tag;
import com.cy.redis.blog.service.TagServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
public class TagMapperTest {
@Autowired
private TagMapper tagMapper;
@Autowired
private TagServiceImpl tagService;
@Test
public void testSelectAll() {
List<Tag> tags = tagMapper.selectList(null);
for (Tag t : tags) {
System.out.println(t);
}
}
}
运行结果
3.Service 逻辑对象设计
设计TagService接口及实现类,定义Tag(标签)业务逻辑。 第一步:定义TagService接口,代码如下:
package com.cy.redis.blog.service;
import com.cy.redis.blog.domain.Tag;
import java.util.List;
public interface TagService {
List<Tag> selectTags();
}
第二步:定义TagServiceImpl类,代码如下:
package com.cy.redis.blog.service;
import com.cy.redis.blog.dao.TagMapper;
import com.cy.redis.blog.domain.Tag;
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{
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private TagMapper tagMapper;
@Override
public List<Tag> selectTags() {
System.out.println("service ");
ValueOperations valueOperations = redisTemplate.opsForValue();
Object tags = valueOperations.get("tags");
System.out.println("tags = " + tags);
if (tags != null) return (List<Tag>) tags;
System.out.println("==select tags from mysql==");
List<Tag> dbTags = tagMapper.selectList(null);
valueOperations.set("tags",dbTags);
return dbTags;
}
}
说明,假如将List存储到redis,此时Tag必须实现Serializable接口。 第三步:定义TestService单元测试类并进行单元测试,代码如下:
package com.cy.redis.service;
import com.cy.redis.blog.domain.Tag;
import com.cy.redis.blog.service.TagService;
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 TestService {
@Autowired
private TagService tagService;
@Test
void testSelectTags(){
List<Tag> tags=
tagService.selectTags();
System.out.println(tags);
}
}
第一次执行 第二次执行
4.Controller逻辑对象设计
创建Tag控制逻辑对象,用于处理请求和响应逻辑,代码如下:
package com.cy.redis.blog.controller;
import com.cy.redis.blog.domain.Tag;
import com.cy.redis.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;
private List<Tag> tags = new ArrayList<>();
@GetMapping()
public List<Tag> doSelectTags() {
if (tags.isEmpty()) {
return tagService.selectTags();
}
return tags;
}
}
启动服务,打开浏览器进行访问测试。
四,业务逻辑代码优化
1.定制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 {
@Bean
public RedisTemplate<Object,Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
System.out.println("===redisTemplate===");
RedisTemplate<Object,Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
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);
template.afterPropertiesSet();
return template;
}
}
2.Service中缓存应用优化
目标:简化缓存代码的编写 解决方案:基于AOP(面向切面编程)方式实现缓存应用 实践步骤: 第一步:在启动上类添加@EnableCaching注解(开启AOP方式的缓存配置),例如:
@EnableCaching
@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 {
@Bean
public KeyGenerator keyGenerator() {
return (o, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append("::");
sb.append(method.getName());
for (Object param : params) {
sb.append(param.toString());
}
return sb.toString();
};
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(
new Jackson2JsonRedisSerializer<Object>(Object.class)))
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
.....
}
其中,写好这个配置类后,可以进行单元测试,检测redis中数据的存储.
3.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;
private List<Tag> tags=new CopyOnWriteArrayList<>();
@GetMapping
public List<Tag> doSelectTags(){
if(tags.isEmpty()) {
synchronized (tags) {
if(tags.isEmpty()) {
tags.addAll(tagService.selectTags());
}
}
}
return tags;
}
}
|