菜鸟的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里面的键也不见了
在这里插入图片描述

九、注意

缓存只适用于单表的情况下,如果是多表联立,建议不要用缓存,因为上面的缓存只会触发了一对应表的增删改后会清空缓存,多表的话一张表更新后,会影响另一张表的数据,但缓存只清空了执行了更新操作的表,另一张表不会清空缓存,会造成数据的不一致。

十、总结

  1. 自定义缓存类MybatisRedisCache,如果不设置自定义缓存类,则默认mybatis自定义的本地缓存(jvm)类,自定义的缓存类属于分布式缓存
  2. mapper接口添加@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)注解,映射到自定义缓存类
  3. 如果用到mapper的xml文件(自定义sql),xml文件必须使用<cache-ref namespace="com.zsc.mapper.UserMapper"/>找到自己的对应的mapper

ps

  1. 如果用mybatis的话,直接用xml配置缓存,使用<cache type="com.zsc.cache.MybatisPlusRedisCache"></cache>注解映射到自定义缓存类即可使用了
  2. 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的缓存块,以达到共享缓存的目的。不过还是建议多表联立不要使用缓存
  • 5
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值