赞
踩
Mybatis官方提供了插件机制为用户开辟了一道可以自定义的拦截扩展功能,在 系统最终执行SQL 之前,分别有四个部位可以做扩展,允许用户在不修改Mybatis核心代码的情况下,添加自己的逻辑处理,去完成各种各样的业务场景
典型案例:
2.分页插件 | MyBatis-Plus (baomidou.com)
这四个部位其实就是Mybatis的四大核心组件:
StatementHandler
、ParameterHandler
、ResultSetHandler
等来执行对应的SQLJDBC,构建SQL语法
,负责和数据库进行交互执行sql语句,(后期下手操作和修改SQL也主要是以它为主)以下是流程图:
精细划分的目的是为了允许在数据库操作的不同阶段进行精确的干预和拦截
- @Intercepts({
- @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
- Object.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
- Object.class,
- RowBounds.class,
- ResultHandler.class})})
- @Component
- public class MyApplicationInterceptor implements Interceptor {
- // 具体实现内容...
- }
可拦截的方法:
update
:负责执行 insert、update、delete 三种类型的 SQL 语句。query
:负责执行 select 类型的 SQL 语句。queryCursor
:负责执行 select 类型的 SQL 语句,返回 Cursor 对象。flushStatements
:提交批处理语句,返回批处理结果。commit
:事务提交。rollback
:事务回滚。getTransaction
:获取事务对象。close
:关闭 executor,同时根据参数决定是否强制回滚未提交的事务。isClosed
:检查 executor 是否已经关闭。clearLocalCache
:清除本地缓存。- @Component
- @Intercepts({
- @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
- })
- @Slf4j
- public class ParameterPluginInterceptor implements Interceptor {}
可拦截的方法:
getParameterObject
:此方法用于获取 SQL 参数对象。setParameters
:此方法将 SQL 命令中的参数与实际的参数对象相匹配。它负责将传入的参数设置到 PreparedStatement 中。- @Component
- @Intercepts({
- @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
- })
- @Slf4j
- public class StatementPluginInterceptor implements Interceptor {}
prepare
:准备一个数据库 Statement 对象以待执行。这个方法根据配置和上下文信息来创建一个 PreparedStatement 或 CallableStatement 对象。parameterize
:在 SQL 语句被执行之前,该方法负责将 SQL 参数设置到 PreparedStatement 对象中。batch
:负责处理批量执行的逻辑,将多个更新语句作为一个批处理提交。update
:执行写操作(insert、update、delete)的 SQL 语句。query
:执行查询操作(select)的 SQL 语句,并返回结果。queryCursor
:负责执行查询操作(select)SQL 语句,返回 Cursor 对象。getBoundSql
:返回 BoundSql 对象,这个对象包含了要执行的 SQL 语句以及该语句中所需的参数信息。- @Intercepts({
- @Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
- })
- /*@Component*/
- @Slf4j
- public class ResultInterceptor implements Interceptor {
handleResultSets
:这是主要的方法之一,它接受一个 Statement 对象作为参数,并将 SQL执行的结果 ResultSet 映射到结果对象。handleOutputParameters
:当存储过程调用完成之后,这个方法会处理其输出参数。它同样接受一个 Statement 对象作为参数。implements Interceptor 以实现一个Mybatis的拦截器,之后必须实现以上三个方法
以下是一个空白的Executor拦截:集成Interceptor之后,再加入注解内容选择要拦截的部分
- @Intercepts({
- @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
- Object.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
- Object.class,
- RowBounds.class,
- ResultHandler.class})})
- @Component
- @Slf4j
- public class ExecutorInterceptor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- MappedStatement mappedStatement = (MappedStatement) invocation.getTarget();
- log.info("==> ExecutorInterceptor: {}", mappedStatement.getId());
-
- return invocation.proceed();
- }
-
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
-
- @Override
- public void setProperties(Properties properties) {
-
- }
- }
以下是一个使用mybaits interceptor拦截器,拦截StatementHandler
可以看到 invocation 中已经夹了很多做拦截器需要的内容了
最重要的东西就是
BoundSql—ParameterMapping对象 再mybatis的 mapper xml中写的每一个#{var},最后都会形成一个 ?,这和我们直接使用jdbc是一个道理,最终都是挨个往 ?(问号) 里面塞值,只不过ParameterMapping记录了这些变量,它会在SQL最终要执行的时候去设置参数
以下实现了一个简单的拦截器,拦截MappedStatement
- import lombok.extern.slf4j.Slf4j;
- import org.apache.ibatis.executor.Executor;
- import org.apache.ibatis.mapping.BoundSql;
- import org.apache.ibatis.mapping.MappedStatement;
- import org.apache.ibatis.plugin.*;
- import org.apache.ibatis.session.ResultHandler;
- import org.apache.ibatis.session.RowBounds;
- import org.springframework.stereotype.Component;
-
- import java.util.Properties;
-
- @Intercepts({
- @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
- Object.class}),
- @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
- Object.class,
- RowBounds.class,
- ResultHandler.class})})
- @Component
- @Slf4j
- public class ExecutorInterceptor implements Interceptor {
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
- String id = mappedStatement.getId();
- String className = id.substring(0, id.lastIndexOf('.'));
- String methodName = id.substring(id.lastIndexOf('.') + 1);
-
- BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
- Object parameterObject = boundSql.getParameterObject();
-
- log.info("==> id: {}",id);
- log.info("==> ClassName: {}", className);
- log.info("==> MethodName: {}", methodName);
-
- log.info("==> SQL语句: {}", boundSql.getSql());
- log.info("==> 参数: {}", parameterObject);
-
- return invocation.proceed();
- }
-
- @Override
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
-
- @Override
- public void setProperties(Properties properties) {
- Interceptor.super.setProperties(properties);
- }
- }
最终效果:(可以看到,打印了SQL的来源,包、方法、SQL语句、参数都拿到了)
ResultSetHandler是对SQL最终操作的结果映射到java中的步骤,以下代码的操作,最终将拦截这个步骤,并对MAP相关结果做出操作,将MAP中的Key都转成大写
- @Slf4j
- @Component
- @Intercepts(
- {@Signature(
- type = ResultSetHandler.class,
- method = "handleResultSets",
- args = {Statement.class}
- )})
- public class ResultMapCaseInterceptor implements Interceptor {
-
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
-
- MetaObject metaResultSetHandler = MetaObject.forObject(resultSetHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
- MappedStatement mappedStatement = (MappedStatement) metaResultSetHandler.getValue("mappedStatement");
- Object returnValue = invocation.proceed();
- List<ResultMap> resultMaps = mappedStatement.getResultMaps();
- String resultMapTypeName = resultMaps.get(0).getType().getName();
-
- String MAP = "java.util.Map";
- String HASH_MAP = "java.util.HashMap";
- String LINKED_HASH_MAP = "java.util.LinkedHashMap";
- if(StrUtil.equalsIgnoreCase(MAP, resultMapTypeName) ||
- StrUtil.equalsIgnoreCase(HASH_MAP, resultMapTypeName) ||
- StrUtil.equalsIgnoreCase(LINKED_HASH_MAP, resultMapTypeName)){
-
- if(returnValue instanceof List<?> list){
- if(CollectionUtil.isNotEmpty(list)) {
- list.forEach(item -> {
- Map<String, Object> map = (Map<String, Object>) item;
- Map<String, Object> newMap = new LinkedHashMap<>();
- map.forEach((k, v) -> {
- newMap.put(k.toUpperCase(), v);
- });
- map.clear();
- map.putAll(newMap);
- });
- }
- }
- }
- return returnValue;
- }
-
- @Override
- public Object plugin(Object target) {
- return Interceptor.super.plugin(target);
- }
-
- @Override
- public void setProperties(Properties properties) {
- }
- }
JSqlParser 是一个 SQL 语句解析器。它转换 Java 类的可遍历层次结构中的 SQL。JSqlParser不仅限于一个数据库,而是支持Oracle,SqlServer,MySQL,PostgreSQL等等,至今它仍在更新,而Mybatisplus之中也包含了这个库,可以直接使用,如果没有使用mybatis plus那么需要手动引入它
官方网站:JSQLParser 4.9 documentation
Github:JSQLParser/JSqlParser(github.com)
Maven:
- <dependency>
- <groupId>com.github.jsqlparser</groupId>
- <artifactId>jsqlparser</artifactId>
- <version>4.9</version>
- </dependency>
Gradle/KT:
implementation("com.github.jsqlparser:jsqlparser:4.9")
以下是一个简单的例子,为了展示Jsqlparser可以解析SQL的内容
- @Test
- public void Test2() throws JSQLParserException, ParseException {
- String originalSql = "SELECT " +
- "t1.id, " +
- "t1.name, " +
- "SUM(t2.amount) AS total_amount, " +
- "(SELECT COUNT(*) FROM orders o WHERE o.customer_id = t1.id) AS order_count " +
- "FROM customers t1 " +
- "JOIN orders t2 ON t1.id = t2.customer_id " +
- "LEFT JOIN payments t3 ON t2.id = t3.order_id " +
- "WHERE t1.status = 'active' " +
- "AND t2.order_date BETWEEN '2023-01-01' AND '2023-12-31' " +
- "GROUP BY t1.id, t1.name " +
- "HAVING SUM(t2.amount) > 1000 " +
- "ORDER BY total_amount DESC, t1.name ASC;";
- CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
- Statement statement = parser.Statement();
- parser.getASTRoot().jjtAccept(sqlModifier, null);
- log.info("==> JsqlParser SQL: {}", statement.toString());
-
- Select selectStatement = (Select) statement;
- PlainSelect plainSelect = selectStatement.getPlainSelect();
-
- Table table = (Table) plainSelect.getFromItem();
- System.out.println(table.toString());
-
- //获取表名们
- Set<String> tableNames = TablesNamesFinder.findTables(originalSql);
- System.out.println("表名们:"+tableNames);
-
- // Print SELECT clause
- System.out.println("SELECT clause: " + plainSelect.getSelectItems());
-
- // Print FROM clause
- System.out.println("FROM clause: " + plainSelect.getFromItem());
-
- // Print JOIN clauses
- if (plainSelect.getJoins() != null) {
- for (Join join : plainSelect.getJoins()) {
- System.out.println("JOIN clause: " + join);
- }
- }
-
- // Print WHERE clause
- System.out.println("WHERE clause: " + plainSelect.getWhere());
-
- // Print GROUP BY clause
- System.out.println("GROUP BY clause: " + plainSelect.getGroupBy());
-
- // Print HAVING clause
- System.out.println("HAVING clause: " + plainSelect.getHaving());
-
- // Print ORDER BY clause
- System.out.println("ORDER BY clause: " + plainSelect.getOrderByElements());
- }
以下是一个简单的使用,在代码里使用Jsqlparser,最直接的方式就是直接调用CCJSqlParserUtil
- String originalSql = "select * from t_user" // 需要解析的SQL语句
- CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
- Statement statement = parser.Statement();
通过 getPlainSelect() 获得Statement中的PlainSelect,而这个PlainSelect就可以拿到很多SQL语句中的内容
- String originalSql = "SELECT rrr.* FROM rel_role_resource rrr " +
- "JOIN rel_role_user rru ON rrr.role_id = rru.role_id " +
- "JOIN `user` u ON u.id = rru.user_id " +
- "AND rrr.org_id = ? " +
- "AND rrr.role_id = rru.role_id " +
- "AND u.id = 123";
- CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
- Statement statement = parser.Statement();
-
- Select select = (Select) statement;
- log.info("==> Select: {}", select.toString());
- PlainSelect plain = select.getPlainSelect();
-
- plain.getSelectItems().forEach(selectItem -> {
- log.info("==> 查询的字段: {}", selectItem.toString());
- });
-
- if (plain.getFromItem() != null) {
- log.info("==> 查询的表: {}", plain.getFromItem().toString());
- }
-
- if (plain.getWhere() != null){
- log.info("==> 查询的条件: {}", plain.getWhere().toString());
- }
-
- if(CollectionUtil.isNotEmpty(plain.getJoins())){
- plain.getJoins().forEach(join -> {
- join.getOnExpressions().forEach(expression -> {
- log.info("==> Join 条件: {}", expression.toString());
- });
- log.info("==> Join: {}", join.toString());
- });
- }
-
- TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
- Set<String> tableList = tablesNamesFinder.getTables(statement);
- tableList.forEach(table -> {
- log.info("==> 查询的表: {}", table);
- });
- log.info("==> 查询的表: {}", tableList);
运行后结果:
该文未完结....持续更新
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。