提问:怎么使用注解开启二级缓存,注解应该放在那里?
在 MyBatis 中使用注解开启二级缓存,需要在 Mapper 接口上添加 @CacheNamespace 注解。让我详细解释:
- 基本用法:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace // 开启二级缓存
public interface UserMapper {
// Mapper 方法
}
- 完整示例:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace(
eviction = LruCache.class, // 缓存淘汰策略
flushInterval = 60000, // 刷新间隔(毫秒)
size = 1024, // 缓存大小
readWrite = true, // 是否读写
blocking = false // 是否阻塞
)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id);
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
int insertUser(User user);
}
- 实体类配置:
java
Apply to TestJwt.java
// 实体类需要实现 Serializable 接口
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
// getter 和 setter
}
- 缓存配置选项:
java
Apply to TestJwt.java
@CacheNamespace(
// 缓存实现类
implementation = PerpetualCache.class,
// 缓存淘汰策略
eviction = LruCache.class,
// 刷新间隔(毫秒)
flushInterval = 60000,
// 缓存大小
size = 1024,
// 是否读写
readWrite = true,
// 是否阻塞
blocking = false
)
- 缓存注解使用场景:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace
public interface UserMapper {
// 查询方法会被缓存
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id);
// 更新方法会清除缓存
@Update("UPDATE user SET name = #{name} WHERE id = #{id}")
int updateUser(User user);
// 删除方法会清除缓存
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(Integer id);
}
- 缓存控制:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace
public interface UserMapper {
// 使用 @Options 控制单个方法的缓存行为
@Select("SELECT * FROM user WHERE id = #{id}")
@Options(useCache = true) // 使用缓存
User getUserById(Integer id);
// 禁用缓存
@Select("SELECT * FROM user WHERE id = #{id}")
@Options(useCache = false) // 不使用缓存
User getUserByIdNoCache(Integer id);
}
- 最佳实践:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace(
eviction = LruCache.class,
flushInterval = 60000,
size = 1024,
readWrite = true
)
public interface UserMapper {
// 查询方法
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id);
// 更新方法
@Update("UPDATE user SET name = #{name} WHERE id = #{id}")
int updateUser(User user);
// 删除方法
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(Integer id);
}
- 注意事项:
- 实体类必须实现 Serializable 接口
- 缓存会占用内存,注意设置合适的缓存大小
- 更新操作会自动清除缓存
- 注意缓存一致性问题
- 常见问题:
java
Apply to TestJwt.java
// 错误:实体类没有实现 Serializable
public class User { // 错误
private Integer id;
private String name;
}
// 正确:实体类实现 Serializable
public class User implements Serializable { // 正确
private Integer id;
private String name;
}
- 缓存配置建议:
- 对于频繁查询但不常更新的数据,可以开启缓存
- 对于实时性要求高的数据,可以禁用缓存
- 根据数据量大小设置合适的缓存大小
- 根据业务需求设置合适的刷新间隔
总结:
- 在 Mapper 接口上使用 @CacheNamespace 注解开启二级缓存
- 实体类必须实现 Serializable 接口
- 可以通过注解参数配置缓存行为
- 注意缓存一致性和性能问题
- 根据业务需求选择合适的缓存配置
开启二级缓存的时候,实体类必须实现Serializable吗?
是的,在 MyBatis 中开启二级缓存时,实体类必须实现 Serializable 接口。这是因为:
- 为什么需要实现 Serializable:
- 二级缓存会将对象序列化后存储
- 序列化是对象持久化的过程
- 反序列化是对象恢复的过程
- 没有实现 Serializable 会导致序列化失败
- 正确的实体类定义:
java
Apply to TestJwt.java
// 正确:实现 Serializable 接口
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
// getter 和 setter
}
// 错误:没有实现 Serializable 接口
public class User { // 会导致序列化异常
private Integer id;
private String name;
private Integer age;
}
- 序列化版本号:
java
Apply to TestJwt.java
public class User implements Serializable {
// 建议添加序列化版本号
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Integer age;
}
- 常见错误:
java
Apply to TestJwt.java
// 错误:没有实现 Serializable
@Mapper
@CacheNamespace
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id); // 会抛出序列化异常
}
// 正确:实现 Serializable
@Mapper
@CacheNamespace
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id); // 正常工作
}
- 注意事项:
- 所有被缓存的实体类都必须实现 Serializable
- 实体类中的其他对象属性也需要实现 Serializable
- 建议添加序列化版本号
- 注意序列化性能影响
总结:
- 开启二级缓存时,实体类必须实现 Serializable 接口
- 这是 MyBatis 二级缓存的要求
- 没有实现 Serializable 会导致序列化异常
- 建议添加序列化版本号
开启二级缓存有什么好处
开启 MyBatis 二级缓存有以下几个主要好处:
- 提高查询性能:
- 减少数据库访问次数
- 降低数据库压力
- 提高响应速度
- 减少网络开销
- 减少数据库负载:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace
public interface UserMapper {
// 第一次查询会访问数据库
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id);
// 第二次查询相同数据会直接从缓存获取
// 不会访问数据库
}
- 适用场景:
- 频繁查询但不常更新的数据
- 数据量较小的表
- 对实时性要求不高的数据
- 多用户共享的数据
- 性能提升示例:
java
Apply to TestJwt.java
// 不使用缓存
for (int i = 0; i < 1000; i++) {
userMapper.getUserById(1); // 每次都会查询数据库
}
// 使用缓存
for (int i = 0; i < 1000; i++) {
userMapper.getUserById(1); // 只有第一次查询数据库,后续从缓存获取
}
- 内存使用优化:
java
Apply to TestJwt.java
@CacheNamespace(
size = 1024, // 控制缓存大小
eviction = LruCache.class // 使用 LRU 淘汰策略
)
public interface UserMapper {
// 缓存配置
}
- 注意事项:
- 缓存会占用内存
- 需要注意缓存一致性
- 不适合频繁更新的数据
- 实体类需要实现 Serializable
- 最佳实践:
java
Apply to TestJwt.java
@Mapper
@CacheNamespace(
eviction = LruCache.class,
flushInterval = 60000, // 定期刷新缓存
size = 1024, // 控制缓存大小
readWrite = true
)
public interface UserMapper {
// 适合缓存的方法
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id);
// 不适合缓存的方法
@Select("SELECT * FROM user WHERE update_time > #{time}")
List<User> getRecentUsers(Date time);
}
- 使用建议:
- 根据业务需求选择合适的缓存策略
- 设置合适的缓存大小
- 定期清理缓存
- 注意缓存一致性
- 性能对比:
text
Apply to TestJwt.java
不使用缓存:
- 每次查询都访问数据库
- 响应时间较长
- 数据库压力大
使用缓存:
- 首次查询访问数据库
- 后续查询从缓存获取
- 响应时间短
- 数据库压力小
- 总结好处:
- 提高查询性能
- 减少数据库访问
- 降低数据库压力
- 提高响应速度
- 减少网络开销
- 优化系统资源使用
- 使用场景示例:
java
Apply to TestJwt.java
// 适合缓存的场景
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserById(Integer id); // 用户基本信息
// 不适合缓存的场景
@Select("SELECT * FROM order WHERE user_id = #{userId}")
List<Order> getUserOrders(Integer userId); // 订单信息经常变化
总结:
- 二级缓存能显著提高查询性能
- 减少数据库访问和压力
- 提高系统响应速度
- 优化资源使用
- 适合特定业务场景
- 需要注意缓存管理
最后我们来测试一下实体类如果没有实现Serializable接口会怎么样?会不会导致序列化异常?
可以看到没有实现Serializable接口导致出现io错误,再来试试实现Serializable接口会不会解决?
可以看到已经不会报错了,看来不实现Serializable接口确实会导致序列化异常。
那么我们再来看看二级缓存对于查询有没有提升?
我们可以看到我第一次查询速度是614ms
再重复查询试试
可以看到确实有所提升