mybatis拦截器中,通常添加两个query的签名方法,如下:
java
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
第一个,表示不支持缓存的query。
第二个,表示支持缓存的query。
a.某些数据变化不频繁,但查询非常频繁。缓存可以减少数据库查询次数,提高响应速度。
b.在分页查询中,缓存可以显著提高性能,尤其是当用户频繁浏览不同页面时。
c.对于复杂查询,生成的 SQL 可能涉及多个表的联接,执行时间较长。缓存可以显著减少这种查询的执行次数。
具体区别
-
访问频率和实时性:
- 不需要缓存:每次查询都直接访问数据库,适用于数据变化频繁或需要最新数据的场景。
- 需要缓存:查询结果可以被缓存,适用于数据变化不频繁但查询频繁的场景。
-
性能和资源使用:
- 不需要缓存:每次都访问数据库,可能会增加数据库负载。
- 需要缓存:利用缓存减少数据库访问次数,显著提高性能和响应速度。
有哪些方法,可以判断是否应用缓存:
1.通过sql语句标识:
java
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CacheInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
String sqlId = mappedStatement.getId();
// 假设我们有一个特定的SQL ID需要使用缓存
if ("com.example.mapper.UserMapper.selectUsers".equals(sqlId)) {
// 使用缓存逻辑
CacheKey cacheKey = ...; // 创建 CacheKey
BoundSql boundSql = ...; // 获取 BoundSql
// 执行带缓存的查询
return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} else {
// 直接访问数据库
return invocation.proceed();
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可选:设置一些属性
}
}
2.通过注解或配置
java
<select id="selectUsers" resultType="User" useCache="true">
SELECT * FROM users
</select>
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CacheInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
ResultHandler resultHandler = (ResultHandler) invocation.getArgs()[3];
Executor executor = (Executor) invocation.getTarget();
// 读取自定义属性
Boolean useCache = (Boolean) mappedStatement.getConfiguration().getVariables().get("useCache");
if (useCache != null && useCache) {
// 使用缓存逻辑
CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, mappedStatement.getBoundSql(parameter));
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} else {
// 直接访问数据库
return invocation.proceed();
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可选:设置一些属性
}
}
3.通过业务逻辑判断
java
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CacheInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
ResultHandler resultHandler = (ResultHandler) invocation.getArgs()[3];
Executor executor = (Executor) invocation.getTarget();
// 根据业务逻辑判断
if (shouldUseCache(mappedStatement, parameter)) {
// 使用缓存逻辑
CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, mappedStatement.getBoundSql(parameter));
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} else {
// 直接访问数据库
return invocation.proceed();
}
}
private boolean shouldUseCache(MappedStatement mappedStatement, Object parameter) {
// 根据业务逻辑判断是否使用缓存
// 示例:如果参数包含某个特定值,则使用缓存
if (parameter instanceof Map) {
Map<String, Object> paramMap = (Map<String, Object>) parameter;
return "useCache".equals(paramMap.get("cacheFlag"));
}
return false;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可选:设置一些属性
}
}
两个query方法的区别:
其实,mapper文件中useCache参数会用来构建MappedStatement对象。即ms.isUseCache()被用来判断是否走缓存逻辑。
或者 通过@Options注解方式配置useCache参数:
java
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("SELECT * FROM your_table WHERE your_conditions")
@Options(useCache = false)
List<YourResultType> selectRealTimeData();
}
java
public abstract class BaseExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 检查二级缓存
if (ms.isUseCache() && resultHandler == null) {
Cache cache = ms.getCache();
if (cache != null) {
// 从二级缓存中获取结果
@SuppressWarnings("unchecked")
List<E> list = (List<E>) cache.getObject(key);
if (list != null) {
return list;
}
}
}
// 如果缓存没有命中,执行数据库查询
List<E> result = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
// 将结果存入二级缓存
if (ms.isUseCache() && resultHandler == null && cache != null) {
cache.putObject(key, result);
}
return result;
}
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
}
其中,mybatis中,启用二级缓存的配置方式:
1.全局配置
java
<configuration>
<!-- 其他配置 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
2.映射文件配置
java
<mapper namespace="com.example.mapper.UserMapper">
<!-- 启用二级缓存 -->
<cache/>
<!-- 其他映射配置 -->
<select id="selectUsers" resultType="User">
SELECT * FROM users
</select>
</mapper>
3.自定义缓存配置
java
<mapper namespace="com.example.mapper.UserMapper">
<!-- 启用二级缓存,并设置自定义属性 -->
<cache
eviction="LRU" <!-- 缓存回收策略:LRU(默认) -->
flushInterval="60000" <!-- 刷新间隔,单位:毫秒 -->
size="512" <!-- 缓存大小 -->
readOnly="true"/> <!-- 只读缓存 -->
<!-- 其他映射配置 -->
<select id="selectUsers" resultType="User">
SELECT * FROM users
</select>
</mapper>
4.使用注解配置
java
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.cache.decorators.LruCache;
@CacheNamespace(
eviction = LruCache.class, // 缓存回收策略
flushInterval = 60000, // 刷新间隔,单位:毫秒
size = 512, // 缓存大小
readWrite = false // 是否可读写
)
public interface UserMapper {
@Select("SELECT * FROM users")
List<User> selectUsers();
}
提取有效信息:
java
private Object extractRouteParameterValue(Invocation invocation, Set<String> routerPropertyNames) {
Object routeValue = null;
try {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
Object parameterObject = args[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
Configuration configuration = mappedStatement.getConfiguration();
for (ParameterMapping parameterMapping : parameterMappings) {
String rawPropertyName = parameterMapping.getProperty();
String actualPropertyName = resolvePropertyName(rawPropertyName);
if (!routerPropertyNames.contains(actualPropertyName.toLowerCase())) {
continue;
}
// copy from DefaultParameterHandler.setParameter方法
// ParameterMode.IN 输入, OUT、INOUT 是在存储过程中使用,暂时无视
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
routeValue = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
routeValue = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
routeValue = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
routeValue = metaObject.getValue(propertyName);
}
}
if (routeValue != null && hasText(routeValue.toString())) {
return routeValue;
}
throw new DataSourceRoutingException(String.format("未检测到有效的数据库路由,请检测是否传入:(%s)", boundSql.getSql()));
}
} catch (DataSourceRoutingException dataSourceRoutingException) {
throw dataSourceRoutingException;
} catch (RuntimeException e) {
throw new DataSourceRoutingException(String.format("数据库路由解析异常, invocation method:{%s}, args:{%s}, routerPropertyNames:{%s}",
invocation.getMethod().toGenericString(), Arrays.toString(invocation.getArgs()), routerPropertyNames), e);
}
return null;
}