Mr. Cappuccino的第56杯咖啡——Mybatis拦截器

Mybatis拦截器

概述

Mybatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用,通过织入拦截器,在不同节点修改一些执行过程中的关键属性,从而影响SQL的生成、执行和返回结果。

默认情况下,Mybatis支持四种对象拦截:

  1. Executor:拦截执行器的方法;
  2. ParameterHandler:拦截参数的处理;
  3. ResultSetHandler:拦截结果集的处理;
  4. StatementHandler:拦截Sql语法构建的处理;

执行顺序:Executor => StatementHandler => ParameterHandler => ResultSetHandler

注:本文代码基于《Mybatis一级缓存&二级缓存》中的"一级缓存(Spring整合Mybatis)"的代码进行调整。

应用场景

  1. 分页查询;
  2. 数据脱敏;
  3. 数据过滤;
  4. 监控Sql语句执行耗时;

项目结构

实现分页查询

StatementInterceptor.java

java 复制代码
package com.mybatis.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;

/**
 * @author honey
 * @date 2023-08-02 15:25:26
 */
@Slf4j
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
@Component
public class StatementInterceptor implements Interceptor {

    @Value("${mybatis.page-helper.rule}")
    private String rule;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        // 判断sql语句的类型
        switch (sqlCommandType) {
            case SELECT:
                extendLimit(statementHandler);
                break;
            case INSERT:
            case UPDATE:
            case DELETE:
            case FLUSH:
            default:
                break;
        }
        log.info("【StatementInterceptor】方法拦截前执行");
        Object result = invocation.proceed();
        log.info("【StatementInterceptor】方法拦截后执行");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private void extendLimit(StatementHandler statementHandler) throws NoSuchFieldException, IllegalAccessException {
        // 获取原生sql语句
        BoundSql boundSql = statementHandler.getBoundSql();
        Class<? extends BoundSql> aClass = boundSql.getClass();
        // 使用反射机制修改原生sql语句
        Field sql = aClass.getDeclaredField("sql");
        sql.setAccessible(true);
        String oldSqlStr = boundSql.getSql();
        log.info("原生sql语句:{}", oldSqlStr);
        // 加上分页规则
        sql.set(boundSql, oldSqlStr + " " + rule);
    }
}

application.yml

yaml 复制代码
mybatis:
  page-helper:
    rule: limit 2

其它拦截器的使用

ExecutorInterceptor.java

java 复制代码
package com.mybatis.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
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;

/**
 * @author honey
 * @date 2023-08-02 18:11:28
 */
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Component
public class ExecutorInterceptor implements Interceptor {

    Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("【ExecutorInterceptor】方法拦截前执行");
        Object result = invocation.proceed();
        log.info("【ExecutorInterceptor】方法拦截后执行");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

ParameterInterceptor.java

java 复制代码
package com.mybatis.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.sql.PreparedStatement;
import java.util.Properties;

/**
 * @author honey
 * @date 2023-08-02 18:22:15
 */
@Slf4j
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
@Component
public class ParameterInterceptor implements Interceptor {

    Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("【ParameterInterceptor】方法拦截前执行");
        Object result = invocation.proceed();
        log.info("【ParameterInterceptor】方法拦截后执行");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

ResultSetInterceptor.java

java 复制代码
package com.mybatis.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.sql.Statement;
import java.util.Properties;

/**
 * @author honey
 * @date 2023-08-02 18:24:00
 */
@Slf4j
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Component
public class ResultSetInterceptor implements Interceptor {

    Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("【ResultSetInterceptor】方法拦截前执行");
        Object result = invocation.proceed();
        log.info("【ResultSetInterceptor】方法拦截后执行");
        return result;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
相关推荐
摇滚侠15 小时前
《SpringBoot 3:入门与应用实战》第 13 章 整合 MyBatis MyBatis 概述 阅读笔记 37
spring boot·笔记·mybatis
摇滚侠15 小时前
《SpringBoot 3:入门与应用实战》第 13 章 整合 MyBatis 整合 MyBatis 阅读笔记 38
spring boot·笔记·mybatis
迪飞特科技2 天前
《MyBatis 批量插入性能优化的 5 个关键参数配置》
android·性能优化·mybatis
Java小白笔记2 天前
MyBatis XML Mapper 标签与 CRUD 原理实战
xml·数据库·mybatis
weixin_461408582 天前
Mybatis-flex小记
java·开发语言·mybatis
SQL-First布道者2 天前
⚡Spring JDBC 完整体系 · 第 10 集 · 手搓 `BaseCondition`
java·spring boot·后端·spring·mybatis·spring jdbc
南城以南溫暖如初1472 天前
社交裂变商城系统设计与实战开发指南
java·spring boot·mysql·vue·mybatis
南城以南溫暖如初1472 天前
分享返现商城系统开发实战:技术架构与运营经验指南
java·spring boot·mysql·架构·vue·mybatis
南城以南溫暖如初1473 天前
社群电商实战指南:从零搭建高转化社区的技术架构
java·spring boot·mysql·系统架构·vue·mybatis
南城以南溫暖如初1473 天前
会员返利商城系统开发实战:架构设计与实现指南
java·spring boot·mysql·vue·mybatis