• 使用Redis替换MyBatis的二级缓存


    使用Redis替换MyBatis的二级缓存

    Mybatis二级缓存的缺陷

    • 服务器重启后,缓存会丢失。

    • Mybatis运行在tomcat容器中,缓存在tomcat容器中。mybatis的二级缓存在集群或者分布式条件下无法共享。

    image-20210314162625800

    MyBatis的默认缓存是由PerpetualCache类来实现的,我们可以模仿他,实现一个,以redis作为缓存的自定义缓存类;

    image-20210314165500843

    第一步:先搭建一个SpringBoot项目

    image-20210314163830024

    项目结构

    image-20210314165847612

    第二步:连接数据库,并逆向生成

    image-20210314164110204

    image-20210314164126215

    逆向生成mapper接口,参考:https://www.cnblogs.com/MLYR/p/14529663.html

    image-20210314164345535

    第三步:编写Controller

    package com.mlyr.controller;
    
    import com.mlyr.mapper.EmpMapper;
    import com.mlyr.pojo.Emp;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * @Description:
     * @Author: 陌路邑人
     * @CreateTime: 2021/3/13
     * @Company:
     */
    @RestController
    @Slf4j
    public class EmpController {
        @Resource
        private EmpMapper empMapper;
    
        //根据id查询
        @GetMapping("emp/{empno}")
        public Emp findById(@PathVariable Integer empno){
            Emp emp = empMapper.selectByPrimaryKey(empno);
            log.debug("EMP==="+emp);
            return emp;
        }
        //查询所有
        @GetMapping("emp")
        public  List<Emp> findAll(){
            List<Emp> empList = empMapper.selectByExample(null);
            log.debug("empList==="+empList);
            return empList;
        }
        //增加
        @PostMapping("emp")
        public  String  addEmp(Emp emp){
            int f = empMapper.insertSelective(emp);
            log.debug("添加==="+f);
            return "添加"+f;
        }
        //修改
        @PutMapping("emp")
        public  String  update(Emp emp){
            int f = empMapper.updateByPrimaryKeySelective(emp);
            log.debug("修改==="+f);
            return "修改"+f;
        }
        //删除
        @DeleteMapping("emp/{empno}")
        public  String  deleteByEmpno(@PathVariable Integer empno){
            int f = empMapper.deleteByPrimaryKey(empno);
            log.debug("删除==="+f);
            return "删除"+f;
        }
    }
    

    第四步:开启Mybatis二级缓存

    application.yml

    spring:
      #配置spring连接mybatis数据库
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8
        username: root
        password: root
      #配置缓存
      cache:
        type: redis
    #配置日志输出
    logging:
      level:
        root: error
        com:
          mlyr: debug
    

    在EmpMapper.xml中添加

    <!--开启二级缓存,指定缓存为自定义的缓存类-->
    <cache type="com.mlyr.tools.MyCache" />
    

    第五步:编写自定义织入类

    • 就是为了获取Spring中封装的RedisTemplate
    package com.mlyr.autowird;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    /**
     * @Description:
     * @Author: 陌路邑人
     * @CreateTime: 2021/3/13
     * @Company:    自定义织入类,为了获取RedisTemplate
     */
    //
    @Component
    public class MyContextAware implements ApplicationContextAware {
    
        private static ApplicationContext act;
        //获取一个Spring容器对象
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            act = applicationContext;
        }
        //创建一个静态方法,按照name获取容器中的Bean对象
        public static Object getBean(String name){
            return act.getBean(name);
        }
    }
    

    第六步:实现自定义缓存类

    package com.mlyr.tools;
    import com.mlyr.autowird.MyContextAware;
    import org.apache.ibatis.cache.Cache;
    import org.apache.ibatis.cache.impl.PerpetualCache;
    import org.springframework.data.redis.core.RedisTemplate;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    /**
     * @Description:
     * @Author: 陌路邑人
     * @CreateTime: 2021/3/13
     * @Company:
     */
    //作为MyBatis的自定义缓存,不能让Spring容器托管
    public class MyCache implements Cache {
        //但是又要使用RedisTemplate用来操作redis
        private RedisTemplate redisTemplate;
        private String id;
        private ReentrantReadWriteLock lock;//读写锁,每次读写前要加锁,防止脏读
        //只能通过织入类获取RedisTemplate
        public MyCache(String id) {
            this.id = id;
            this.redisTemplate = (RedisTemplate) MyContextAware.getBean("redisTemplate");
            lock = new ReentrantReadWriteLock();
        }
        //缓存标识
        @Override
        public String getId() {
            return id;
        }
        //放入缓存一次,它会调用一次clear(),清除所有的缓存
        // 将数据库中查出来的数据,放入redis数据库中
        @Override
        public void putObject(Object key, Object value) {
            try {
                lock.writeLock().lock();
                System.out.println("放入缓存===【"+key+"】【"+value+"】");
                redisTemplate.opsForHash().put(id,key,value);
                 int num = 60 + new Random().nextInt(60);
                //设置过期时间,只能针对于namespace命名空间进行过期时间
                redisTemplate.expire(id,num, TimeUnit.SECONDS);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.writeLock().unlock();
            }
        }
        //获取缓存中的值,根据key
        @Override
        public Object getObject(Object key) {
            try {
                lock.readLock().lock();
                System.out.println("获取缓存中的值,根据key==="+key);
                return redisTemplate.opsForHash().get(id,key);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
            return null;
        }
        //删除key的缓存
        @Override
        public Object removeObject(Object key) {
            try {
                lock.writeLock().lock();
                System.out.println("删除key的缓存"+key);
                return redisTemplate.opsForHash().delete(id,key);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
            return null;
        }
        //清空id下的所有缓存
        @Override
        public void clear() {
            try {
                lock.writeLock().lock();
                System.out.println("缓存大小==="+getSize());
                redisTemplate.delete(id);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        }
        //获取缓存的大小
        @Override
        public int getSize() {
            return new Integer(redisTemplate.opsForHash().size(id).toString());
        }
    }
    

    测试

    当第一次访问所有的emp

    • image-20210314170158797
    • image-20210314170616684
    • image-20210314171002462

    再次访问

    • image-20210314170918594

    在进行添加一个emp时

    • image-20210314171121227
    • image-20210314171544479
    • image-20210314171616067

    再次查询的时候,就会又在数据库中直接查询了

    在进行删除、修改的操作时,和上面一样,都会清空缓存,防止脏读

    注意点

    • 当有多表联查的时候,比如上面的emp与dept,在进行查询所有的emp联查dept后,在进行修改dept数据,缓存中的emp并没有改变,这就产生了脏读!!!

    image-20210314185209890

    image-20210314185227053

    解决多表之间的,另一张表改变,缓存脏读

    • 首先在DeptMapper.xml中添加<cache></cache>
    • 然后在EmpMapper.xml中引入dept的namespace<cache-ref namespace="com.mlyr.mapper.DeptMapper"></cache-ref>

    就是相当于两个xml的缓存合在一起,一边的改变能够影响另一边的改变

    不经风雨,怎见彩虹?
  • 相关阅读:
    iOS testFlight的使用
    Java读书笔记
    Vim编辑器
    seleniumRC测试用例——登录
    自动化测试工具——selenium
    《Oracle从入门到精通》
    WEB服务器相关知识摘录
    oracle表空间与分区表
    Linux 常用命令
    Linux 常见问题
  • 原文地址:https://www.cnblogs.com/MLYR/p/14533222.html
Copyright © 2020-2023  润新知