本文目录导读:

- 项目基础结构
- 实体类
- Mapper接口和XML
- Mapper XML文件
- 业务层演示缓存
- MyBatis配置文件
- Redis二级缓存实现(生产环境)
- 使用Redis缓存的Mapper
- 缓存测试类
- 核心配置文件
- 关键要点说明
我将为您提供一个完整的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项目中。