Mybatis中sql拦截增强-AOP+interceptor实现分页和排序

简介: 基于interceptor可以实现sql的完整打印,除了实现打印之外。其实还可以实现分页和排序,下面的分页和排序基于aop+mybatis的interceptor实现。其本质还是对mappedStament的boundSql进行增强。下面的项目来源于github,通过这个我们可以很好的学习mybatis中插件interceptor的使用。

mybatis的执行的大概过程:首先需要有sqlSessionFactroy,然后通过sqlSessionFactory拿到sqlSession,然后通过sqlSession调用getMapper拿到代理的接口,然后拿到代理的接口的信息mapperInterface,从而找到需要执行的具体的方法中的sql方法,,如果执行过,同时没有发生改变的话,则直接返回结果,否则会进行更新,同时如果执行过的话,会直接返回结果,此时会看到methodCache中有我们执行过的方法。

mybatis中,如果想进行sql的拦截,需要对其基于interceptor做拦截。因为mybatis的执行中,我们需要获取它的boundSql,而获取boundSql,需要获取MappedStatement,而MappedStatement可以在StatementHandler语句处理器中找到,因此可以在此基础上获取,通过反射的方式获取,此时可以用到插件模块中的invocation对象获取,然后对其进行增强。

基于interceptor可以实现sql的完整打印,除了实现打印之外。其实还可以实现分页和排序,下面的分页和排序基于aop+mybatis的interceptor实现。其本质还是对mappedStament的boundSql进行增强。

下面的项目来源于github,通过这个我们可以很好的学习mybatis中插件interceptor的使用。

首先定义分页元注解:

/*** 自定义分页注解*/@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documentedpublic@interfaceLimit {
/*** 当前页面* * @return*/intpage() default0;
/*** 每页显示数量* * @return*/intpageSize() default10;
}

定义排序元注解:

/*** 自定义排序注解*/@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documentedpublic@interfaceOrderBy {
/*** 表的别名*/StringtableAlias() default"";
/*** 排序字段*/StringorderColumn() default"";
/*** ASC/DESC 默认倒序* @return*/booleanisAsc() defaultfalse;
}

定义基础切面处理类:

/*** @ClassName BaseAspectAbstract* @Description 基础切面处理类,每一个Spring的切面都需要去继承此抽象类* @Date**/publicabstractclassBaseAspectAbstract {
privatestaticTreeMap<Integer, SQLLanguage>CONTAINERS=newTreeMap<>();
// 放入sql 切点、sql类型、sqlpublicvoidputSQL(JoinPointpoint, SQLEnumssqlEnums, SQLLanguagesqlLanguage) {
CONTAINERS.put(sqlEnums.getId(), sqlLanguage);
// 获取方法里的参数Objectparmas=point.getArgs()[0];
Mapmap= (Map)parmas;
map.put("SQL", getSQL());
    }
publicTreeMap<Integer, SQLLanguage>getSQL() {
returnCONTAINERS;
    }
}

进行分页切面:

@Component@Aspect@Order(3) //拼接sql时的顺序publicclassLimitAspectextendsBaseAspectAbstract {
@Pointcut("@annotation(com.mybatis.interceptor.annotation.Limit)")
publicvoidlimitCut() {}
@Before("limitCut()")
publicvoidlimit(JoinPointpoint) {
StringBuilderlimitBuilder=newStringBuilder(" LIMIT ");
MethodSignaturemethodSignature= (MethodSignature)point.getSignature();
// 获得对应注解Limitlimit=methodSignature.getMethod().getAnnotation(Limit.class);
if (!StringUtils.isEmpty(limit)) {
limitBuilder.append(limit.page()).append(",").append(limit.pageSize());
putSQL(point, LIMIT, newSQLLanguage(limitBuilder.toString()));
        }
    }
}

进行排序切面:

@Component@Aspect@Order(2)
publicclassOrderByAspectextendsBaseAspectAbstract {
// 切点:对注解中的特定注解进行拦截,进行增强@Pointcut("@annotation(com.mybatis.interceptor.annotation.OrderBy)")
publicvoidorderByCut() {}
// 执行切点操作,将其进行增强,放入排序@Before("orderByCut()")
publicvoidorderBy(JoinPointpoint) {
StringBuilderorderByBuilder=newStringBuilder(" ORDER BY ");
MethodSignaturemethodSignature= (MethodSignature)point.getSignature();
// 获得对应注解OrderByorderBy=methodSignature.getMethod().getAnnotation(OrderBy.class);
if (!StringUtils.isEmpty(orderBy)) {
Stringsort=orderBy.isAsc() ?" asc " : " desc";
orderByBuilder.append(orderBy.orderColumn()).append(sort);
putSQL(point, ORDERBY, newSQLLanguage(orderByBuilder.toString()));
        }
    }
}

枚举sql顺序:

/*** sql类型枚举*/publicenumSQLEnums {
/*** 数字越靠前 则拼接SQL语句越靠前执行,目前拼接顺序为* SELECT * FROM table GROUP BY ORDER BY xxx LIMIT 0, 10*/LIKE(1, "LIKE"), GROUPBY(2, "GROUP BY"), ORDERBY(3, "ORDER BY"), LIMIT(4, "LIMIT");
privateintid;
privateStringcondition;
SQLEnums(intid, Stringcondition) {
this.id=id;
this.condition=condition;
    }
publicintgetId() {
returnid;
    }
publicvoidsetId(intid) {
this.id=id;
    }
publicStringgetCondition() {
returncondition;
    }
publicvoidsetCondition(Stringcondition) {
this.condition=condition;
    }
}

执行sql增强的注解放置在serviceImpl里面:

@RequestMapping("/nba")
publicclassPlayController {
@AutowiredprivatePlayerServiceplayerService;
@RequestMapping("/player")
publicList<Player>getList(Map<String, Object>params) {
List<Player>players=playerService.getList(params);
returnplayers;
    }
}

执行sql增强:

@ServicepublicclassPlayerServiceImplimplementsPlayerService {
@AutowiredprivatePlayerMapperplayerMapper;
// 加入自定义注解,方便切点进行增强@OrderBy(orderColumn="height")
@Limit()
@OverridepublicList<Player>getList(Map<String, Object>params) {
returnplayerMapper.getList(params);
    }
}

执行到这里会执行动态代理,然后执行sql拦截。

执行sql拦截:

@Intercepts(@Signature(type=StatementHandler.class, method="prepare", args= {Connection.class, Integer.class}))
@ComponentpublicclassDataFilterInterceptorextendsAbstractSqlParserHandlerimplementsInterceptor {
// 拦截器@OverridepublicObjectintercept(Invocationinvocation) throwsThrowable {
StatementHandlerstatementHandler=PluginUtils.realTarget(invocation.getTarget());
MetaObjectmetaObject=SystemMetaObject.forObject(statementHandler);
// SQLLanguage 解析sqlParser(metaObject);
// 非查询操作MappedStatementmappedStatement= (MappedStatement)metaObject.getValue("delegate.mappedStatement");
if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
returninvocation.proceed();
        }
// 取出原始SQL 取出参数BoundSqlboundSql= (BoundSql)metaObject.getValue("delegate.boundSql");
StringdataSql=boundSql.getSql();
ObjectparamObj=boundSql.getParameterObject();
Mapmap= (Map)paramObj;
StringsqlLanguage=getSQLLanguage(map);
Stringsql=dataSql+sqlLanguage;
// 重写sqlmetaObject.setValue("delegate.boundSql.sql", sql);
returninvocation.proceed();
    }
// 插件@OverridepublicObjectplugin(Objecttarget) {
if (targetinstanceofStatementHandler) {
returnPlugin.wrap(target, this);
        }
returntarget;
    }
@OverridepublicvoidsetProperties(Propertiesproperties) {
    }
// 获取sql语句privateStringgetSQLLanguage(Map<String, Object>map) {
TreeMap<Integer, SQLLanguage>sqlMap= (TreeMap)map.get("SQL");
StringBuildersqlBuilder=newStringBuilder();
for (Map.EntrytreeMap : sqlMap.entrySet()) {
SQLLanguagesql= (SQLLanguage)treeMap.getValue();
if (null!=sql) {
sqlBuilder.append(sql);
            }
        }
returnsqlBuilder.toString();
    }
}


目录
相关文章
|
4月前
|
SQL Java 数据库连接
MyBatis分页
MyBatis作为Java持久层框架,需结合数据库特性或插件实现分页。分页分为物理分页(如MySQL的LIMIT)和逻辑分页(内存截取),推荐使用PageHelper插件自动注入分页语句,提升开发效率与性能。需注意索引优化、深分页问题及多表关联时的兼容性,结合业务场景选择合适方案。
187 4
|
5月前
|
SQL XML Java
通过MyBatis的XML配置实现灵活的动态SQL查询
总结而言,通过MyBatis的XML配置实现灵活的动态SQL查询,可以让开发者以声明式的方式构建SQL语句,既保证了SQL操作的灵活性,又简化了代码的复杂度。这种方式可以显著提高数据库操作的效率和代码的可维护性。
349 18
|
10月前
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
9月前
|
SQL Java 数据库连接
MyBatis动态SQL字符串空值判断,这个细节99%的程序员都踩过坑!
本文深入探讨了MyBatis动态SQL中字符串参数判空的常见问题。通过具体案例分析,对比了`name != null and name != &#39;&#39;`与`name != null and name != &#39; &#39;`两种写法的差异,指出后者可能引发逻辑混乱。为避免此类问题,建议在后端对参数进行预处理(如trim去空格),简化MyBatis判断逻辑,提升代码健壮性与可维护性。细节决定成败,严谨处理参数判空是写出高质量代码的关键。
1242 0
|
10月前
|
SQL Java 数据库连接
微服务——MyBatis分页
本文介绍了分页的多种实现方式,包括自带RowBounds分页、第三方插件PageHelper分页、SQL分页、数组分页及拦截器分页。其中,RowBounds是先查询全部结果再内存分页;PageHelper通过修改SQL动态添加分页关键字;SQL分页依赖数据库自身的分页功能如`LIMIT`;数组分页则是查询全量数据后用`subList`方法截取;拦截器分页则统一在SQL后添加分页语句。最后总结逻辑分页适合小数据量,但大数据量易内存溢出;物理分页虽小数据量效率较低,但更适合大数据场景,优先推荐使用。
143 0
|
5月前
|
SQL Java 数据库连接
SSM相关问题-1--#{}和${}有什么区别吗?--Mybatis都有哪些动态sql?能简述一下动 态sql的执行原理吗?--Spring支持的几种bean的作用域 Scope
在MyBatis中,`#{}`是预处理占位符,可防止SQL注入,适用于大多数参数传递场景;而`${}`是直接字符串替换,不安全,仅用于动态表名、列名等特殊场景。二者在安全性、性能及使用场景上有显著区别。
97 0
|
8月前
|
SQL Java 数据安全/隐私保护
发现问题:Mybatis-plus的分页总数为0,分页功能失效,以及多租户插件的使用。
总的来说,使用 Mybatis-plus 确实可以极大地方便我们的开发,但也需要我们理解其工作原理,掌握如何合适地使用各种插件。分页插件和多租户插件是其中典型,它们的运用可以让我们的代码更为简洁、高效,理解和掌握好它们的用法对我们的开发过程有着极其重要的意义。
784 15
|
8月前
|
SQL XML Java
菜鸟之路Day35一一Mybatis之XML映射与动态SQL
本文介绍了MyBatis框架中XML映射与动态SQL的使用方法,作者通过实例详细解析了XML映射文件的配置规范,包括namespace、id和resultType的设置。文章还对比了注解与XML映射的优缺点,强调复杂SQL更适合XML方式。在动态SQL部分,重点讲解了`&lt;if&gt;`、`&lt;where&gt;`、`&lt;set&gt;`、`&lt;foreach&gt;`等标签的应用场景,如条件查询、动态更新和批量删除,并通过代码示例展示了其灵活性与实用性。最后,通过`&lt;sql&gt;`和`&lt;include&gt;`实现代码复用,优化维护效率。
821 5
|
10月前
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
10月前
|
SQL Java 关系型数据库
MyBatis篇-分页
本文介绍了多种分页方式,包括自带rowbound内存分页、第三方插件pagehelper(通过修改SQL实现分页)、SQL分页(依赖limit或rownum等关键字)、数组分页(先查询全部数据再用subList分页)、拦截器分页(自定义拦截器为SQL添加分页语句)。最后总结了逻辑分页(内存分页,适合小数据量)和物理分页(直接在数据库层面分页,适合大数据量)的优缺点,强调物理分页优先于逻辑分页。