笔记

万物寻其根,通其堵,便能解其困。
  博客园  :: 新随笔  :: 管理

mybits拦截器/重写SqlSessionFactory

Posted on 2024-06-12 15:47  草妖  阅读(5)  评论(0)    收藏  举报

*\FirstSBDemo\pom.xml

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.namejr</groupId>
    <artifactId>FirstSBDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- 注意,这里需要排除web自带的logging依赖-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.14</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.11</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!-- 重写数据库的DruidDataSource,和mybatis版本要关联 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- 补充排除的log4j -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <!-- 注册webapp目录为资源目录 -->
                <directory>src/main/webapp</directory>
                <targetPath>META-INF/resources</targetPath>
                <includes>
                    <include>**/**</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
                </configuration>
            </plugin>
            <plugin>
                <!-- 配置jar包打包工具 -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <webResources>
                        <resource>
                            <directory>${project.basedir}/libs</directory>
                            <targetPath>WEB-INF/lib</targetPath>
                            <includes>
                                <include>**/*.jar</include>
                            </includes>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

*\FirstSBDemo\src\main\java\com\namejr\StartApplication.java

package com.namejr;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@SpringBootApplication
@EnableWebMvc
@MapperScan({"com.namejr.dao"})
@ServletComponentScan({"com.namejr.base","com.namejr.controller","com.namejr.service"})
public class StartApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(StartApplication.class, args);
    }
    
    protected SpringApplicationBuilder configuer(SpringApplicationBuilder builder) {
        return super.configure(builder);
    }
}

*\FirstSBDemo\src\main\java\com\namejr\dao\PublicDao.java

package com.namejr.dao;

import org.springframework.stereotype.Repository;

import java.util.HashMap;
import java.util.List;

/*
 * 公共Dao
 * */
@Repository
public interface PublicDao {
    // 获取obs信息
    List<HashMap<String,String>> getResGuids();
}

*\FirstSBDemo\src\main\java\com\namejr\base\DatabaseConfig.java

package com.namejr.base;

import org.apache.ibatis.io.VFS;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
 * 数据库配置连接处理
 * */
@Configuration
public class DatabaseConfig {
    @Value("${mybatis.type-aliases-package}")
    private String typeAliasesPackage;
    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
        if(mapperLocations.contains(",")){
            String[] tempMapperLocations=mapperLocations.split(",",-1);
            List<Resource> tempResourceList=new ArrayList<>();
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            for(int i= 0;i<tempMapperLocations.length;i++){
                tempResourceList.addAll(Arrays.asList(resolver.getResources(tempMapperLocations[i])));
            }
            Resource[] tempResourceInfos=new Resource[tempResourceList.size()];
            for(int i=0;i<tempResourceList.size();i++){
                tempResourceInfos[i]=tempResourceList.get(i);
            }
            sessionFactory.setMapperLocations(tempResourceInfos);
        }else {
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            sessionFactory.setMapperLocations(resolver.getResources(mapperLocations));
        }
        /*
        // configLocatoin=》mybatis.config-location: classpath:mybatis-config.xml
        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
        */
        SqlSessionFactory result = sessionFactory.getObject();
        // 注册自定义拦截器
        result.getConfiguration().addInterceptor(new SqlSessionFactoryInterceptor());
        return result;
    }
}

*\FirstSBDemo\src\main\java\com\namejr\base\SqlSessionFactoryInterceptor.java

package com.namejr.base;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
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 java.sql.Statement;
import java.util.Properties;

/** MyBatis的拦截器
 * 笔记参照地址:MyBatis Interceptor - Milton - 博客园 (cnblogs.com)
 * @Intercepts 描述:标志该类是一个拦截器
 *      注解: 这个地方配置需要拦截的对象方法, 每个方法对应一个Signature, 这个Signature由对象类型, 方法名和参数组成, 方法名和参数可以直接参考这个对象的接口.
 * @Signature 描述:指明该拦截器需要拦截哪一个接口的哪一个方法
 *      type:四种类型接口中的某一个接口,四种接口类型分别(拦截顺序也按这个)为Executor、StatementHandler、ParameterHandler和ResultSetHandler,如Executor.class;
 *      method:对应接口中的某一个方法名,比如Executor的query方法;
 *      args:对应接口中的某一个方法的参数,比如Executor中query方法因为重载原因,有多个,args就是指明参数类型,从而确定是具体哪一个方法;
 * Object plugin(Object target)接口: 用于封装目标对象, 可以返回目标对象本身也可以返回一个它的代理, 这将决定是否会进行拦截
 * void setProperties(Properties properties)接口: 用于配置本插件的相关属性, 值可以通过Mybatis配置文件传入
 * Object intercept(Invocation invocation) throws Throwable接口: 执行拦截的方法, 其中 invocation.getTarget() 可以看到实际被代理的对象, 根据对象类型不同, 可以读取这个对象方法的参数并进行需要的操作.
 * */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "batch", args = { Statement.class}),
        @Signature(type = StatementHandler.class, method = "update", args = { Statement.class}),
        @Signature(type = StatementHandler.class, method = "query", args = { Statement.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class SqlSessionFactoryInterceptor implements Interceptor {
    /**
     * 用于封装目标对象, 可以返回目标对象本身也可以返回一个它的代理, 这将决定是否会进行拦截
     * 注:MyBatis拦截器用到责任链模式+动态代理+反射机制;所有可能被拦截的处理类都会生成一个代理类,如果有N个拦截器,就会有N个代理,层层生成动态代理是比较耗性能的。而且虽然能指定插件拦截的位置,但这个是在执行方法时利用反射动态判断的,初始化的时候就是简单的把拦截器插入到了所有可以拦截的地方。所以尽量不要编写不必要的拦截器。另外我们可以在调用插件的地方添加判断,只要是当前拦截器拦截的对象才进行调用,否则直接返回目标对象本身,这样可以减少反射判断的次数,提高性能。
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            // 仅当对象为Executor时, 才使用本插件
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    /**
     * 用于配置本插件的相关属性
     */
    @Override
    public void setProperties(Properties properties) {
        // Do nothing
    }

    /** 执行拦截处理 **/
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        Object target = invocation.getTarget(); //被代理对象
        // Object method = invocation.getMethod();  // 被代理方法
        // Object ags = invocation.getArgs();  // 方法参数
        if (target instanceof Executor) {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
            Object parameter = invocation.getArgs()[1];
            /** 几个核心部件:(建议跳转Mybatis——拦截器Interceptor_mybatis 拦截器-CSDN博客查看详情,注释仅作笔记处理)
             * Configuration:初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中。
             * SqlSessionFactory:SqlSession工厂。
             * SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要的数据库增删改查功能。
             * Executor:MyBatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过ResultSetHandler进行自动映射,另外,它还处理二级缓存的操作。
             * StatementHandler:MyBatis直接在数据库执行SQL脚本的对象。另外它也实现了MyBatis的一级缓存。
             * ParameterHandler:负责将用户传递的参数转换成JDBC Statement所需要的参数。是MyBatis实现SQL入参设置的对象。
             * ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合。是MyBatis把ResultSet集合映射成POJO的接口对象。
             * TypeHandler:负责Java数据类型和JDBC数据类型之间的映射和转换。
             * MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装。
             * SqlSource :负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回。
             * BoundSql:表示动态生成的SQL语句以及相应的参数信息。
             * **/
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String sql = boundSql.getSql();
            System.out.println("SQL: "+sql);
        }
        Object result = invocation.proceed();  // 真正调用被代理方法
        long duration = System.currentTimeMillis() - start;
        System.out.println("Time elapsed: "+duration+"ms");
        return result;
    }
}

*\FirstSBDemo\src\main\java\com\namejr\controller\PublicController.java

package com.namejr.controller;

import com.namejr.dao.PublicDao;
import com.namejr.serviceImpl.PublicServiceImpl;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping(value = "/api/public")
@Validated
public class PublicController {
    @Autowired
    private PublicServiceImpl pServiceImpl;
    @Autowired
    private PublicDao pDao;

    @RequestMapping(value = "/getServerTime", method = RequestMethod.GET,produces = "application/json;charset=UTF-8")
    public String getServerTime() {
        return DateTime.now().toString("yyyy-MM-dd HH:mm:ss");
    }

    @RequestMapping(value = "/getDBInfos", method = RequestMethod.GET,produces = "application/json;charset=UTF-8")
    public String getDBInfos() {
        List<HashMap<String,String>> tempOBSInfos= pDao.getResGuids();
        if(tempOBSInfos!=null&&!tempOBSInfos.isEmpty()){
            tempOBSInfos.forEach((Map<String,String> tempOBSInfo)->{
                System.out.println(tempOBSInfo.get("res_guid"));
            });
            return "successful";
        }
        return "failed";
    }
}

*\FirstSBDemo\src\main\resources\application.properties

server.port=8080
logging.config=classpath:logback-spring.xml


# mysql信息配置(需要根据实际配置)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testDB?characterEncoding=UTF-8&allowMultiQueries=true&&useAffectedRows=true&allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=namejr
spring.datasource.password=000000

mybatis.type-aliases-package=ccom.namejr.bean
mybatis.mapper-locations=classpath:/dao/*.xml

*\FirstSBDemo\src\main\resources\dao\PublicDao.xml

<?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.namejr.dao.PublicDao">
    <select id="getResGuids" resultType="java.util.HashMap">
        select distinct res_guid from ques_resource limit 1;
    </select>
</mapper>

访问输出:http://127.0.0.1:8080/api/public/getDBInfos

SQL: select distinct res_guid from ques_resource limit 1;
Time elapsed: 183ms
dt-c304326b1eb54e8594c5cc35f76a04ae