MyBatis 插件开发

MyBatis加强(4)~mybatis 插件开发 Python微信订餐小程序课程视频 https://edu.youkuaiyun.com/course/detail/36074 Python实战量化交易理财系统 https://edu.youkuaiyun.com/course/detail/35475 作者:一乐乐 欢迎大家来一乐乐的博客园 #MySignature { display: block; background-color: rgba(198, 206, 212, 1); border-radius: 7px; box-shadow: 1px 1px 1px rg 阅读详情

说明

Mybatis 允许在映射语句执行过程中的某些点拦截对的调用。默认情况下,MyBatis 允许插件拦截以下方法调用:

拦截器方法说明
Executorupdate, query, flushStatements, commit, rollback, getTransaction, close, isClosed执行 SQL 的核心对象(增删改查、事务控制等),可以在这里打印 SQL 语句,监控 SQL 执行时间等
StatementHandlerprepare, parameterize, batch, update, query封装了 JDBC Statement 操作,是 SQL 语法的构建器,可以在这里对 SQL 语句进行修改
ParameterHandlergetParameterObject, setParameters处理 SQL 入参绑定
ResultSetHandlerhandleResultSets, handleOutputParameters处理查询结果

注:拦截需求并不一定要跟拦截器类型严格对应。如,一般在StatementHandler中修改SQL,但是也可以在ParameterHandler修改SQL,只要可以在执行前获取、修改SQL即可。


代码

NotInjectSQL注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 不需要修改SQL的类和方法的注解
 *
 * @author java_t_t
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotInjectSQL {
}

Desensitization注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 需要脱密的字段的注解
 *
 * @author java_t_t
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {
}

Executor拦截器示例:

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.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;

@Slf4j
@Intercepts({
        @Signature(
                type = Executor.class, // 拦截的类
                method = "update", // 拦截的方法。insert、update、delete都属于update方法
                args = {MappedStatement.class, Object.class} // 方法参数
        ),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})
})
public class MybatisExecutorInterceptor implements Interceptor {
    private static final DateTimeFormatter LOCAL_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter LOCAL_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter LOCAL_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("=====> Executor");
        printSql(invocation);
        long startTime = System.currentTimeMillis();
        Object proceed = invocation.proceed();
        log.info("===> Executor ===> SQL expend " + (System.currentTimeMillis() - startTime) + " ms");
        return proceed;
    }

    private void printSql(Invocation invocation) {
        Object[] queryArgs = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) queryArgs[0];
        // 获取BoundSql对象,包含带占位符'?'的SQL,以及对应的参数列表
        BoundSql boundSql = mappedStatement.getBoundSql(queryArgs[1]);
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        Configuration configuration = mappedStatement.getConfiguration();
        // 替换空白符
        String originalSql = boundSql.getSql().replaceAll("[\\s]+", " ");
        String[] split = originalSql.split("\\?");
        int index = 0;
        StringBuffer sql = new StringBuffer(split[index++]);
        StringBuffer sb = new StringBuffer();
        if (parameterMappings.size() > 0 && parameterObject != null) {
            if (configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
                sql.append(String.format("\"%s\"", parameterObject)).append(index < split.length ? split[index++] : "");
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    Object obj = null;
                    if (metaObject.hasGetter(propertyName)) {
                        obj = metaObject.getValue(propertyName);
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        obj = boundSql.getAdditionalParameter(propertyName);
                    }
                    if (obj == null) {
                        sql.append("null").append(index < split.length ? split[index++] : "");
                        sb.append(propertyName).append(" = null, ");
                        continue;
                    }
                    String parameterValue;
                    String className = obj.getClass().getName();
                    switch (className) {
                        case "java.lang.String" -> parameterValue = "\"" + obj + "\"";
                        case "java.time.LocalDateTime" ->
                                parameterValue = String.format("STR_TO_DATE(\"%s\",\"%%Y-%%m-%%d %%H:%%i:%%s\")",
                                        LOCAL_DATE_TIME_FORMAT.format((LocalDateTime) obj));
                        case "java.time.LocalDate" ->
                                parameterValue = String.format("STR_TO_DATE(\"%s\",\"%%Y-%%m-%%d\")",
                                        LOCAL_DATE_FORMAT.format((LocalDate) obj));
                        case "java.time.LocalTime" ->
                                parameterValue = String.format("STR_TO_DATE(\"%s\",\"%%H:%%i:%%s\")",
                                        LOCAL_TIME_FORMAT.format((LocalTime) obj));
                        case "java.util.Date" ->
                                parameterValue = String.format("STR_TO_DATE(\"%s\",\"%%Y-%%m-%%d %%H:%%i:%%s\")",
                                        SIMPLE_DATE_FORMAT.format((Date) obj));
                        default -> parameterValue = obj.toString();
                    }
                    sql.append(parameterValue).append(index < split.length ? split[index++] : "");
                    sb.append(propertyName).append("=").append(obj).append("(").append(obj.getClass().getSimpleName()).append("), ");
                }
            }
            if (sb.length() > 2) {
                sb.deleteCharAt(sb.length() - 1);
                sb.deleteCharAt(sb.length() - 1);
            }
        }

        log.info("===> Executor ===> Original SQL: " + originalSql);
        log.info("===> Executor ===> Real SQL: " + sql);
        log.info("===> Executor ===> Real PARAM: " + sb);
    }
}

StatementHandler拦截器示例:

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.select.Values;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.io.StringReader;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisStatementHandlerInterceptor implements Interceptor {
    private static final String IS_DELETE = "IS_DELETE";
    private static final String UPDATE_TIME = "UPDATE_TIME";
    private static final String CREATE_TIME = "CREATE_TIME";
    private static final String SQL_PATH = "delegate.boundSql.sql";
    private static final Column INJECT_CURRENT_TIMESTAMP = new Column("CURRENT_TIMESTAMP()");
    private static final EqualsTo INJECT_IS_DELETE = new EqualsTo()
            .withLeftExpression(new Column(IS_DELETE))
            .withRightExpression(new LongValue(0));
    private static final CCJSqlParserManager SQL_PARSER = new CCJSqlParserManager();
    private static final Map<String, Boolean> NOT_INJECT_MAP = new HashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("=====> StatementHandler");
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        log.info("===> StatementHandler ===> Original Sql:{}", metaObject.getValue(SQL_PATH).toString().replaceAll("[\\s]+", " "));
        injectSql(metaObject);
        log.info("===> StatementHandler ===> Modified Sql:{}", metaObject.getValue(SQL_PATH).toString().replaceAll("[\\s]+", " "));

        return invocation.proceed();

        /* 修改或添加查询参数
        metaObject.setValue("delegate.parameterHandler.parameterObject.pageSize", 5);
        metaObject.setValue("delegate.parameterHandler.parameterObject.addValue", "add_value");

        // 关闭内存分页。内存分页(逻辑分页):把数据查询到内存中,再分页;物理分页:在sql语句中添加limit offset, size
        metaObject.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
        metaObject.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);*/
    }

    private void injectSql(MetaObject metaObject) {
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (hasAnnotation(mappedStatement)) {
            return;
        }
        // mappedStatement.getSqlCommandType()的值是xml中的标签,跟实际的查询类型不一定完全符合,所以需要根据解析的实际结果决定注入方式
        String originalSql = (String) metaObject.getValue(SQL_PATH);
        try (StringReader sr = new StringReader(originalSql)) {
            Statement parse = SQL_PARSER.parse(sr);
            if (parse instanceof Insert) {
                injectInsert((Insert) parse, metaObject);
            } else if (parse instanceof Update) {
                injectUpdate((Update) parse, metaObject);
            } else if (parse instanceof Delete) {
                injectDelete((Delete) parse, metaObject);
            } else if (parse instanceof Select) {
                injectSelect((Select) parse, metaObject);
            }
        } catch (JSQLParserException e) {
            log.warn(e.getMessage());
        }
    }

    private void injectInsert(Insert insert, MetaObject metaObject) {
        ExpressionList<Column> columns = insert.getColumns();
        Values values = insert.getValues();
        if (columns == null || columns.isEmpty()
                || values == null || values.getExpressions() == null || values.getExpressions().isEmpty()) {
            return;
        }
        boolean noUpdateTime = true;
        boolean noCreateTime = true;
        boolean noIsDelete = true;
        for (Column column : columns) {
            String columnName = column.getColumnName().replace("`", "").trim().toUpperCase(Locale.ROOT);
            if (UPDATE_TIME.equals(columnName)) {
                noUpdateTime = false;
            }
            if (CREATE_TIME.equals(columnName)) {
                noCreateTime = false;
            }
            if (IS_DELETE.equals(columnName)) {
                noIsDelete = false;
            }
        }
        if (noUpdateTime) {
            columns.add(new Column(UPDATE_TIME));
            addValues(values.getExpressions(), INJECT_CURRENT_TIMESTAMP);
        }
        if (noCreateTime) {
            columns.add(new Column(CREATE_TIME));
            addValues(values.getExpressions(), INJECT_CURRENT_TIMESTAMP);
        }
        if (noIsDelete) {
            columns.add((Column) INJECT_IS_DELETE.getLeftExpression());
            addValues(values.getExpressions(), INJECT_IS_DELETE.getRightExpression());
        }
        metaObject.setValue(SQL_PATH, insert.toString());
    }

    private void addValues(List expressions, Expression expression) {
        if (expressions == null) {
            return;
        }
        Iterator iterator = expressions.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            if (next instanceof JdbcParameter) {
                expressions.add(expression);
                return;
            }
            if (next instanceof List) {
                addValues((List) next, expression);
            }
        }
    }

    private void injectUpdate(Update update, MetaObject metaObject) {
        Expression where = update.getWhere();
        if (where == null) {
            update.setWhere(INJECT_IS_DELETE);
        } else if (!where.toString().toUpperCase(Locale.ROOT).contains(IS_DELETE)) {
            update.setWhere(BinaryExpression.and(where, INJECT_IS_DELETE));
        }
        List<UpdateSet> updateSets = update.getUpdateSets();
        if (updateSets == null) {
            metaObject.setValue(SQL_PATH, update.toString());
            return;
        }
        boolean noUpdateTime = true;
        outer:
        for (UpdateSet updateSet : updateSets) {
            for (Column column : updateSet.getColumns()) {
                if (UPDATE_TIME.equals(column.getColumnName().replace("`", "").trim().toUpperCase(Locale.ROOT))) {
                    noUpdateTime = false;
                    break outer;
                }
            }
        }
        if (noUpdateTime) {
            UpdateSet updateSet = new UpdateSet();
            updateSet.setColumns(new ExpressionList<>().withExpressions(new Column(UPDATE_TIME)));
            updateSet.setValues(new ExpressionList().withExpressions(INJECT_CURRENT_TIMESTAMP));
            updateSets.add(updateSet);
        }

        metaObject.setValue(SQL_PATH, update.toString());
    }

    private void injectDelete(Delete delete, MetaObject metaObject) {
        Expression where = delete.getWhere();
        if (where == null) {
            delete.setWhere(INJECT_IS_DELETE);
        } else if (!where.toString().toUpperCase(Locale.ROOT).contains(IS_DELETE)) {
            delete.setWhere(BinaryExpression.and(where, INJECT_IS_DELETE));
        }
        metaObject.setValue(SQL_PATH, delete.toString());
    }

    private void injectSelect(Select select, MetaObject metaObject) {
        int childNum = select.getASTNode().jjtGetNumChildren();
        if (childNum == 1) {
            PlainSelect plainSelect = select.getPlainSelect();
            addWhere(plainSelect);
        } else if (childNum > 1) {
            SetOperationList operationList = select.getSetOperationList();
            for (Select operationSelect : operationList.getSelects()) {
                addWhere(operationSelect.getPlainSelect());
            }
        }
        metaObject.setValue(SQL_PATH, select.toString());
    }

    private void addWhere(PlainSelect plainSelect) {
        // 有join的多表查询不自动添加is_delete字段
        if (plainSelect.getJoins() != null && plainSelect.getJoins().size() > 0) {
            return;
        }
        Expression where = plainSelect.getWhere();
        if (where == null) {
            plainSelect.setWhere(INJECT_IS_DELETE);
        } else if (!where.toString().toUpperCase(Locale.ROOT).contains(IS_DELETE)) {
            plainSelect.setWhere(BinaryExpression.and(where, INJECT_IS_DELETE));
        }
    }

    /**
     * 判断类、方法上是否有@NotInjectSQL注解
     * 原理是mappedStatement.id的命名规则是 ${package名}.${mapper类名}.${方法名}。可以获取类路径,再通过反射加载类
     *
     * @param mappedStatement mappedStatement
     * @return true-有@NotInjectSQL注解/false-没有@NotInjectSQL注解
     */
    private boolean hasAnnotation(MappedStatement mappedStatement) {
        String sqlId = mappedStatement.getId();
        Boolean hasAnnotation = NOT_INJECT_MAP.get(sqlId);
        if (hasAnnotation != null) {
            return hasAnnotation;
        }
        int lastIndexOf = sqlId.lastIndexOf('.');
        String className = sqlId.substring(0, lastIndexOf);

        try {
            Class<?> mapperClass = Class.forName(className);
            if (mapperClass.getAnnotation(NotInjectSQL.class) != null) {
                for (Method declaredMethod : mapperClass.getDeclaredMethods()) {
                    NOT_INJECT_MAP.put(className + "." + declaredMethod.getName(), true);
                }
            } else {
                for (Method declaredMethod : mapperClass.getDeclaredMethods()) {
                    NOT_INJECT_MAP.put(className + "." + declaredMethod.getName(),
                            declaredMethod.getAnnotation(NotInjectSQL.class) != null);
                }
            }
            // 一般不会走到default情况。若真走到default分支,不对sql做修改
            return NOT_INJECT_MAP.getOrDefault(sqlId, true);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

ParameterHandler拦截器示例:

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.sql.PreparedStatement;
import java.util.Map;

@Slf4j
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})})
public class MybatisParameterHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("=====> ParameterHandler");
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

        Object parameterObject = parameterHandler.getParameterObject();
        if (parameterObject instanceof Map<?, ?>) {
            Map<String, Object> parameterMap = (Map<String, Object>) parameterObject;
            for (Map.Entry<String, Object> entry : parameterMap.entrySet()) {
                if ("pageSize".equals(entry.getKey())) {
                    Object pageSize = parameterMap.get("pageSize");
                    if (pageSize != null && Long.parseLong(pageSize.toString()) > 10) {
                        // 修改查询参数
                        parameterMap.put("pageSize", 10);
                    }
                }
            }

        }
        return invocation.proceed();
    }
}

ResultSetHandler拦截器示例:

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.List;
import java.util.Locale;

@Slf4j
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MybatisResultSetHandlerInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("=====> ResultSetHandler");
        Object proceedResult = invocation.proceed();
        if (proceedResult == null) {
            return proceedResult;
        }
        if (proceedResult instanceof List<?>) {
            List<?> list = (List<?>) proceedResult;
            for (Object item : list) {
                desensitization(item);
            }
        } else {
            desensitization(proceedResult);
        }
        return proceedResult;
    }

    private void desensitization(Object obj) throws IllegalAccessException {
        for (Field field : obj.getClass().getDeclaredFields()) {
            // 对可能的密码字段脱敏
            if (field.getName().toLowerCase(Locale.ROOT).contains("password")) {
                field.setAccessible(true);
                Object fieldValue = field.get(obj);
                field.set(obj, getDesensitiveValue(fieldValue));
                continue;
            }
            Desensitization annotation = field.getAnnotation(Desensitization.class);
            if (annotation == null) {
                continue;
            }
            field.setAccessible(true);
            Object fieldValue = field.get(obj);
            if (fieldValue == null) {
                continue;
            }
            field.set(obj, getDesensitiveValue(fieldValue));
        }
    }

    private static Object getDesensitiveValue(Object obj) {
        if (obj instanceof String) {
            String str = (String) obj;
            if (str.length() <= 2) {
                return "***";
            } else {
                return str.charAt(0) + "***" + str.charAt(str.length() - 1);
            }
        }
        // 处理基本类型byte/short/int/long/float/double/boolean/char
        if (obj instanceof Byte || obj instanceof Short || obj instanceof Integer || obj instanceof Long || obj instanceof Float || obj instanceof Double) {
            return (byte) 0;
        }
        if (obj instanceof Boolean) {
            return false;
        }
        if (obj instanceof Character) {
            return '*';
        }
        return null;
    }
}

如果需要解析SQL,需要在pom.xml中引入jsqlparser:

<!-- https://mvnrepository.com/artifact/com.github.jsqlparser/jsqlparser -->
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>5.2</version>
</dependency>

注册插件

  1. 使用Spring Boot默认连接池,使用配置文件注册:

1.1. 在resources/mybatis目录下添加mybatis-config.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
        <plugin interceptor="com.example.study.config.MybatisExecutorInterceptor"/>
        <plugin interceptor="com.example.study.config.MybatisStatementHandlerInterceptor"/>
        <plugin interceptor="com.example.study.config.MybatisParameterHandlerInterceptor"/>
        <plugin interceptor="com.example.study.config.MybatisResultSetHandlerInterceptor"/>
    </plugins>
</configuration>

1.2. 在application.properties添加以下配置:

mybatis.config-location=classpath:mybatis/mybatis-config.xml
  1. 使用Druid连接池,通过配置类注册:

在配置类中,SqlSessionFactoryBean对象调用setPlugins()方法注册(参考SpringBoot连接Mysql数据库)

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        factoryBean.setPlugins(new MybatisExecutorInterceptor(),
                new MybatisStatementHandlerInterceptor(),
                new MybatisParameterHandlerInterceptor(),
                new MybatisResultSetHandlerInterceptor());
        return factoryBean.getObject();
    }

注:以上拦截器可以放到一个类中,在@Intercepts中添加对应的@Signature,然后在intercept(invocation)方法中,用invocation.getTarget() instanceof(如invocation.getTarget() instanceof Executor、invocation.getTarget() instanceof StatementHandler)判断当前是哪个拦截器,然后执行对应的拦截逻辑。


参考文章

  1. MyBatis 3 | Configuration – mybatis

  2. MyBatis 插件开发的完整详细例子

  3. 简述 Mybatis 的插件运⾏原理,如何编写⼀个插件

  4. Mybatis 自定义拦截器与插件开发

  5. 深入理解Mybatis插件开发

MyBatis 如何编写一个自定义插件? intercept()方法的实现决定了我们自定义的逻辑,可以在这里添加一些额外的业务逻辑,比如缓存等等。在互联网场景下,我们需要经常对数据进行操作,并且需要保证数据的安全性和可维护性,自定义插件的使用可以很好地满足这些需求。MyBatis四大对象分别是拦截器、执行器、参数处理器和结果处理器,可以选择其中任意一个对象,但要注意需要增强的方法在该对象中的位置。第二步,为我们自定义的插件生成代理对象。可以通过Plugin类的wrap()方法实现,它接收两个参数,分别是目标对象target和我们自定义的插件。 阅读详情

相关推荐

深入探索MyBatis中的插件开发

在前文中,我已经提到了 MyBatis 插件,它是 MyBatis 框架的一个重要特性,可以实现 MyBatis 的功能扩展。Executor 接口插件: 对 MyBatis 执行器进行拦截、修改 SQL 和执行结果的扩展点;ParameterHandler 插件: 对 SQL 参数处理过程进行拦截并对参数进行加工处理;ResultHandler 插件: 对 SQL 执行结果进行拦截并对其进行加工处理;StatementHandler 插件: 对 MyBatis 操作数据库相关语句的拦截;

AI天才研究院 2694

MyBatis插件开发 : 插件原理、插件开发流程

增强代码 MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHa...

Guizy 1956

别再为OpenBCI_GUI安装发愁了!保姆级教程带你从Processing配置到成功运行(附常见错误解决)

本文提供了一份详细的OpenBCI_GUI安装教程,涵盖从Processing配置到成功运行的全过程,特别针对常见错误提供了解决方案。通过环境搭建、软件依赖安装、源码处理及首次运行实战,帮助用户高效完成数据可视化工具的部署与使用。

weixin_28366475的博客 269

MyBatis的插件开发

MyBatis的插件运行原理介绍及插件开发流程

wenqi1992的博客 2642

6.5 构网型储能系统的协同控制策略

本文系统阐述了构网型储能系统的协同控制策略,重点分析了多机组群协同运行面临的功率分配不均、动态响应失配等挑战。研究提出了改进型下垂控制与虚拟阻抗法、基于一致性算法的分布式协同控制等解决方案,探讨了构网型储能与同步发电机在惯性响应、一次调频等方面的协同机制,以及与跟网型电源在电压支撑、阻抗重塑等方面的互补作用。文章构建了分层控制架构,为实现多类型电源的全局优化运行提供了技术路径,对提升高比例新能源电力系统的稳定性具有重要意义。

技术工作者 1004

全面讲解电子齿轮比与脉冲数计算

什么是编码器分辨率。

viixin的博客 1万+

MyBatis源码解析系列之十二:自定义插件开发

欢迎回到我的MyBatis源码解析系列。在前几篇文章中,我们详细介绍了MyBatis的架构及其核心组件的实现原理,并深入探讨了MyBatis与Spring的集成。本篇文章将探讨如何开发自定义插件,以扩展MyBatis的功能,满足特定的业务需求。MyBatis插件机制基于Java的动态代理,允许开发者通过实现接口来拦截执行的核心方法,从而实现诸如性能监控、日志记录、SQL重写等功能。Executor:执行器,负责SQL语句的执行。:语句处理器,负责SQL语句的生成和参数设置。

阿里渣渣java研发组-群主 790

【系统分析师】高分论文:论基于构件的软件开发(气象灾害影响评估系统)

本文以某手机气象灾害影响评估系统为例,探讨了基于构件技术的软件开发实践。通过分析开发中常见的功能模块重复问题,提出建立企业构件库、针对性选择构件和加强构件管理的解决方案。项目实施中成功复用登录模块等通用构件,并针对气象数据转换需求开发新构件,最终在8个月内高质量完成系统建设。文章总结了构件开发的三个关键点:构件库建设是基础、构件选择需精准、构件管理要规范。同时指出需注意全新需求开发、性能优化等问题,建议通过培训提升团队对构件的理解力。该案例证明构件技术能有效缩短周期、降低成本,为企业积累可复用的技术资产。

数据知道的博客 4253

MyBatis 插件开发实战

MyBatis插件开发实战

774

在移动固态上安装ubuntu系统,重启后卡住报错:blk_update_request: I/O error, dev sda, sector ... op 0x0 (READ)如何解决?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值 。

**My Coding Family** 1799

12、知识图谱数据处理与推理全解析

本文深入解析了知识图谱中的数据处理与推理技术,涵盖SHACL的多种约束类型(如值约束、范围约束、字符串约束、逻辑约束等)及其在数据质量控制中的应用,并介绍了常用的SHACL工具与实际案例。文章对比了SPARQL与SHACL的特点,探讨了RDF(S)的局限性,引出表达能力更强的OWL和OWL2语言及其在语义网和生物医学等领域的应用。此外,还阐述了规则在知识表示中的作用,包括产生式规则和Datalog规则,并总结了知识图谱中推理的重要性与典型流程,为构建高质量、可推理的知识图谱提供了全面的技术指导。

zz67890的博客 135

MyBatis 插件开发的完整详细例子

MyBatis 插件开发的完整详细例子

爱的叹息的专栏 1126

芒果YOLO11改进17:即插即用 | 损失函数Repulsion Loss|Repulsion 解决目标遮挡场景下的目标检测,为解决密集人群检测中遮挡设计的损失函数,打造高效

即插即用 | 损失函数Repulsion Loss|Repulsion 解决目标遮挡场景下的目标检测,为解决密集人群检测中遮挡设计的损失函数,打造高效

包括YOLOv5、YOLOv7、YOLOv8等模型改进 484

深入MyBatis-插件开发

插件原理 MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果;MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。 Mybatis插件又称拦截器, 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Excutor–(update, query, flushStatemen...

追梦的小狼狗 187

用Python绘制DCA曲线详解及示例代码

在这篇文章中,我们将详细解释如何使用Python绘制DCA曲线,并提供示例代码。通过使用上述的示例代码和解释,你可以自己尝试不同的投资金额、期数和增长率来绘制自定义的DCA曲线。通过运行上述代码,我们将得到一个绘制了DCA曲线的图表。该图表将显示出DCA投资策略在给定投资金额、投资期数和增长率下的投资价值随时间的变化情况。在上述代码中,我们使用一个循环来计算每个投资期的DCA曲线值。现在,我们可以使用上述函数来计算DCA曲线的值,并将其绘制成图表。最后,我们返回计算得到的DCA曲线值列表。

TechPr的博客 1409

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Mybatis 插件开发

Mybatis 插件开发插件注意事项插件机制三级目录 插件注意事项 一个好的插件有几点需要注意: 1、易用,不需要别的配置。 2、不限制用户使用方式。 3、做出友好提示。 插件机制 Mybatis在创建执行器、参数处理器、Statement处理器、结果处理器四个环节提供了接口扩展,我们可以轻松的对以上处理器进行修改,来满足我们的业务要求。 我们接下来看下这个创建执行器、处理器时候的代码。首先要实现对 三级目录 ...

landywu1985的博客 475

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

MyBatis 使用教程及插件开发

MyBatis 使用教程代码演示

小凯的博客 1153

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

mybatis插件开发

/default只能使用在接口方法中,当使用该修饰符的时候,方法被视为普通方法,可以直接写方法体。方法中获取参数:properties.getProperty("username");该方法的作用是就是让mybatis判断,是否要进行拦截,然后做出决定是否生成一个代理。4. StatementHandler:拦截Sql语法构建的处理。2. ParameterHandler:拦截参数的处理。3. ResultHandler:拦截结果集的处理。1. Executor:拦截执行器的方法。

ArrayAda的博客 553

MyBatis插件开发原理

一、前言 MyBatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变MyBatis的默认行为(诸如SQL重写之类的),由于插件会深入到MyBatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。 MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现目标对象在执行目标方法之前进行拦截的...

Java小蚂蚁 3153

Mybatis之插件开发

Mybatis之插件开发

hcyxsh的博客 227

一文教你学会开发mybatis插件

手把手教你掌握mybatis插件的开发

weixin_45642975的博客 654

Mybatis插件开发

Mybatis插件开发 这里主要了解一下Mybatis的插件开发。 Mybatis插件介绍 Mybatis插件准确的说应该是拦截器,接口名为Interceptor,在配置中叫插件,功能非常强大,可以让我们无侵入式的对SQL的执行进行干涉,从SQL语句重写、参数注入、结果集返回等每个主要环节。 MyBatis默认支持对4大对象 ​ Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClos

qq_44621930的博客 330

MyBatis插件开发:简单分页插件

原文地址:xeblog.cn/articles/20 MyBatis插件开发流程 类实现Interceptor接口; 类上添加注解@Intercepts({@Signature(type, method, args)}) type:需要拦截的对象,只可取四大对象之一Executor.class、StatementHandler.class、ParameterHandler.class、Resu...

weixin_33835103的博客 326

mybatis缓存以及插件开发

一、Mybatis缓存 1. 一级缓存 1)概念 (1)Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession (2)Mybatis默认开启一级缓存 (3)也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;第二次以后是直接去缓存中取 (4)当执行SQL查询中间发生了增删改的操作,MyBatis会把Sq...

Stream_who的博客 491

【Mybatis相关】mybatis框架基础以及自定义插件开发

mybatis框架结构,自定义插件开发

wendao76的专栏 1211

MyBatis 中,如何实现插件开发?

除了这个例子,如果还需要了解更多的插件开发技巧,可以看官方文档中有关插件开发的章节:https://mybatis.org/mybatis-3/zh/configuration.html#plugins。需要注意的是,在使用以上方式过程中,也需要确保 SQL 语句的安全性,并避免 SQL 注入等安全问题的出现。需要注意的是,当有多个插件时,它们的执行顺序和在配置文件中的顺序有关,应根据需要设置执行顺序。例如,在一个查询中,可能会根据用户提供的参数动态拼接查询条件,可以使用。

luoluoal的博客 364
上一篇: 控制台打印带格式内容
下一篇: Spring Boot中拦截器的编写
java_t_t
博客等级 码龄9年 171粉丝 107原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值