SpringBoot集成MyBatis采用Redis做二级缓存

摘要

本文记录了SpringBoot2集成Mybatis采用Redis做缓存测试Mysql百万级数据库查询性能(Mysql和Redis使用Docker模式部署在虚拟机中)的实验过程。测试结果为直接从Mysql查询在350毫秒左右,增加Redis缓存查询在5毫秒左右。Redis作为缓存能够大幅度提高查询效率。整个集成的关键信息包括在POM中增加spring-boot-starter-data-redis依赖,实现Mybatis缓存接口Cache,使用Spring默认的RedisTemplate操作缓存,通过StringRedisSerializer将数据序列化为JSON格式。 下文详细记录了每一步的集成过程。


通过SpringBoot集成Mybatis

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>spring-boot-mybatis-cache2-redis</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</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-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

SpringBoot配置

spring:
  redis:
    host: 192.168.172.144
    password:
    port: 6379
    jedis:
      pool:
        min-idle: 0
        max-active: 8
        max-idle: 8
        max-wait: 5000
  datasource:
    url: jdbc:mysql://192.168.172.144:3306/test?characterEncoding=UTF-8&amp;allowMultiQueries=true
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
mybatis:
  configuration:
    cache-enabled: true
  mapper-locations: classpath:mapper/*.xml


配置Mybatis
编写一个POJO。UserInfo。
编写一个Mapper接口。

@Mapper
@Component
public interface UserInfoMapper {

    UserInfo selectById(Long userId);

}

针对Mapper接口进行sql配置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserInfoMapper">
    <!--使用自定义的缓存-->
    <cache type="com.example.demo.cache.RedisCache"/>

    <resultMap id="userResultMap" type="com.example.demo.entities.UserInfo">
        <id property="userId" column="userId" jdbcType="BIGINT" javaType="java.lang.Long"/>
        <result property="userName" column="userName" jdbcType="VARCHAR" javaType="java.lang.String"/>
        <result property="userAge" column="userAge" jdbcType="INTEGER" javaType="java.lang.Integer"/>
        <result property="label" column="label" jdbcType="VARCHAR" javaType="java.lang.String"/>
    </resultMap>
    <select id="selectById" resultMap="userResultMap" parameterType="java.lang.Long" useCache="true">
        SELECT userId,userName,userAge,label
        FROM userInfo
        WHERE userId = #{id}
    </select>

</mapper>

开启Mybatis二级缓存使用Redis做缓存

开启Mybatis二级缓存,自定义Cache

 <cache type="com.example.demo.cache.RedisCache"/>
 
 mybatis:
  configuration:
    cache-enabled: true

编写缓存实现方法:
public class RedisCache implements Cache

public class RedisCache implements Cache {

    private String id;

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public RedisCache(String id) {
        this.id = id;
    }

    private RedisTemplate<Object, Object> getRedisTemplate(){
        return ApplicationContextHolder.getBean("redisTemplate");
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void putObject(Object key, Object value) {
        getRedisTemplate().boundHashOps(getId()).put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return getRedisTemplate().boundHashOps(getId()).get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return getRedisTemplate().boundHashOps(getId()).delete(key);
    }

    @Override
    public void clear() {
        getRedisTemplate().delete(getId());
    }

    @Override
    public int getSize() {
        Long size = getRedisTemplate().boundHashOps(getId()).size();
        return size == null ? 0 : size.intValue();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}

使用RedisTemplate进行操作。

@Configuration
public class RedisConfiguration {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public RedisTemplate<String,Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
        return redisTemplate;
    }

    /**
     * 设置数据存入 redis 的序列化方式
     * @param template
     * @param factory
     */
    private void initDomainRedisTemplate(RedisTemplate<String, Object> template, RedisConnectionFactory factory) {
        // 定义 key 的序列化方式为 string
        // 需要注意这里Key使用了 StringRedisSerializer,那么Key只能是String类型的,不能为Long,Integer,否则会报错抛异常。
        StringRedisSerializer redisSerializer = new StringRedisSerializer();
        template.setKeySerializer(redisSerializer);
        // 定义 value 的序列化方式为 json
        @SuppressWarnings({"rawtypes", "unchecked"})
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        //hash结构的key和value序列化方式
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setEnableTransactionSupport(true);
        template.setConnectionFactory(factory);
    }

}

采用JSON格式存储。

通过POSTMAN进行测试-正例

Postman配置述
请求设置为:

http://localhost:8080/getbyid?id={{$randomInt}}

参数补充说明:


{{$guid}}:添加一个V4风格GUID(如: aa002-44ac-45ca-aae3-52bf19650e2d)

{{$timestamp}}:将当前的时间戳,精确到秒

{{$randomInt}}:添加0和1000之间的随机整数

测试结果:
测试返回结果
性能测试结果:
从Mysql查询数据为350毫秒,从redis缓存获取5毫秒。

RedisDesktopManager查看缓存信息

Redis里面缓存的信息:
在这里插入图片描述

Redis中具体缓存值查看:

键名:
["org.apache.ibatis.cache.CacheKey",{"multiplier":37,"hashcode":260798286,"checksum":-94844027,"count":6,"updateList":["java.util.ArrayList",["com.example.demo.mapper.UserInfoMapper.selectById",0,2147483647,"SELECT userId,userName,userAge,label\n        FROM userInfo\n        WHERE userId = ?",["java.lang.Long",173],"SqlSessionFactoryBean"]],"updateCount":6}]
键值:
["java.util.ArrayList",[["com.example.demo.entities.UserInfo",{"userId":173,"userName":"姓名173","userAge":20,"label":"标签173"}]]]

反例测试-试试缓存穿透

修改请求为http://localhost:8080/getbyid?id=-{{$randomInt}},前面增加-。

通过POSTMAN做1000次循环,传入的参数在数据库中都不存在。
在这里插入图片描述
大部分数据都是在350毫秒左右,如果已经存在了从redis缓存读取大概在5毫秒左右。
在这里插入图片描述
缓存穿透是在如果数据库查询对象为空,则不放进缓存的场景下。本次试验里,缓存为空也记录到redis里了。算是解决了缓存穿透的问题。如果这样的数据太多,会导致redis空间浪费,可以单独设置这样的失效时间更短点,比如60秒。


缓存穿透、缓存雪崩、缓存击穿,集中点在缓存Key是否存在,缓存有效期有很大的关系,比如缓存击穿类似爆款,可以设置永不过期,缓存雪崩需要分散有效期避免集体趴窝。
后面研究如何针对不同场景进行缓存过期时间设置。

更优秀博文补充记录:

Spring Boot + Mybatis + Redis二级缓存(Java Web现代化开发)

总结

Mybatis二级缓存控制粒度太过于简单,无法针对命名空间独立设置过期时间,无法对缓存增加逻辑判断。对于简单的单表查询,快速开发可以使用,对于并发高要求的环境下不推荐,建议自己根据缓存框架编写灵活的代码。

发布了25 篇原创文章 · 获赞 7 · 访问量 936

猜你喜欢

转载自blog.csdn.net/m0_46485771/article/details/104763255