当前位置: 首页 > news >正文

Mybatis 拦截器 说明和使用 (二)

Mybatis 拦截器使用

  • 一.处理器类简介
    • 1.主要类梳理
    • 2.处理器接口
    • 3.简要调用流程(仅做参考,不标准)
  • 二.拦截器应用
    • 1.环境准备
      • 1.依赖包(多环境打包可忽略)
      • 2.配置文件(访问的本地 Postgres 数据库)
      • 3.启动类
      • 4.数据库连接池配置
      • 5.MVC 层代码
      • 6.Map 接口和实现
      • 7.表结构和数据
    • 2.拦截器配置方式
      • 1.Spring注解
      • 2.手动注册
      • 3.顺序问题
    • 3.拦截器处理Map
    • 4.为不同SQL动态设置超时

一.处理器类简介

1.主要类梳理

作用
org.apache.ibatis.session.Configuration数据源配置信息、插件、映射器等
org.apache.ibatis.session.SqlSession顶层API,数据库会话类
org.apache.ibatis.session.SqlSessionFactorySqlSession工厂
org.apache.ibatis.type.TypeHandlerJdbcType 和 JavaType 映射
org.apache.ibatis.mapping.BoundSql动态生成 SQL 的封装类
org.apache.ibatis.mapping.MappedStatementxml 标签配置信息封装对象

2.处理器接口

处理器接口默认实现方法
ParameterHandlerDefaultParameterHandlergetParameterObject()
setParameters(PreparedStatement ps)
ResultSetHandlerDefaultResultSetHandlerhandleResultSets(Statement stmt)
handleCursorResultSets(Statement stmt)
handleOutputParameters(CallableStatement cs)
StatementHandlerBaseStatementHandler
CallableStatementHandler
PreparedStatementHandler
RoutingStatementHandler
SimpleStatementHandler
prepare(Connection connection, Integer transactionTimeout)
parameterize(Statement statement)
batch(Statement statement)
update(Statement statement)
query(Statement statement, ResultHandler resultHandler)
queryCursor(Statement statement)
getBoundSql()
getParameterHandler()
ExecutorBaseExecutor
BatchExecutor
CachingExecutor
ClosedExecutor
ReuseExecutor
SimpleExecutor

update(MappedStatement ms, Object parameter)
query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql)
query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds)
flushStatements()
commit(boolean required)
rollback(boolean required)
createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)
isCached(MappedStatement ms, CacheKey key)
clearLocalCache()
deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType)
getTransaction()
close(boolean forceRollback)
isClosed()
setExecutorWrapper(Executor executor)

3.简要调用流程(仅做参考,不标准)

在这里插入图片描述

SimpleExecutor -> newStatementHandler -> RoutingStatementHandler -> SimpleStatementHandler -> newParameterHandler -> newResultSetHandler

二.拦截器应用

1.环境准备

在这里插入图片描述

1.依赖包(多环境打包可忽略)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <!--配置阿里云依赖包和插件仓库-->
    <repositories>
        <repository>
            <id>aliyun</id>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>aliyun-plugin</id>
            <url>https://maven.aliyun.com/nexus/content/groups/public/</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.11</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-autoconfigure</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.5.0</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
    </dependencies>


    <!-- 多环境配置 -->
    <profiles>
        <profile>
            <id>test</id>
            <properties>
                <properties.active>test</properties.active>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>dev</id>
            <properties>
                <properties.active>dev</properties.active>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <properties.active>prod</properties.active>
            </properties>
        </profile>
    </profiles>

    <build>
        <finalName>demo</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.4</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <resources>
            <!--打包的时候先去掉所有的配置文件-->
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>application*.yml</exclude>
                </excludes>
            </resource>
            <!--添加需要包含的文件-->
            <resource>
                <directory>src/main/resources</directory>
                <!-- 是否替换yml或者properties里@xx@表示的maven properties属性值 -->
                <filtering>true</filtering>
                <!--在打包的时候,根据-P参数,加上需要的yml配置文件-->
                <includes>
                    <include>application.yml</include>
                    <include>application-${properties.active}.yml</include>
                </includes>
            </resource>
        </resources>
    </build>


</project>

2.配置文件(访问的本地 Postgres 数据库)

server:
  port: 8093

spring:
  application:
    name: moon
  datasource:
    druid:
      username: postgres
      password: 
      url: jdbc:postgresql://127.0.0.1:5432/wiki_animal_db
      driverClassName: org.postgresql.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      # 初始化大小,最小,最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      validation-query: select version()
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,wall用于防火墙
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall
      use-global-data-source-stat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

#mybatis配置
mybatis:
  mapper-locations: classpath:/mapper/*.xml  #修改为对应的mapper文件路径
  #驼峰命名
  configuration:
    map-underscore-to-camel-case: true

#打印sql
logging:
  level:
    com.demo: info

3.启动类

package com.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author
 * @date 2022-10-13 19:22
 * @since 1.8
 */
@MapperScan("com.demo.mapper")
@SpringBootApplication
public class DemoApplication {

    /**
     * 主类
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }
}

4.数据库连接池配置

package com.demo.confin;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.sql.SQLException;
import java.util.Properties;

/**
 * @author 
 * @date 2022-10-14 16:29
 * @since 1.8
 */
@Slf4j
@Component
public class AnimalDataSource {

    /**
     * 获取配置
     * @return
     */
    @Bean(name = "druidProperties")
    @ConfigurationProperties(prefix = "spring.datasource")
    public Properties druidProperties(){
        return new Properties();
    }

    /**
     * @description: postgres dataSource
     * @params: [properties]
     * @return: com.alibaba.druid.pool.DruidDataSource
     * @create: 2022-08-18
     */
    @Primary
    @Bean(name = "druidDataSource")
    public DruidDataSource druidDataSource(@Qualifier("druidProperties")Properties properties){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.configFromPropety(properties);
        try {
            druidDataSource.init();
        } catch (SQLException e) {
            log.error("Animal Datasource Init Exception:",e);
        }
        return druidDataSource;
    }
}

5.MVC 层代码

package com.demo.controller;

import com.demo.entity.AnimalEntity;
import com.demo.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 
 * @date 2022-10-17 19:48
 * @since 1.8
 */
@RestController
@RequestMapping("/animal")
public class AnimalController {


    @Autowired
    AnimalService animalService;

    @GetMapping("/query")
    public List<AnimalEntity>query() {
        return animalService.query();
    }

    @GetMapping("/queryByName/{name}")
    public Map<Integer, String> query(@PathVariable("name") String name) {
        return animalService.queryByName(name);
    }

    @GetMapping("/queryByCountryAndName")
    public AnimalEntity queryByCountryAndName(String name,String country) {
        return animalService.queryByCountryAndName(name,country);
    }
}

package com.demo.service;

import com.demo.entity.AnimalEntity;
import java.util.List;
import java.util.Map;

/**
 * @author
 * @date 2022-10-17 19:48
 * @since 1.8
 */
public interface AnimalService {

    /**
     * 查询
     * @return
     */
    List<AnimalEntity> query();

    /**
     * 按名字查询
     * @param name
     * @return
     */
    Map<Integer, String> queryByName(String name);

    /**
     * 按国家和名字查询
     * @param name
     * @param country
     * @return
     */
    AnimalEntity queryByCountryAndName(String name,String country);
}

package com.demo.service.impl;

import com.demo.entity.AnimalEntity;
import com.demo.mapper.AnimalMapper;
import com.demo.service.AnimalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

/**
 * @author 
 * @date 2022-10-17 19:50
 * @since 1.8
 */
@Service
public class AnimalServiceImpl implements AnimalService {


    @Autowired
    AnimalMapper animalMapper;

    @Override
    public List<AnimalEntity> query() {
        return animalMapper.query();
    }


    @Override
    public Map<Integer, String> queryByName(String name) {
        return animalMapper.queryByName(name).get(0);
    }

    @Override
    public  AnimalEntity queryByCountryAndName(String name,String country){
        return animalMapper.queryByCountryAndName(name,country);
    }
}

6.Map 接口和实现

package com.demo.mapper;

import com.demo.entity.AnimalEntity;

import java.util.List;
import java.util.Map;

/**
 * @author
 * @date 2022-10-17 19:50
 * @since 1.8
 */
public interface AnimalMapper {

    /**
     * 查询
     * @return
     */
    List<AnimalEntity> query();

    /**
     * 查名字
     * @param name
     * @return
     */
    List<Map<Integer, String>> queryByName(String name);

    /**
     * 按国家和名字
     * @param name
     * @param country
     * @return
     */
    AnimalEntity queryByCountryAndName(String name,String country);

}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.demo.mapper.AnimalMapper">

    <resultMap id="rabbit" type="com.demo.entity.AnimalEntity">
        <result column="id" property="id"/>
        <result column="animal_name" property="name"/>
        <result column="country" property="country"/>
    </resultMap>

    <select id="query" resultMap="rabbit">
        select id,animal_name,country from public.tb_rabbit_basic
    </select>

    <select id="queryByName" resultMap="rabbit">
        select id,animal_name,country from public.tb_rabbit_basic where animal_name like '%'||#{name}||'%'
    </select>

    <select id="queryByCountryAndName" resultMap="rabbit">
        select id,animal_name,country from public.tb_rabbit_basic where animal_name = #{name} and country = #{country}
    </select>

</mapper>

7.表结构和数据

-- public.tb_rabbit_basic definition

-- Drop table

-- DROP TABLE public.tb_rabbit_basic;

CREATE TABLE public.tb_rabbit_basic (
	id int4 NULL,
	animal_name varchar NULL,
	country varchar NULL
);

INSERT INTO public.tb_rabbit_basic (id,animal_name,country) VALUES
	 (1,'安哥拉兔子','安哥拉'),
	 (2,'道奇兔','荷兰'),
	 (3,'多瓦夫兔','德国'),
	 (4,'巨型花明兔','比利时');

2.拦截器配置方式

1.Spring注解

定义拦截器类,在 Executor 阶段拦截了 query 方法

package com.demo.confin;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.util.Properties;

/**
 * @author
 * @date 2022-10-21 23:09
 * @since 1.8
 */
@Slf4j
@Component
@Intercepts({
        @Signature(method = "query",type = Executor.class,args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class AnimalInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        log.info("进入拦截器 Mapper.xml Method ID Is:{}",mappedStatement.getId());

        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

启动服务并调用

在这里插入图片描述

日志:

2022-10-21 23:14:25.008 DEBUG 15240 --- [           main] com.demo.DemoApplication                 : Running with Spring Boot v2.7.4, Spring v5.3.23
2022-10-21 23:14:25.009  INFO 15240 --- [           main] com.demo.DemoApplication                 : The following 1 profile is active: "dev"
2022-10-21 23:14:26.297  INFO 15240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8093 (http)
2022-10-21 23:14:26.308  INFO 15240 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-10-21 23:14:26.308  INFO 15240 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-10-21 23:14:26.480  INFO 15240 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-10-21 23:14:26.481  INFO 15240 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1420 ms
2022-10-21 23:14:26.764  INFO 15240 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
2022-10-21 23:14:27.298  INFO 15240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8093 (http) with context path ''
2022-10-21 23:14:27.308  INFO 15240 --- [           main] com.demo.DemoApplication                 : Started DemoApplication in 2.796 seconds (JVM running for 3.222)
2022-10-21 23:14:38.102  INFO 15240 --- [nio-8093-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-10-21 23:14:38.103  INFO 15240 --- [nio-8093-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-10-21 23:14:38.103  INFO 15240 --- [nio-8093-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
2022-10-21 23:14:38.141  INFO 15240 --- [nio-8093-exec-2] com.demo.confin.AnimalInterceptor        : 进入拦截器 Mapper.xml Method ID Is:com.demo.mapper.AnimalMapper.query
2022-10-21 23:14:38.581 DEBUG 15240 --- [nio-8093-exec-2] com.demo.mapper.AnimalMapper.query       : ==>  Preparing: select id,animal_name,country from public.tb_rabbit_basic
2022-10-21 23:14:38.712 DEBUG 15240 --- [nio-8093-exec-2] com.demo.mapper.AnimalMapper.query       : ==> Parameters: 
2022-10-21 23:14:38.728 DEBUG 15240 --- [nio-8093-exec-2] com.demo.mapper.AnimalMapper.query       : <==      Total: 4

2.手动注册

取消上面自定义拦截器的初始化注解:@Component;并通过程序手动注册,如果多拦截器的情况下,可以取消所有自动注入,并统一手动处理,PageHelper 分页插件失效或者自定义拦截器不生效,多数情况即为注入顺序导致功能相互覆盖引起的

新增手动注入拦截器类

package com.demo.confin;

import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author 
 * @date 2022-10-21 23:19
 * @since 1.8
 */
@Component
public class MybatisInterceptorListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    /**
     * 控制注册自定义拦截器顺序
     * 1.避免被PageHelper覆盖(暂不涉及)
     * 2.避免循环依赖
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        for (SqlSessionFactory factory : sqlSessionFactoryList) {
            factory.getConfiguration().addInterceptor(new AnimalInterceptor());
        }
    }
}

重启服务并调用接口
在这里插入图片描述
拦截器生效
在这里插入图片描述

3.顺序问题

同一类型的拦截类,处理时才会有顺序问题,Mybatis 四种类型直接的顺序关系是固定的,也即,如果多个拦截器都是拦截 Executor 的同一 query 方法,才会有注册覆盖现象;不会出现Executor 的拦截覆盖了 StatementHandler 的拦截,但是在应用中还是要谨慎注意处理时是否受到"顺序"影响,或者其他处理冲突

实际观察一下这四个类的处理顺序:Executor、ParameterHandler、StatementHandler、ResultSetHandler

分别定义四个拦截器,并乱序注入

package com.demo.confin;

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

import java.util.Properties;

/**
 * @author 
 * @date 2022-10-21 23:35
 * @since 1.8
 */
@Slf4j
@Intercepts({
        @Signature(method = "query",type = Executor.class,args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class OneInterceptor implements Interceptor {

    /**
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.info("Proxy {} Target {}",this.getClass().getName(),invocation.getTarget().getClass());
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

package com.demo.confin;

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.Properties;

/**
 * @author 
 * @date 2022-10-21 23:13
 * @since 1.8
 */
@Slf4j
@Intercepts({
        @Signature(method = "setParameters",type = ParameterHandler.class,args = {PreparedStatement.class})
})
public class TwoInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.info("Proxy {} Target {}",this.getClass().getName(),invocation.getTarget().getClass());

        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

package com.demo.confin;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
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.session.ResultHandler;

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

/**
 * @author 
 * @date 2022-10-21 23:53
 * @since 1.8
 */
@Slf4j
@Intercepts({
        @Signature(method = "query", type = StatementHandler.class, args = {Statement.class, ResultHandler.class})
})
public class ThreeInterceptor implements Interceptor {

    /**
     * mapper.xml 不可以重载,会取包加方法名作为
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.info("Proxy {} Target {}",this.getClass().getName(),invocation.getTarget().getClass());
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

package com.demo.confin;

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.sql.Statement;
import java.util.Properties;

/**
 * @author 
 * @date 2022-10-21 23:53
 * @since 1.8
 */
@Slf4j
@Intercepts({
        @Signature(method = "handleResultSets", type = ResultSetHandler.class,args = {Statement.class})
})
public class FourInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.info("Proxy {} Target {}",this.getClass().getName(),invocation.getTarget().getClass());

        Object result = invocation.proceed();

        //TODO 对结果对象进行加工
        return result;
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

注册顺序:Executor、ResultSetHandler、ParameterHandler、StatementHandler

package com.demo.confin;

import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author
 * @date 2022-10-21 23:19
 * @since 1.8
 */
@Component
public class MybatisInterceptorListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private List<SqlSessionFactory> sqlSessionFactoryList;

    /**
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        for (SqlSessionFactory factory : sqlSessionFactoryList) {
            factory.getConfiguration().addInterceptor(new OneInterceptor());
            factory.getConfiguration().addInterceptor(new FourInterceptor());
            factory.getConfiguration().addInterceptor(new TwoInterceptor());
            factory.getConfiguration().addInterceptor(new ThreeInterceptor());
        }
    }
}

启动服务并调用接口,查看打印结果:127.0.0.1:8093/animal/query

是按1,2,3,4调用的:
在这里插入图片描述
日志内打印了被代理类:CachingExecutor、DefaultParameterHandler、RoutingStatementHandler、DefaultResultSetHandler

2022-10-21 23:36:33.909  INFO 26032 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8093 (http) with context path ''
2022-10-21 23:36:33.924  INFO 26032 --- [           main] com.demo.DemoApplication                 : Started DemoApplication in 2.657 seconds (JVM running for 3.071)
2022-10-21 23:36:41.437  INFO 26032 --- [nio-8093-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-10-21 23:36:41.437  INFO 26032 --- [nio-8093-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-10-21 23:36:41.437  INFO 26032 --- [nio-8093-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
2022-10-21 23:36:41.475  INFO 26032 --- [nio-8093-exec-2] com.demo.confin.OneInterceptor           : Proxy com.demo.confin.OneInterceptor Target class org.apache.ibatis.executor.CachingExecutor
2022-10-21 23:36:41.930 DEBUG 26032 --- [nio-8093-exec-2] com.demo.mapper.AnimalMapper.query       : ==>  Preparing: select id,animal_name,country from public.tb_rabbit_basic
2022-10-21 23:36:42.070  INFO 26032 --- [nio-8093-exec-2] com.demo.confin.TwoInterceptor           : Proxy com.demo.confin.TwoInterceptor Target class org.apache.ibatis.scripting.defaults.DefaultParameterHandler
2022-10-21 23:36:42.071  INFO 26032 --- [nio-8093-exec-2] com.demo.confin.ThreeInterceptor         : Proxy com.demo.confin.ThreeInterceptor Target class org.apache.ibatis.executor.statement.RoutingStatementHandler
2022-10-21 23:36:42.071 DEBUG 26032 --- [nio-8093-exec-2] com.demo.mapper.AnimalMapper.query       : ==> Parameters: 
2022-10-21 23:36:42.075  INFO 26032 --- [nio-8093-exec-2] com.demo.confin.FourInterceptor          : Proxy com.demo.confin.FourInterceptor Target class org.apache.ibatis.executor.resultset.DefaultResultSetHandler
2022-10-21 23:36:42.088 DEBUG 26032 --- [nio-8093-exec-2] com.demo.mapper.AnimalMapper.query       : <==      Total: 4

3.拦截器处理Map

从上面 Executor 类的 query 方法我们可以看到一个 ResultHandler 结果处理类,实现此接口就能自定义数据处理方式,我们尝试一下将查询结果转为 Map 处理

自定义Handler,就是取出所需属性值填充到Map,再用List封装一下

package com.demo.confin.result;

import com.demo.entity.AnimalEntity;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 
 * @date 2022-10-21 23:24
 * @since 1.8
 */
public class MapResultHandler implements ResultHandler<AnimalEntity> {

    /**
     * 定义一个结果:因为如果返回数据为多条,DefaultSqlSession 会自动按List处理,避免报错需要包装一下
     */
    List<Map<Integer, String>> list = new ArrayList<>(1);

    /**
     * 初始化MAP
     */
    Map<Integer, String> map = new HashMap<>(16);

    @Override
    public void handleResult(ResultContext<? extends AnimalEntity> resultContext) {
        map.put(resultContext.getResultObject().getId(),resultContext.getResultObject().getName());
    }

    public Object getResult(){
        list.add(map);
        return list;
    }
}

改造一下 OneInterceptor 仅对指定查询处理,并取消其他三个拦截器的注册

package com.demo.confin;

import com.demo.confin.result.MapResultHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
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.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

/**
 * @author
 * @date 2022-10-21 23:35
 * @since 1.8
 */
@Slf4j
@Intercepts({
        @Signature(method = "query",type = Executor.class,args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class OneInterceptor implements Interceptor {

    /**
     * 可以设为有参构造,并在舒适化时传入一些参数;
     * 如果用依赖诸如的方式则要注意注入顺序
     */
    public OneInterceptor(){

    }

    /**
     *
     * 取出 MappedStatement 并根据 ID 过滤我们要处理的查询方法(全部处理则无需过滤)
     * mapper.xml 不可重载,ID 即包名.类名.方法名,MappedStatement 即 XML 配置文件内某个 SQL 标签的封装类
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.info("Proxy {} Target {}",this.getClass().getName(),invocation.getTarget().getClass());

        //我们签名的方法第一个参数为MappedStatement
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String id = mappedStatement.getId();
        //比如只处理按名字查询
        String queryByName = "com.demo.mapper.AnimalMapper.queryByName";
        if (queryByName.equals(id)){
            //修改参数
            invocation.getArgs()[1] = "兔";
            //添加自定义处理器
            MapResultHandler mapResultHandler = new MapResultHandler();
            invocation.getArgs()[3] = mapResultHandler;
            //调用
            invocation.proceed();
            //返回
            return mapResultHandler.getResult();
        }
        return invocation.proceed();
    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

启动服务,并调用接口(顺便测试参数修改):127.0.0.1:8093/animal/queryByName/测试

在这里插入图片描述

4.为不同SQL动态设置超时

package com.demo.confin;

import com.alibaba.druid.pool.DruidPooledPreparedStatement;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
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.session.ResultHandler;

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

/**
 * @author 
 * @date 2022-10-21 23:49
 * @since 1.8
 */
@Slf4j
@Intercepts({
        @Signature(method = "query", type = StatementHandler.class, args = {Statement.class, ResultHandler.class})
})
public class DynamicSetSqlTimeOutInterceptor implements Interceptor {


    /**
     * TODO
     * 1.定义 Filed,如超时值
     * 2.通过依赖注入、有参构造初始化 Filed
     */

    /**
     * 附加超时
     */
    private int addedTime;

    /**
     * 构造初始化
     * @param addedTime
     */
    public DynamicSetSqlTimeOutInterceptor(int addedTime){
        this.addedTime = addedTime;
    }

    /**
     * 调用存储过程时获取参数的超时配置并重置到SQL配置里
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //获取连接信息
        DruidPooledPreparedStatement statement = (DruidPooledPreparedStatement) invocation.getArgs()[0];

        //TODO 通过SQL内是否包含某些特殊字符判断是否需要动态设置超时
        if (statement.getSql().startsWith("")){

            //取参数
            RoutingStatementHandler target = (RoutingStatementHandler)invocation.getTarget();
            BoundSql boundSql = target.getBoundSql();
            Map param = (Map) boundSql.getParameterObject();
            //TODO 获取超时参数(通过SQL参数传递)
            try {
                int timeOut = (int) param.get("sqlTimeOut");
                //设置超时
                statement.setQueryTimeout(timeOut + addedTime);
                //TODO 缓存超时信息并通过 close 或 cancel 方法主动取消查询 statement statement.close(); statement.cancel();
                Object proceed = invocation.proceed();
                return proceed;
            } finally {
                //TODO 收尾处理
                log.info("处理完成...");
            }
        }

        //非存储过程调用直接返回
        return invocation.proceed();

    }

    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

此处不在演示效果,可通过慢查询或调用存储过程设置超时,测试效果

相关文章:

  • Vue.js 组件精讲(目前已有6614人一起学习)
  • 【Spring】一文带你吃透IOC技术
  • I2C知识大全系列二 —— I2C硬件及时序
  • Python基础入门(持续更新中)
  • 【预测模型-SVM预测】基于粒子群算法结合支持向量机SVM实现Covid-19风险预测附matlab代码
  • 初阶数据结构 二叉树常用函数 (二)
  • 【正点原子I.MX6U-MINI应用篇】4、嵌入式Linux关于GPIO的一些操作
  • 【C语言】解题训练
  • 【蓝桥杯国赛真题04】python输出平方 蓝桥杯青少年组python编程 蓝桥杯国赛真题解析
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • RabbitMQ 集群部署及配置
  • python @classmethod详解
  • JSP超市管理系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc
  • FreeRTOS 软件定时器的使用
  • 软件测试培训到底值不值得参加?
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • 345-反转字符串中的元音字母
  • C学习-枚举(九)
  • Java知识点总结(JavaIO-打印流)
  • spring学习第二天
  • vue-router的history模式发布配置
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 高性能JavaScript阅读简记(三)
  • 给github项目添加CI badge
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 记录:CentOS7.2配置LNMP环境记录
  • 入门到放弃node系列之Hello Word篇
  • 软件开发学习的5大技巧,你知道吗?
  • 设计模式走一遍---观察者模式
  • 中国人寿如何基于容器搭建金融PaaS云平台
  • Hibernate主键生成策略及选择
  • 阿里云重庆大学大数据训练营落地分享
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • # Apache SeaTunnel 究竟是什么?
  • (9)目标检测_SSD的原理
  • (备忘)Java Map 遍历
  • (多级缓存)多级缓存
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (离散数学)逻辑连接词
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (十)T检验-第一部分
  • (数据结构)顺序表的定义
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (转)Linux下编译安装log4cxx
  • (转载)PyTorch代码规范最佳实践和样式指南
  • 、写入Shellcode到注册表上线
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET MVC之AOP
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .NetCore 如何动态路由
  • .NET开发人员必知的八个网站