Redis07: comprehensive application in SpringBoot project

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;
    }
}

Keywords: Java Database Redis

Added by ziggs on Thu, 14 Oct 2021 09:08:37 +0300