Business description
All article tags are queried from a blog database and stored in the cache. Subsequent queries can be obtained from the cache. Improve its query performance.
preparation
Initialization data
Initialize the data in the database. The SQL script is as follows:
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");
Add project dependency
Add mysql database access dependency based on the original dependency of JT template project
<!--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>
Add database access configuration
Add the database access configuration in the project's configuration file (for example, application.yml)
spring: datasource: url: jdbc:mysql:///blog?serverTimezone=Asia/Shanghai&characterEncoding=utf8 username: root password: root
Design and implementation of business logic code
Domain object design
Create a Tag class and store Tag (Tag information) based on this type of object. The code is as follows
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; /** * Design of label class */ @TableName("tb_tag") public class Tag implements Serializable { private static final long serialVersionUID = 4504013456197711455L; /**Tag id*/ @TableId(type = IdType.AUTO) private Long id; /**Tag name*/ 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 logical object design
Create a data access interface for Tag information. The code is as follows:
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> { }
Create a unit test class and perform unit tests using the relevant methods in TagMapper, for example:
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 logical object design
Design TagService interface and implementation class, and define Tag business logic.
Step 1: define the TagService interface. The code is as follows:
package com.jt.blog.service; import com.jt.blog.domain.Tag; import java.util.List; public interface TagService { /** * Query all tags * @return */ List<Tag> selectTags(); }
Step 2: define the TagServiceImpl class. The code is as follows:
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 { //RedisTemplate configuration in RedisAutoConfiguration class @Autowired private RedisTemplate redisTemplate; @Autowired private TagMapper tagMapper; @Override public List<Tag> selectTags() { //1. Query Tag information from redis, and return it directly if redis has any ValueOperations<String,List<Tag>> valueOperations = redisTemplate.opsForValue(); List<Tag> tags=valueOperations.get("tags"); if(tags!=null&&!tags.isEmpty())return tags; //2. If the tag information is not obtained from redis, query mysql tags = tagMapper.selectList(null); //3. Store the tag information queried from mysql in redis valueOperations.set("tags", tags); //4. Return query results return tags; } }
Note: if the List is stored in redis, Tag must implement the Serializable interface.
Step 3: define TagServiceTests unit test class and conduct unit test. The code is as follows:
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 logical object design
Create a Tag control logic object to process request and response logic. The code is as follows
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 } }
Start the service and open the browser for access test. At the same time, consider whether we can add a local cache in this layer.
Business logic code optimization
Cache application optimization in Service
Objective: to simplify the writing of cache code
Solution: implement cache application based on AOP (aspect oriented programming)
Practice steps:
Step 1: add @ EnableCaching annotation on the startup class (enable AOP cache configuration), for example:
@EnableCaching //Start cache configuration in AOP mode @SpringBootApplication public class RedisApplication { .... }
Step 2: reconstruct the selectTags() method in TagServiceImpl, and use @ Cacheable annotation on the method,
@Cacheable(value = "tagCache") @Override public List<Tag> selectTags() { return tagMapper.selectList(null); }
The method described by @ Cacheable is a pointcut method in AOP. When accessing this method, the bottom layer of the system will check whether there is the data you want in the cache through an interceptor. If there is, it will return directly. If not, it will execute the method to query the data from the database
We can also define the serialization method of key and value in Redis, for example:
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()); // Category sb.append("::"); sb.append(method.getName()); // Method name for (Object param : params) { sb.append(param.toString()); } return sb.toString(); }; } // Configure cache manager @Bean public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(60)) // 60s cache failure // Set the serialization method of the key .serializeKeysWith(RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer())) // Set the serialization mode of value .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer( new Jackson2JsonRedisSerializer<Object>(Object.class))) // Do not cache null values .disableCachingNullValues(); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(config) .transactionAware() .build(); } }
After writing this configuration class, you can conduct unit tests to detect the storage of data in redis
Add local cache in Controller
Add a local cache in the Controller to reduce access to the remote redis cache, for example:
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; //Where is this object stored? (JVM) private List<Tag> tags=new CopyOnWriteArrayList<>();//Local cache @GetMapping public List<Tag> doSelectTags(){ if(tags.isEmpty()) { synchronized (tags) { if(tags.isEmpty()) { tags.addAll(tagService.selectTags());//1.redis,2.mysql } } } return tags; } }