前面提到了,使用mybatis cache,一般是结合redis使用。
一、demo
1、数据表
create table demo.t_address
(id int auto_incrementprimary key,address_name varchar(200) null,address_code varchar(20) null,address_type int null
);
项目结构:
2、pom
<?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>org.example</groupId><artifactId>plus-mybatis-cache</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.4</version><relativePath/></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--尽量不要同时导入mybatis 和 mybatis_plus,避免版本差异--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.13</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency></dependencies>
</project>
3、配置文件
server.port=1112
server.servlet.context-path=/mybatisCacheDemo
#mysql
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3308/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=wtyy
#mybatis
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
#打印日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 开启二级缓存
mybatis-plus.configuration.cache-enabled=true
#redis
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.enabled=true
spring.data.redis.lettuce.pool.time-between-eviction-runs=30s
4、util
package com.pluscache.demo.util;import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import java.lang.annotation.Annotation;
import java.util.Map;@Component
public class ApplicationContextUtil implements ApplicationContextAware {private static ApplicationContext applicationContext ;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextUtil.applicationContext = applicationContext;}public static Object getBean(String name) {return ApplicationContextUtil.applicationContext.getBean(name);}public static <T> T getBean(String name, Class<T> clazz) {return applicationContext.getBean(name, clazz);}public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {return applicationContext.getBeansOfType(clazz);}public static ApplicationContext getApplicationContext() {return applicationContext;}public static <T extends Annotation> T getAnnotation(Object bean, Class<T> annotationClass) {T annotation = bean.getClass().getAnnotation(annotationClass);if (annotation == null) {annotation = AopUtils.getTargetClass(bean).getAnnotation(annotationClass);}return annotation;}}
5、config
package com.pluscache.demo.config;import com.pluscache.demo.util.ApplicationContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;@Slf4j
//@Component
public class MybatisRedisCache implements Cache {/* * id是必须的带上的,这里的id会指定当前放入缓存的mapper的namespace* 如这里的id就是com.sample.dao.IEmployeeDao*/private final String id;private RedisTemplate redisTemplate;public MybatisRedisCache(final String id) {//获取redis实例//redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");//指定key的序列化方式//redisTemplate.setKeySerializer(new StringRedisSerializer());//redisTemplate.setHashKeySerializer(new StringRedisSerializer());this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic void putObject(Object key, Object value) {this.getRedisTemplate();redisTemplate.opsForHash().put(id.toString(),key.toString(),value);}private void getRedisTemplate() {if (redisTemplate == null) {//由于启动期间注入失败,只能运行期间注入,这段代码可以删除redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");}}@Overridepublic Object getObject(Object key) {this.getRedisTemplate();return redisTemplate.opsForHash().get(id.toString(),key.toString());}@Overridepublic Object removeObject(Object key) {return null;}@Overridepublic void clear() {this.getRedisTemplate();redisTemplate.delete(id.toString());}@Overridepublic int getSize() {this.getRedisTemplate();return redisTemplate.opsForHash().size(id.toString()).intValue();}
}
6、dto
package com.pluscache.demo.dto;import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;@Data
@TableName("t_address")
public class AddressDTO implements Serializable {private Integer id;private String addressName;private String addressCode;public Integer addressType;
}
7、dao
(1)repository
可以省略,移到service中
package com.pluscache.demo.repository;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pluscache.demo.dto.AddressDTO;
import com.pluscache.demo.mapper.AddressMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
@Slf4j
public class AddressRepository {@Autowiredprivate AddressMapper addressMapper;//plus新增public void insert(AddressDTO addressDTO) {addressMapper.insert(addressDTO);}//手动SQL新增public void save(AddressDTO addressDTO) {addressMapper.save(addressDTO);}public AddressDTO getById(Integer id) {LambdaQueryWrapper<AddressDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(AddressDTO::getId, id);return addressMapper.selectOne(queryWrapper);}public List<AddressDTO> listByType(Integer type) {LambdaQueryWrapper<AddressDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(AddressDTO::getAddressType, type);return addressMapper.selectList(queryWrapper);}public List<AddressDTO> listByTypeRecord(Integer type) {return addressMapper.listByTypeRecord(type);}public List<AddressDTO> listById(Integer id) {LambdaQueryWrapper<AddressDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(AddressDTO::getId, id);return addressMapper.selectList(queryWrapper);}
}
(2)mapper
在这里做的缓存
package com.pluscache.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pluscache.demo.config.MybatisRedisCache;
import com.pluscache.demo.dto.AddressDTO;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Param;import java.util.List;@CacheNamespace(implementation = MybatisRedisCache.class, eviction = MybatisRedisCache.class)
public interface AddressMapper extends BaseMapper<AddressDTO> {void save(@Param("record") AddressDTO addressDTO);List<AddressDTO> listByTypeRecord(@Param("type") Integer type);
}
(3)xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pluscache.demo.mapper.AddressMapper"><cache-ref namespace="com.pluscache.demo.mapper.AddressMapper"/><insert id="save">insert into t_address(address_name,address_code,address_type) values (#{record.addressName},#{record.addressCode},#{record.addressType})</insert><select id="listByTypeRecord" resultType="com.pluscache.demo.dto.AddressDTO">select address_name,address_code from t_address where address_type =#{type}</select></mapper>
8、service
package com.pluscache.demo.service.impl;import com.pluscache.demo.dto.AddressDTO;
import com.pluscache.demo.repository.AddressRepository;
import com.pluscache.demo.service.AddressService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service("addressService")
public class AddressServiceImpl implements AddressService {@Autowiredprivate AddressRepository addressRepository;@Overridepublic void insert(AddressDTO addressDTO) {addressRepository.insert(addressDTO);}@Overridepublic void save(AddressDTO addressDTO) {addressRepository.save(addressDTO);}@Overridepublic AddressDTO getById(Integer id) {return addressRepository.getById(id);}@Overridepublic List<AddressDTO> listByType(Integer type) {return addressRepository.listByType(type);}@Overridepublic List<AddressDTO> listByTypeRecord(Integer type) {return addressRepository.listByTypeRecord(type);}@Overridepublic List<AddressDTO> listById(Integer id) {return addressRepository.listById(id);}
}
9、controller
package com.pluscache.demo.controller;import com.pluscache.demo.dto.AddressDTO;
import com.pluscache.demo.service.AddressService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
@RequestMapping("/address")
public class AddressController {@Autowiredprivate AddressService addressService;@RequestMapping("/insert")public void insert(AddressDTO addressDTO) {addressService.insert(addressDTO);}@RequestMapping("/save")public void save(AddressDTO addressDTO) {addressService.save(addressDTO);}@RequestMapping("/getById")public AddressDTO getById(Integer id) {return addressService.getById(id);}@RequestMapping("/listByType")public List<AddressDTO> listByType(Integer type) {return addressService.listByType(type);}@RequestMapping("/listByTypeRecord")public List<AddressDTO> listByTypeRecord(Integer type) {return addressService.listByTypeRecord(type);}@RequestMapping("/listById")public List<AddressDTO> listById(Integer id) {return addressService.listById(id);}}
10、启动类
package com.pluscache.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.pluscache.demo.mapper")
@SpringBootApplication
public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}
}
11、测试
(1)新增数据
新增了几条数据
(2)SQL法根据type查询
① 第一次访问
localhost:1112/mybatisCacheDemo/address/listByTypeRecord?type=1
后台打印:
查看redis生成了缓存
② 再次访问,没有查询db了
(3) mybatis-plus根据type查询
① 第一次访问localhost:1112/mybatisCacheDemo/address/listByType?type=1
后台从db查询
查看redis的缓存:可以看到又生成了一条缓存
② 再次访问,没有从数据库查询
(4)mybatis-plus根据id查询
① 第一次访问localhost:1112/mybatisCacheDemo/address/listById?id=1
走db查看
查看redis缓存:
② 再次访问,不再走db查询
(5)执行add
这里生成的id是自增长的,理论上,只需要清空type=1的缓存,不需要清空id=1的缓存。但是刷新redis可以看到,整个大key都清除了。
二、缺点
从上面的demo可以看到,mybatis cache不够灵活;且一个mapper对应一个大key,存在redis存储bigkey的问题。由此引入了spring cache。当然,选择哪种缓存策略取决于你的具体需求,如果你需要一个轻量级的、与 MyBatis 紧密集成的缓存解决方案,可以选择 MyBatis Cache。如果你需要一个功能丰富的缓存解决方案,支持多种缓存策略、分布式缓存以及高级缓存管理,可以选择 Spring Cache。
两者的比较:
1、概念
MyBatis 的 Cache 默认是一个本地缓存,可以自定义引入redis,用于缓存查询的结果以提高性能。
Spring Cache 是一个抽象的缓存层,它可以支持多种缓存实现(如 EhCache、Redis、Caffeine等),并提供了一套统一的接口来进行缓存操作。
2、使用
MyBatis Cache 主要用于 MyBatis 的二级缓存,主要是缓存 SQL 查询的结果,主要是针对 MyBatis 层面。
Spring Cache 是一个通用的缓存抽象,它可以和任何技术栈的缓存框架集成,不仅限于 MyBatis。
3、配置复杂度:
MyBatis Cache 配置相对简单,通常在 MyBatis 配置文件中进行配置。
Spring Cache 的配置相对复杂,需要配置缓存管理器,并且可以使用注解或者 XML 配置方式。
4、缓存策略:
MyBatis Cache 只能使用内置的缓存策略。
Spring Cache 支持多种缓存策略,可以灵活配置和扩展。
5、分布式支持:
MyBatis Cache 不支持分布式环境下的缓存同步。
Spring Cache 支持分布式缓存,可以通过 Redis 等中间件来实现。
6、缓存数据形式:
MyBatis Cache 缓存的是对象的深拷贝。
Spring Cache 缓存的数据可以是任意数据类型。
7、缓存 key 的定义:
MyBatis Cache 需要自定义 key 生成机制。
Spring Cache 可以使用 SpEL 表达式定义 key。
8、缓存失效策略:
MyBatis Cache 需要自定义缓存的失效机制。
Spring Cache 可以通过注解定义缓存的失效时间。
9、缓存缓存的数据范围:
MyBatis Cache 缓存的数据范围仅限于当前 namespace。
Spring Cache 可以在多个 namespace 或者多个应用间共享缓存数据。