一、开启二级缓存

cache-enabled: true

# mybatis-plus相关配置
mybatis-plus:
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
  mapper-locations: classpath:mapper/*.xml
  # 以下配置均有默认值,可以不设置
  global-config:
    db-config:
      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
      id-type: auto
      #字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"
      field-strategy: NOT_EMPTY
      #数据库类型
      db-type: ORACLE
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 一级缓存配置 一级缓存是本地或者说局部缓存,它不能被关闭,只能配置缓存范围。SESSION 或者 STATEMENT。
    local-cache-scope: session
    # 二级缓存总开关
    cache-enabled: true

二、添加缓存注解

@EnableCaching

@SpringBootApplication
@MapperScan({"com.qiang.mybaties.plus.test.cache.dao"})
@EnableCaching
public class MybatiesPlusCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatiesPlusCacheApplication.class, args);
    }
}

三、实体类实现接口

Serializable

@Data
public class Account extends Model<Account> implements Serializable{

    private Integer id;

    private Integer balance;

    private Integer freezeMoney;

    /**
     * 获取主键值
     *
     * @return 主键值
     */
    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

四、配置Redis模板

RedisConfig

@Configuration
public class RedisConfig {

    /**
     * 配置RedisTemplate
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory, Jackson2JsonRedisSerializer serializer) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // redis连接工厂
        template.setConnectionFactory(factory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // redis.key序列化器
        template.setKeySerializer(stringRedisSerializer);
        // redis.value序列化器
        template.setValueSerializer(serializer);
        // redis.hash.key序列化器
        template.setHashKeySerializer(stringRedisSerializer);
        // redis.hash.value序列化器
        template.setHashValueSerializer(serializer);
        // 调用其他初始化逻辑
        template.afterPropertiesSet();
        // 这里设置redis事务一致
        template.setEnableTransactionSupport(true);
        return template;
    }

    /**
     * 配置redis Json序列化器
     *
     * @return
     */
    @Bean
    public Jackson2JsonRedisSerializer redisJsonSerializer() {
        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        return serializer;
    }
}

五、配置Redis缓存

RedisCache

public final class RedisCache implements Cache {

    /**
     * 日志
     */
    private static final Logger logger = LogManager.getLogger(RedisCache.class);

    /**
     * 读写锁
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * ID,获取缓存对象的唯一标识
     */
    private String id;

    /**
     * redisTemplate
     */
    private static RedisTemplate redisTemplate;

    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("缓存实例需要一个id!");
        } else {
            logger.info("开启Redis缓存: id = {}", id);
            this.id = id;
        }
    }

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

    @Override
    public int getSize() {
        try {
            if (redisTemplate == null) {
                redisTemplate = getRedisTemplate();
            }
            Long size = redisTemplate.opsForHash().size(this.id.toString());
            logger.info("Redis缓存大小: id = {}, size = {}", id, size);
            return size.intValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public void putObject(final Object key, final Object value) {
        try {
            if (redisTemplate == null) {
                redisTemplate = getRedisTemplate();
            }
            logger.info("设置Redis缓存: id = {}, key = {}, value = {}", id, key, value);
            redisTemplate.opsForHash().put(this.id.toString(), key.toString(), value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getObject(final Object key) {
        try {
            if (redisTemplate == null) {
                redisTemplate = getRedisTemplate();
            }
            Object hashVal = redisTemplate.opsForHash().get(this.id.toString(), key.toString());
            logger.info("获取Redis缓存: id = {}, key = {}, hashVal = {}", id, key, hashVal);
            return hashVal;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public Object removeObject(final Object key) {
        try {
            if (redisTemplate == null) {
                redisTemplate = getRedisTemplate();
            }
            redisTemplate.opsForHash().delete(this.id.toString(), key.toString());
            logger.info("移除Redis缓存: id = {}, key = {}", id, key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void clear() {
        try {
            if (redisTemplate == null) {
                redisTemplate = getRedisTemplate();
            }
            redisTemplate.delete(this.id.toString());
            logger.info("清空Redis缓存: id = {}", id);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    @Override
    public String toString() {
        return "RedisCache {" + this.id + "}";
    }

    /**
     * 由于启动期间注入失败,只能运行期间注入
     *
     * @return
     */
    public RedisTemplate getRedisTemplate() {
        redisTemplate = (RedisTemplate<String, Object>) SpringUtil.getBean("redisTemplate");
        return redisTemplate;
    }

}

SpringUtil

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    public static Object getBean(String name) {
        return 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);
    }
}

六、在Dao层使用

AccountDao,此时,MybatiesPlus自带的方法缓存已经生效。但是Xml文件里的方法还未生效,接着下一步

// 使用缓存
@CacheNamespace(implementation= RedisCache.class,eviction=RedisCache.class)
public interface AccountDao extends BaseMapper<Account> {

    /**
     *  查询去重的Balance
     * @return
     */
    List<Account> selectDistinctBalance();

    /**
     * 通过ID查询单条数据
     *
     * @param id 主键
     * @return 实例对象
     */
    Account queryById(Integer id);

    /**
     * 查询指定行数据
     *
     * @param offset 查询起始位置
     * @param limit  查询条数
     * @return 对象列表
     */
    List<Account> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);


    /**
     * 通过实体作为筛选条件查询
     *
     * @param account 实例对象
     * @return 对象列表
     */
    List<Account> queryAll(Account account);

    /**
     * 新增数据
     *
     * @param account 实例对象
     * @return 影响行数
     */
    int insertOne(Account account);

    /**
     * 修改数据
     *
     * @param account 实例对象
     * @return 影响行数
     */
    int update(Account account);

    /**
     * 通过主键删除数据
     *
     * @param id 主键
     * @return 影响行数
     */
    int deleteById(Integer id);
}

七、在Xml文件使用

AccountDao.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.qiang.mybaties.plus.test.cache.dao.AccountDao">

    <resultMap id="AccountMap" type="com.qiang.mybaties.plus.test.cache.entity.Account">
        <result property="id" column="ID" jdbcType="INTEGER"/>
        <result property="balance" column="BALANCE" jdbcType="INTEGER"/>
        <result property="freezeMoney" column="FREEZE_MONEY" jdbcType="INTEGER"/>
    </resultMap>
	<!-- 使用缓存 -->
    <cache-ref namespace="com.qiang.mybaties.plus.test.cache.dao.AccountDao"/>

    <sql id="Base_Column_List">
        ID, BALANCE, FREEZE_MONEY
    </sql>
    <select id="selectDistinctBalance" resultType="com.qiang.mybaties.plus.test.cache.entity.Account">
        SELECT DISTINCT
            ( BALANCE ),
            ID,
            FREEZE_MONEY
        FROM
            ACCOUNT
    </select>

    <!--查询单个-->
    <select id="queryById" resultMap="AccountMap">
        select
          ID, BALANCE, FREEZE_MONEY
        from TEST.ACCOUNT
        where ID = #{id}
    </select>

    <!--查询指定行数据-->
    <select id="queryAllByLimit" resultMap="AccountMap">
        select
          ID, BALANCE, FREEZE_MONEY
        from TEST.ACCOUNT
        limit #{offset}, #{limit}
    </select>

    <!--通过实体作为筛选条件查询-->
    <select id="queryAll" resultMap="AccountMap">
        select
        ID, BALANCE, FREEZE_MONEY
        from TEST.ACCOUNT
        <where>
            <if test="id != null">
                and ID = #{id}
            </if>
            <if test="balance != null">
                and BALANCE = #{balance}
            </if>
            <if test="freezeMoney != null">
                and FREEZE_MONEY = #{freezeMoney}
            </if>
        </where>
    </select>

    <!--新增所有列-->
    <insert id="insertOne" keyProperty="id" useGeneratedKeys="true">
        insert into TEST.ACCOUNT(BALANCE, FREEZE_MONEY)
        values (#{balance}, #{freezeMoney})
    </insert>

    <!--通过主键修改数据-->
    <update id="update">
        update TEST.ACCOUNT
        <set>
            <if test="balance != null">
                BALANCE = #{balance},
            </if>
            <if test="freezeMoney != null">
                FREEZE_MONEY = #{freezeMoney},
            </if>
        </set>
        where ID = #{id}
    </update>

    <!--通过主键删除-->
    <delete id="deleteById">
        delete from TEST.ACCOUNT where ID = #{id}
    </delete>

</mapper>

八、验证是否成功

8.1 作用域为自带方法

测试代码

	@Test
    public void testRedisCacheCommon(){
        // 测试 mybaties-plus 中间件为redis 作用域为 mybaties自带方法 的二级缓存
        List<Account> list1 = accountService.list();
        System.out.println(list1);
        List<Account> list2 = accountService.list();
        System.out.println(list2);
    }

结果看到第二次查询使用了缓存

image-20201118153805723

Redis也成功添加缓存

image-20201118153847576

缓存更新

	@Test
    public void testRedisCacheCommon(){
        // 测试 mybaties-plus 中间件为redis 作用域为 mybaties自带方法 的二级缓存
        List<Account> list1 = accountService.list();
        System.out.println(list1);
        boolean b = accountService.removeById(2);
        System.out.println(b);
        List<Account> list2 = accountService.list();
        System.out.println(list2);
    }

结果看到查询了两次数据库,当缓存作用在同一个CacheNamespace时候,发生增删改操作,则缓存更新

image-20201118154507531

8.2 作用域为Xml文件

测试代码

	@Test
    public void testRedisCacheByXml() {
        // 测试 mybaties-plus 中间件为redis 作用域为 xml文件 的二级缓存
        Account account = new Account();
        account.setId(1);
        List<Account> accounts1 = accountService.queryAll(account);
        System.out.println(accounts1);
        List<Account> accounts2 = accountService.queryAll(account);
        System.out.println(accounts2);
    }

结果看到第二次查询使用了缓存

image-20201204144943390

Redis也成功添加缓存

image-20201204145100289

缓存更新

	@Test
    public void testRedisCacheByXml() {
        // 测试 mybaties-plus 中间件为redis 作用域为 xml文件 的二级缓存
        Account account = new Account();
        account.setId(1);
        List<Account> accounts1 = accountService.queryAll(account);
        System.out.println(accounts1);
        int i = accountService.deleteById(1);
        System.out.println(i);
        List<Account> accounts2 = accountService.queryAll(account);
        System.out.println(accounts2);
    }

结果看到查询了两次数据库,当缓存作用在同一个CacheNamespace时候,发生增删改操作,则缓存更新

image-20201204145315679