mybatis拦截器中,通常添加两个query的签名方法,如下:
- @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语句标识:
- @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.通过注解或配置
- SELECT * FROM users
-
-
-
- @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.通过业务逻辑判断
- @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
paramMap = (Map) 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参数:
- 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
selectRealTimeData(); - }
- public abstract class BaseExecutor implements Executor {
-
- @Override
- public
List 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
List 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
list = (List) cache.getObject(key); - if (list != null) {
- return list;
- }
- }
- }
- // 如果缓存没有命中,执行数据库查询
- List
result = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); - // 将结果存入二级缓存
- if (ms.isUseCache() && resultHandler == null && cache != null) {
- cache.putObject(key, result);
- }
- return result;
- }
-
- protected abstract
List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; - }
其中,mybatis中,启用二级缓存的配置方式:
1.全局配置
-
-
-
"cacheEnabled" value="true"/> -
2.映射文件配置
"com.example.mapper.UserMapper"> -
-
-
-
-
- SELECT * FROM users
-
3.自定义缓存配置
"com.example.mapper.UserMapper"> -
-
- eviction="LRU"
- flushInterval="60000"
- size="512"
- readOnly="true"/>
-
-
-
- SELECT * FROM users
-
4.使用注解配置
- 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
selectUsers(); - }
提取有效信息:
- private Object extractRouteParameterValue(Invocation invocation, Set
routerPropertyNames) { -
- Object routeValue = null;
- try {
- Object[] args = invocation.getArgs();
- MappedStatement mappedStatement = (MappedStatement) args[0];
- Object parameterObject = args[1];
- BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
- List
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;
- }