菜鸟的mybatis plus结合redis二级缓存学习总结
说明
更新时间:2020/8/22 18:28,更新了缓存相关内容
本文主要对mybatis plus结合redis二级缓存的学习总结,里面涉及mybatis plus和redis的相关知识,项目名称springboot_redis5,本文会持续更新,不断地扩充
本文仅为记录学习轨迹,如有侵权,联系删除
一、项目配置
建议在看这篇博文前先去学习一下mybatis plus和redis相关知识,或者可以看一下我之前整理的菜鸟的redis学习总结和菜鸟的mybatis plus学习总结,下面直接上手实战,这里是结合了redis的分布式缓存,与jvm的本地缓存有明显区别。
给出项目结构
(1)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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zsc</groupId>
<artifactId>springboot_redis5</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_redis5</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--Druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(2)application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test2?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
password: 123
username: root
#切换为druid数据源
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
#需要导入log4j依赖
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#redis配置
redis:
host: 127.0.0.1
port: 6379
mybatis-plus:
#指定xml的配置文件
mapper-locations: classpath*:/mapper/*.xml
configuration:
#缓存开启
cache-enabled: true
#mybatis-plus:
# mapper-locations: classpath*:/mapper/*.xml
# type-aliases-package: com.redis.shaobing.entity
# global-config:
# db-config:
# id-type: auto
# table-underline: true
# configuration:
# cache-enabled: true
# map-underscore-to-camel-case: true
logging:
level:
com.zsc: trace
(3)sql文件
/*
Navicat Premium Data Transfer
Source Server : test1
Source Server Type : MySQL
Source Server Version : 80015
Source Host : localhost:3306
Source Schema : test2
Target Server Type : MySQL
Target Server Version : 80015
File Encoding : 65001
Date: 22/08/2020 18:26:33
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_vip0');
INSERT INTO `role` VALUES (2, 'ROLE_vip1');
INSERT INTO `role` VALUES (3, 'ROLE_vip2');
INSERT INTO `role` VALUES (4, 'ROLE_vip3');
INSERT INTO `role` VALUES (5, 'ROLE_vip44');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (3, '灰太狼', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (4, '喜羊羊', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (5, '懒羊羊', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (6, '小灰灰', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq');
INSERT INTO `user` VALUES (7, '张三', '123456');
INSERT INTO `user` VALUES (8, '张三', '123');
INSERT INTO `user` VALUES (10, '张三', '123');
INSERT INTO `user` VALUES (11, '张三', '123');
INSERT INTO `user` VALUES (12, '张三', '123');
INSERT INTO `user` VALUES (13, '张三', '123');
INSERT INTO `user` VALUES (14, '张三', '123');
INSERT INTO `user` VALUES (15, '张三', '123');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(11) NULL DEFAULT NULL,
`rid` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 1, 3);
INSERT INTO `user_role` VALUES (4, 1, 4);
INSERT INTO `user_role` VALUES (5, 3, 2);
INSERT INTO `user_role` VALUES (6, 4, 3);
INSERT INTO `user_role` VALUES (7, 6, 4);
INSERT INTO `user_role` VALUES (8, 5, 1);
SET FOREIGN_KEY_CHECKS = 1;
二、自定义缓存
后面用于缓存的存储
package com.zsc.cache;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zsc.util.ApplicationContextUtil;
import com.zsc.util.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @ClassName : MybatisRedisCache
* @Description : mybatis-plus结合redis自定义缓存管理,注意:该缓存模板并不完善,这里没有设置缓存时间,
* 实际上应该根据应用场景设置过期时间等
* @Author : CJH
* @Date: 2020-08-19 21:49
*/
@Slf4j
public class MybatisPlusRedisCache implements Cache {
/**
* 注意,这里无法通过@Autowired等注解的方式注入bean,只能手动获取
*/
private RedisUtil redisUtil;
/**
* 手动获取bean
*
* @return
*/
private void getRedisUtil() {
redisUtil = (RedisUtil) ApplicationContextUtil.getBean("redisUtil");
}
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private String id;
public MybatisPlusRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
log.info("存入缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
//将key加密后存入
redisUtil.hset(this.id.toString(),this.MD5Encrypt(key),value);
} catch (Exception e) {
log.error("存入缓存失败!");
e.printStackTrace();
}
}
@Override
public Object getObject(Object key) {
log.info("获取缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
if (key != null) {
return redisUtil.hget(this.id.toString(),this.MD5Encrypt(key));
}
} catch (Exception e) {
log.error("获取缓存失败!");
e.printStackTrace();
}
return null;
}
@Override
public Object removeObject(Object key) {
log.info("删除缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
if (key != null) {
redisUtil.del(this.MD5Encrypt(key));
}
} catch (Exception e) {
log.error("删除缓存失败!");
e.printStackTrace();
}
return null;
}
@Override
public void clear() {
log.info("清空缓存");
if (redisUtil == null) {
getRedisUtil();//获取bean
}
try {
redisUtil.del(this.id.toString());
} catch (Exception e) {
log.error("清空缓存失败!");
e.printStackTrace();
}
}
@Override
public int getSize() {
if (redisUtil == null) {
getRedisUtil();//获取bean
}
Long size = (Long)redisUtil.execute((RedisCallback<Long>) RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
/**
* MD5加密存储key,以节约内存
*/
private String MD5Encrypt(Object key){
String s = DigestUtils.md5DigestAsHex(key.toString().getBytes());
return s;
}
}
三、配置类
主要有两个,一个是mybayis plus的相关配置,一个是redis的相关配置
package com.zsc.config;
/**
* @ClassName : MyBatisPlusConfig
* @Description : mybatis plus配置类
* @Author : CJH
* @Date: 2020-08-15 16:06
*/
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@MapperScan("com.zsc.mapper")//可以将主启动类的扫描mapper放到这里
@EnableTransactionManagement//管理事务
@Configuration//表明这是一个配置类
public class MyBatisPlusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
//分页查询插件配置
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
return paginationInterceptor;
}
}
package com.zsc.config;
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 com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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;
/**
* redis配置类
*/
@Configuration
public class RedisConfig {
/**redis配置模板,可直接copy使用**/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
throws UnknownHostException {
//为了开发的方便,一般直接使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
//json的序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance ,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
四、实体类
主要有两个,Role和User
/**
* @ClassName : Role
* @Description : 角色类
* @Author : CJH
* @Date: 2020-08-20 11:38
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("role")
public class Role implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
//角色可以赋值给多个用户
@TableField(exist = false)//mybatis-plus联表查询相关的实体必须用该语句修饰,不然会报错
private List<User> userList;
}
package com.zsc.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* @ClassName : User
* @Description : 用户
* @Author : CJH
* @Date: 2020-08-19 21:13
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "username")//与数据库字段映射
private String name;
private String password;
//用户可以有多个角色
@TableField(exist = false)//mybatis-plus联表查询相关的实体必须用该语句修饰,不然会报错
private List<Role> roleList;
}
五、持久层的mapper接口
主要有两个,RoleMapper和UserMapper
package com.zsc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zsc.cache.MybatisPlusRedisCache;
import com.zsc.entity.Role;
import com.zsc.entity.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.CacheNamespaceRef;
import org.springframework.stereotype.Repository;
/**
* @ClassName : RoleMapper
* @Description : 角色类持久层接口
* @Author : CJH
* @Date: 2020-08-20 11:40
*/
@Repository
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)
public interface RoleMapper extends BaseMapper<Role> {
}
package com.zsc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zsc.cache.MybatisPlusRedisCache;
import com.zsc.entity.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.CacheNamespaceRef;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* @ClassName : UserMapper
* @Description : User持久层
* @Author : CJH
* @Date: 2020-08-19 21:19
*/
@Repository
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)
public interface UserMapper extends BaseMapper<User> {
String findUserById(Long id);
User findUserAllRoleById(Long id);
}
六、工具类
主要有两个,ApplicationContextUtil用于手动获取bean,在自定义缓存里面用到,另一个是RedisUtil的工具类,这个纯属个人习惯,个人喜欢把redis的相关命令自己封装成一个工具类来使用
package com.zsc.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @ClassName : ApplicationContextUtil
* @Description : 手动获取bean的工具类
* @Author : CJH
* @Date: 2020-08-19 22:13
*/
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
/**
*
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
return context.getBean(beanName);
}
}
package com.zsc.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
*
* Redis工具类,一般企业开发常用的一个工具类,不会去用原生的redis配置类
*
*/
@Component("redisUtil")
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object execute(RedisCallback action){
Object execute = redisTemplate.execute(action);
return execute;
}
/**
* 删除Collection<String>集合中的key
* @param keys
* @return
*/
public Long del(Collection<String> keys){
Long delete = redisTemplate.delete(keys);
return delete;
}
/**
* 模糊查询key
* @param str 模糊查询的条件
* @return
*/
public Set<String> keys(String str){
Set<String> keys = redisTemplate.keys(str);
return keys;
}
/**
* 消息订阅与发布:发布
* @param channel
* @param message
*/
public void convertAndSend(String channel, Object message){
redisTemplate.convertAndSend(channel,message);
}
/**
* 如果不存在且保存的时间大于0即可创建成功,否则创建失败
* @param key 键
* @param value 值
* @param time 缓存的时间
* @return
*/
public boolean setNx(String key,Object value,long time){
try {
if (time > 0) {
return redisTemplate.opsForValue().setIfAbsent(key,value,time,TimeUnit.SECONDS);
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 如果不存在才创建成功,否则就创建失败
* @param key
* @param value
* @return
*/
public boolean setNx(String key,Object value){
try {
return redisTemplate.opsForValue().setIfAbsent(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// =============================common============================
/**
* 26
* 指定缓存失效时间
* 27
*
* @param key 键
* 28
* @param time 时间(秒)
* 29
* @return 30
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 44
* 根据key 获取过期时间
* 45
*
* @param key 键 不能为null
* 46
* @return 时间(秒) 返回0代表为永久有效
* 47
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 53
* 判断key是否存在
* 54
*
* @param key 键
* 55
* @return true 存在 false不存在
* 56
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 67
* 删除缓存
* 68
*
* @param key 可以传一个值 或多个
* 69
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 83
* 普通缓存获取
* 84
*
* @param key 键
* 85
* @return 值
* 86
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 92
* 普通缓存放入
* 93
*
* @param key 键
* 94
* @param value 值
* 95
* @return true成功 false失败
* 96
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 109
* 普通缓存放入并设置时间
* 110
*
* @param key 键
* 111
* @param value 值
* 112
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* 113
* @return true成功 false 失败
* 114
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 130
* 递增
* 131
*
* @param key 键
* 132
* @param delta 要增加几(大于0)
* 133
* @return 134
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 143
* 递减
* 144
*
* @param key 键
* 145
* @param delta 要减少几(小于0)
* 146
* @return 147
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* 157
* HashGet
* 158
*
* @param key 键 不能为null
* 159
* @param item 项 不能为null
* 160
* @return 值
* 161
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 167
* 获取hashKey对应的所有键值
* 168
*
* @param key 键
* 169
* @return 对应的多个键值
* 170
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 176
* HashSet
* 177
*
* @param key 键
* 178
* @param map 对应多个键值
* 179
* @return true 成功 false 失败
* 180
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 192
* HashSet 并设置时间
* 193
*
* @param key 键
* 194
* @param map 对应多个键值
* 195
* @param time 时间(秒)
* 196
* @return true成功 false失败
* 197
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 212
* 向一张hash表中放入数据,如果不存在将创建
* 213
*
* @param key 键
* 214
* @param item 项
* 215
* @param value 值
* 216
* @return true 成功 false失败
* 217
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 229
* 向一张hash表中放入数据,如果不存在将创建
* 230
*
* @param key 键
* 231
* @param item 项
* 232
* @param value 值
* 233
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* 234
* @return true 成功 false失败
* 235
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 250
* 删除hash表中的值
* 251
*
* @param key 键 不能为null
* 252
* @param item 项 可以使多个 不能为null
* 253
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 259
* 判断hash表中是否有该项的值
* 260
*
* @param key 键 不能为null
* 261
* @param item 项 不能为null
* 262
* @return true 存在 false不存在
* 263
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* 269
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* 270
*
* @param key 键
* 271
* @param item 项
* 272
* @param by 要增加几(大于0)
* 273
* @return 274
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* 280
* hash递减
* 281
*
* @param key 键
* 282
* @param item 项
* 283
* @param by 要减少记(小于0)
* 284
* @return 285
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 292
* 根据key获取Set中的所有值
* 293
*
* @param key 键
* 294
* @return 295
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 306
* 根据value从一个set中查询,是否存在
* 307
*
* @param key 键
* 308
* @param value 值
* 309
* @return true 存在 false不存在
* 310
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 321
* 将数据放入set缓存
* 322
*
* @param key 键
* 323
* @param values 值 可以是多个
* 324
* @return 成功个数
* 325
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 336
* 将set数据放入缓存
* 337
*
* @param key 键
* 338
* @param time 时间(秒)
* 339
* @param values 值 可以是多个
* 340
* @return 成功个数
* 341
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 355
* 获取set缓存的长度
* 356
*
* @param key 键
* 357
* @return 358
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 369
* 移除值为value的
* 370
*
* @param key 键
* 371
* @param values 值 可以是多个
* 372
* @return 移除的个数
* 373
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 386
* 获取list缓存的内容
* 387
*
* @param key 键
* 388
* @param start 开始
* 389
* @param end 结束 0 到 -1代表所有值
* 390
* @return 391
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 402
* 获取list缓存的长度
* 403
*
* @param key 键
* 404
* @return 405
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 416
* 通过索引 获取list中的值
* 417
*
* @param key 键
* 418
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* 419
* @return 420
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 431
* 将list放入缓存
* 432
*
* @param key 键
* 433
* @param value 值
* 434
* //@param time 时间(秒)
* 435
* @return 436
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 467
* 将list放入缓存
* 468
*
* @param key 键
* 469
* @param value 值
* 470
* //@param time 时间(秒)
* 471
* @return 472
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 484
* 将list放入缓存
* 485
* <p>
* 486
*
* @param key 键
* 487
* @param value 值
* 488
* @param time 时间(秒)
* 489
* @return 490
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 504
* 根据索引修改list中的某条数据
* 505
*
* @param key 键
* 506
* @param index 索引
* 507
* @param value 值
* 508
* @return 509
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 521
* 移除N个值为value
* 522
*
* @param key 键
* 523
* @param count 移除多少个
* 524
* @param value 值
* 525
* @return 移除的个数
* 526
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
七、mapper对应xml文件
<?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.zsc.mapper.RoleMapper">
<!-- mapper 使用缓存的方式是使用”<cache-ref>“标签引用dao的命名空间,因为dao中已经开启缓存,-->
<!-- 这样与dao中的缓存保持一直,如果mapper文件不引用缓存,-->
<!-- 执行update方法时,不会清除dao的缓存,导致数据库数据与缓存数据不一致。-->
<!-- 如果直接使用”<cache>“标签开启缓存,会与dao中的冲突,服务启动失败。-->
<cache-ref namespace="com.zsc.mapper.RoleMapper"/>
<resultMap id="roleMap" type="com.zsc.entity.Role">
<id property="id" column="id" ></id>
<result property="name" column="name"></result>
<!--userList-->
<collection property="userList" ofType="com.zsc.entity.User">
<id property="id" column="userId"></id>
<!--记住一点,<collection>标签里的column并不是与数据库表字段对应,而且自己起的一个别名,
将数据库查询到的数据映射或者说是赋值给这个别名,再油这个别名映射到property对应的实体类属性上-->
<result property="name" column="name"></result>
<result property="password" column="password"></result>
</collection>
</resultMap>
</mapper>
<?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.zsc.mapper.UserMapper">
<!-- mapper 使用缓存的方式是使用”<cache-ref>“标签引用dao的命名空间,因为dao中已经开启缓存,-->
<!-- 这样与dao中的缓存保持一直,如果mapper文件不引用缓存,-->
<!-- 执行update方法时,不会清除dao的缓存,导致数据库数据与缓存数据不一致。-->
<!-- 如果直接使用”<cache>“标签开启缓存,会与dao中的冲突,服务启动失败。-->
<!-- <cache type="com.zsc.cache.MybatisPlusRedisCache"></cache>-->
<cache-ref namespace="com.zsc.mapper.UserMapper"/>
<resultMap id="userMap" type="com.zsc.entity.User">
<id property="id" column="id" ></id>
<result property="name" column="username"></result>
<result property="password" column="password"></result>
<!--roleList-->
<collection property="roleList" ofType="com.zsc.entity.Role">
<id property="id" column="roleId"></id>
<!--记住一点,<collection>标签里的column并不是与数据库表字段对应,而且自己起的一个别名,
将数据库查询到的数据映射或者说是赋值给这个别名,再油这个别名映射到property对应的实体类属性上-->
<result property="name" column="username"></result>
</collection>
</resultMap>
<select id="findUserById" resultType="java.lang.String">
select username from user where id = #{id}
</select>
<select id="findUserAllRoleById" resultMap="userMap">
select u.*, r.id as roleId,r.name from user u,role r,user_role ur
where u.id = ur.uid
and r.id = ur.rid
and ur.uid = #{id}
</select>
</mapper>
八、测试
在测试之前查看自己的redis,此时还没有查询的相关缓存
测试查询
@Test
public void findTest(){
log.info("*****************************************************************************************");
User u1 = new User();
u1.setId(1L);
User user1 = userMapper.selectById(u1);
log.info("用户1=[{}]",user1);
log.info("*****************************************************************************************");
User u3 = new User();
u3.setId(3L);
User user3 = userMapper.selectById(u3);
log.info("用户3=[{}]",user3);
log.info("*****************************************************************************************");
User u4 = new User();
u4.setId(4L);
User user4 = userMapper.selectById(u4);
log.info("用户4=[{}]",user4);
log.info("*****************************************************************************************");
User u5 = new User();
u5.setId(5L);
User user5 = userMapper.selectById(u5);
log.info("用户5=[{}]",user5);
log.info("*****************************************************************************************");
}
结果如下
所有的查询都先去获取缓存,发现缓存没有后才去查询数据库,并且将查询到数据存入缓存,重新执行结果,因为是分布式缓存,所以项目关闭后缓存不会消失,存在redis服务器里面,结果如下
数据全部是从缓存里面获取的,查看redis缓存发现有一个UserMapper的键存在,这个即是存储的缓存数据
执行增删改任一操作,会清空缓存,这里以执行更新操作为例
@Test
public void updateTest(){
User user = new User();
user.setId(7L);
user.setName("张三");
user.setPassword("123456");
int update = userMapper.updateById(user);
log.info("update = {}",update);
}
运行结果,缓存清空
reids里面的键也不见了
九、注意
缓存只适用于单表的情况下,如果是多表联立,建议不要用缓存,因为上面的缓存只会触发了一对应表的增删改后会清空缓存,多表的话一张表更新后,会影响另一张表的数据,但缓存只清空了执行了更新操作的表,另一张表不会清空缓存,会造成数据的不一致。
十、总结
- 自定义缓存类MybatisRedisCache,如果不设置自定义缓存类,则默认mybatis自定义的本地缓存(jvm)类,自定义的缓存类属于分布式缓存
- mapper接口添加
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)注解,
映射到自定义缓存类 - 如果用到mapper的xml文件(自定义sql),xml文件必须使用
<cache-ref namespace="com.zsc.mapper.UserMapper"/>
找到自己的对应的mapper
ps
- 如果用mybatis的话,直接用xml配置缓存,使用
<cache type="com.zsc.cache.MybatisPlusRedisCache"></cache>
注解映射到自定义缓存类即可使用了 - mybatis可以开启共享缓存用于解决多表联立缓存的问题,即将多张表用同一块缓存,主要任一关联表执行了增删改操作就会清空改缓存块,解决数据不一致问题,主要使用
<cache-ref namespace="com.zsc.mapper.UserMapper"/>
注解,直接引用别的接口的缓存块,比如:
UserMapper.xml使用了<cache type="com.zsc.cache.MybatisPlusRedisCache"></cache>
开启缓存,RoleMapper.xml则使用<cache-ref namespace="com.zsc.mapper.UserMapper"/>
也引用UserMapper.xml的缓存块,以达到共享缓存的目的。不过还是建议多表联立不要使用缓存