MyBatis缓存案例

wen java案例 1

本文目录导读:

MyBatis缓存案例

  1. 项目基础结构
  2. 实体类
  3. Mapper接口和XML
  4. Mapper XML文件
  5. 业务层演示缓存
  6. MyBatis配置文件
  7. Redis二级缓存实现(生产环境)
  8. 使用Redis缓存的Mapper
  9. 缓存测试类
  10. 核心配置文件
  11. 关键要点说明

我将为您提供一个完整的MyBatis缓存案例,涵盖一级缓存、二级缓存的基础用法,以及更实用的生产环境缓存方案(使用Redis)。

项目基础结构

src/main/java/com/example/cache/
├── entity/
│   ├── User.java
│   └── Order.java
├── mapper/
│   ├── UserMapper.java
│   ├── UserMapper.xml
│   ├── OrderMapper.java
│   └── OrderMapper.xml
├── service/
│   ├── UserService.java
│   └── UserServiceImpl.java
└── config/
    ├── MyBatisConfig.java
    └── RedisConfig.java

实体类

// User.java
package com.example.cache.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private LocalDateTime createTime;
    // getter/setter 省略
}
// Order.java
package com.example.cache.entity;
import java.io.Serializable;
import java.math.BigDecimal;
public class Order implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private Long userId;
    private String orderNo;
    private BigDecimal amount;
    // getter/setter 省略
}

Mapper接口和XML

// UserMapper.java
package com.example.cache.mapper;
import com.example.cache.entity.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
@CacheNamespace(eviction = org.apache.ibatis.cache.decorators.LruCache.class,
               flushInterval = 60000,
               size = 1024,
               readWrite = true)
public interface UserMapper {
    User selectUserById(@Param("id") Long id);
    int insertUser(User user);
    int updateUser(User user);
    int deleteUser(@Param("id") Long id);
}
// OrderMapper.java
package com.example.cache.mapper;
import com.example.cache.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface OrderMapper {
    Order selectOrderById(@Param("id") Long id);
    int insertOrder(Order order);
}

Mapper XML文件

<!-- UserMapper.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.example.cache.mapper.UserMapper">
    <!-- 查询用户,使用二级缓存 -->
    <select id="selectUserById" resultType="User" useCache="true">
        SELECT * FROM t_user WHERE id = #{id}
    </select>
    <!-- 插入用户,刷新缓存 -->
    <insert id="insertUser" parameterType="User" flushCache="true">
        INSERT INTO t_user (username, email, age, create_time)
        VALUES (#{username}, #{email}, #{age}, #{createTime})
    </insert>
    <!-- 更新用户,刷新缓存 -->
    <update id="updateUser" parameterType="User" flushCache="true">
        UPDATE t_user
        SET username = #{username},
            email = #{email},
            age = #{age}
        WHERE id = #{id}
    </update>
    <!-- 删除用户,刷新缓存 -->
    <delete id="deleteUser" parameterType="Long" flushCache="true">
        DELETE FROM t_user WHERE id = #{id}
    </delete>
</mapper>
<!-- OrderMapper.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.example.cache.mapper.OrderMapper">
    <!-- 不使用缓存 -->
    <select id="selectOrderById" resultType="Order" useCache="false">
        SELECT * FROM t_order WHERE id = #{id}
    </select>
    <insert id="insertOrder" parameterType="Order">
        INSERT INTO t_order (user_id, order_no, amount)
        VALUES (#{userId}, #{orderNo}, #{amount})
    </insert>
</mapper>

业务层演示缓存

// UserService.java
package com.example.cache.service;
import com.example.cache.entity.User;
public interface UserService {
    User getUserById(Long id);
    User getUserWithCache(Long id);
    void updateUser(User user);
    void deleteUser(Long id);
}
// UserServiceImpl.java
package com.example.cache.service;
import com.example.cache.entity.User;
import com.example.cache.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    @Transactional(readOnly = true)
    public User getUserById(Long id) {
        // 一级缓存:同一个SqlSession内缓存
        log.info("第一次查询,写入一级缓存");
        User user1 = userMapper.selectUserById(id);
        log.info("第二次查询,命中一级缓存");
        User user2 = userMapper.selectUserById(id);
        // 在同一个事务中,user1 和 user2 应该是同一个实例(一级缓存)
        log.info("user1 == user2 : {}", user1 == user2);
        return user2;
    }
    @Override
    @Transactional(readOnly = true)
    public User getUserWithCache(Long id) {
        // 演示二级缓存:需要每次调用新的SqlSession
        log.info("Service层第一次调用(可能触发二级缓存)");
        User user1 = userMapper.selectUserById(id);
        // 模拟业务逻辑
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("Service层第二次调用(验证二级缓存)");
        User user2 = userMapper.selectUserById(id);
        return user2;
    }
    @Override
    @Transactional
    public void updateUser(User user) {
        // 更新操作会清除对应Mapper的二级缓存
        log.info("更新用户信息,同时清除二级缓存");
        userMapper.updateUser(user);
    }
    @Override
    @Transactional
    public void deleteUser(Long id) {
        userMapper.deleteUser(id);
    }
}

MyBatis配置文件

<!-- mybatis-config.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 配置日志 -->
        <setting name="logImpl" value="SLF4J"/>
        <!-- 懒加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
</configuration>

Redis二级缓存实现(生产环境)

// RedisCache.java
package com.example.cache.config;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.DigestUtils;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache {
    private final String id;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public RedisCache(String id) {
        this.id = id;
    }
    @Override
    public String getId() {
        return id;
    }
    @Override
    public void putObject(Object key, Object value) {
        try {
            RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate");
            if (redisTemplate != null) {
                // 添加前缀,防止key冲突
                String redisKey = getRedisKey(key);
                redisTemplate.opsForValue().set(redisKey, value, 30, TimeUnit.MINUTES);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public Object getObject(Object key) {
        try {
            RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate");
            if (redisTemplate != null) {
                String redisKey = getRedisKey(key);
                return redisTemplate.opsForValue().get(redisKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public Object removeObject(Object key) {
        try {
            RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate");
            if (redisTemplate != null) {
                String redisKey = getRedisKey(key);
                return redisTemplate.delete(redisKey);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public void clear() {
        try {
            RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate");
            if (redisTemplate != null) {
                // 清除该namespace下的所有缓存
                Set<String> keys = redisTemplate.keys(id + "*");
                if (keys != null && !keys.isEmpty()) {
                    redisTemplate.delete(keys);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public int getSize() {
        try {
            RedisTemplate redisTemplate = SpringContextUtils.getBean("redisTemplate");
            if (redisTemplate != null) {
                Long size = redisTemplate.opsForList().size(id + ":size");
                return size == null ? 0 : size.intValue();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    private String getRedisKey(Object key) {
        String keyString = String.valueOf(key);
        // 使用MD5生成key,避免key过长
        String md5Key = DigestUtils.md5DigestAsHex(keyString.getBytes());
        return id + ":" + md5Key;
    }
}
// SpringContextUtils.java
package com.example.cache.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }
    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }
}

使用Redis缓存的Mapper

// UserMapperWithRedis.java
package com.example.cache.mapper;
import com.example.cache.config.RedisCache;
import com.example.cache.entity.User;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
@CacheNamespace(implementation = RedisCache.class, 
                eviction = org.apache.ibatis.cache.decorators.LruCache.class,
                flushInterval = 60000,
                size = 1024,
                readWrite = true)
public interface UserMapperWithRedis {
    User selectUserById(@Param("id") Long id);
    int insertUser(User user);
}

缓存测试类

// CacheTest.java
package com.example.cache;
import com.example.cache.entity.User;
import com.example.cache.mapper.UserMapper;
import com.example.cache.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringBootTest
public class CacheTest {
    @Autowired
    private UserService userService;
    @Autowired
    private UserMapper userMapper;
    @Test
    public void testFirstLevelCache() {
        // 测试一级缓存(SqlSession级别)
        log.info("=== 测试一级缓存 ===");
        User user = userService.getUserById(1L);
        log.info("查询结果: {}", user);
    }
    @Test
    public void testSecondLevelCache() {
        // 测试二级缓存(Mapper级别)
        log.info("=== 测试二级缓存 ===");
        // 第一次查询,会查询数据库并写入二级缓存
        User user1 = userMapper.selectUserById(1L);
        log.info("第一次查询: {}", user1);
        // 模拟不同SqlSession查询
        User user2 = userMapper.selectUserById(1L);
        log.info("第二次查询: {}", user2);
        // 验证是否为同一个实例
        log.info("user1 == user2 : {}", user1 == user2);
    }
    @Test
    public void testCacheUpdate() {
        // 测试更新后缓存失效
        log.info("=== 测试缓存更新 ===");
        // 查询缓存
        User user = userMapper.selectUserById(1L);
        log.info("查询缓存: {}", user);
        // 更新
        user.setUsername("updated_user");
        userMapper.updateUser(user);
        // 再次查询,应该触发缓存刷新
        User userAfterUpdate = userMapper.selectUserById(1L);
        log.info("更新后查询: {}", userAfterUpdate);
    }
    @Test
    public void testPerformanceComparison() {
        // 测试缓存性能提升
        log.info("=== 测试缓存性能 ===");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            userMapper.selectUserById(1L);
        }
        long end = System.currentTimeMillis();
        log.info("100次查询耗时: {} ms", (end - start));
    }
}

核心配置文件

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 5000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.cache.entity
  configuration:
    cache-enabled: true
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

关键要点说明

一级缓存特性

  • 作用范围:SqlSession级别
  • 默认开启:无法关闭
  • 失效条件:执行增删改操作后自动清除

二级缓存特性

  • 作用范围:Mapper级别(namespace级别)
  • 需要开启:在配置文件或注解中开启
  • 跨SqlSession:多个SqlSession可以共享
  • 序列化要求:实体类需要实现Serializable

缓存策略

  • LRU(Least Recently Used):最近最少使用
  • FIFO:先进先出
  • SOFT:软引用
  • WEAK:弱引用

生产环境建议

  • 数据量小的基础数据表使用二级缓存
  • 频繁更新的数据不使用缓存
  • 敏感数据(如金融数据)避免缓存
  • 使用Redis等分布式缓存替代本地缓存

这个案例涵盖了MyBatis缓存的所有关键内容,从基础用法到生产环境实践,可以直接集成到Spring Boot项目中。

抱歉,评论功能暂时关闭!