一、Mybatis插件简介

Mybatis插件运转原理及自定义插件_简述mybatis的插件运转原理,以及如何编写一个插件-CSDN博客

MyBatis 是一款优秀的持久层结构,它简化了数据库操作过程,提供了强壮的 SQL 映射功用。MyBatis 插件是用来扩展 MyBatis 结构功用的工具,能够经过插件来定制和增强 MyBatis 的功用。

MyBatis 插件能够用来完成一些自定义的功用,比如阻拦 SQL 句子、修正 SQL 句子、添加新的功用等。经过插件,咱们能够在 MyBatis 结构的各个阶段进行干预和扩展,然后完成更灵敏、更强壮的功用。

通常情况下,编写一个 MyBatis 插件需求完成 MyBatis 提供的接口,并在装备文件中注册插件。Mybatis只支撑针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口

总的来说,MyBatis 插件是一种扩展机制,能够让咱们更好地定制和增强 MyBatis 结构的功用,使得咱们能够更好地适应各种不同的事务需求。

二、工程创建及前期准备工作

以下都为前期准备工作,可直接略过,插件核心完成代码在第三节

手写Mybatis主动填充插件

手写Mybatis主动填充插件

创建test数据库

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

完成代码

本试验省略了Controller和Service

User.java

package com.example.mybatisplugin.entity;
​
import com.example.mybatisplugin.anno.FiledFill;
​
import java.io.Serial;
import java.time.LocalDateTime;
import java.util.Date;
import java.io.Serializable;
​
/**
 * (User)实体类
 *
 * @author makejava
 * @since 2024-03-11 14:41:35
 */
public class User implements Serializable {
  @Serial
  private static final long serialVersionUID = 813676794892349198L;
​
  private Integer id;
​
  private String username;
​
  private String password;
  @FiledFill(fill = FiledFill.FillType.INSERT)
  private Date createTime;
​
  @FiledFill(fill = FiledFill.FillType.INSERT_UPDATE)
  private Date updateTime;
  
    //省略了getter和setter办法@Override
  public String toString() {
    return "User{" +
        "id=" + id +
        ", username='" + username + ''' +
        ", password='" + password + ''' +
        ", createTime=" + createTime +
        ", updateTime=" + updateTime +
        '}';
   }
}
​
​

其中@FiledFill注解是自定义注解,文章后边会写到,可暂时不用添加

UserDao.java

@Mapper
public interface UserDao {
    /**
     * 新增数据
     *
     * @param user 实例对象
     * @return 影响行数
     */
    int insert(User user);
    /**
     * 修正数据
     *
     * @param user 实例对象
     * @return 影响行数
     */
    int update(User user);
}

UserDao.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.example.mybatisplugin.dao.UserDao">
    <resultMap type="com.example.mybatisplugin.entity.User" id="UserMap">
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="username" column="username" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
        <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
    </resultMap>
    <!--新增一切列-->
    <insert id="insert" keyProperty="id" useGeneratedKeys="true">
        insert into user(username,password,create_time,update_time)
        values (#{username},#{password},#{createTime},#{updateTime})
    </insert>
    <!--经过主键修正数据-->
    <update id="update">
        update user
        <set>
            <if test="username != null and username != ''">
                username = #{username},
            </if>
            <if test="password != null and password != ''">
                password = #{password},
            </if>
            <if test="createTime != null">
                create_time = #{createTime},
            </if>
            <if test="updateTime != null">
                update_time = #{updateTime},
            </if>
        </set>
        where id = #{id}
    </update>
</mapper>

装备文件

application.yaml 数据库暗码记住改成自己的

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: *****
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
server:
  port: 8080
mybatis:
  config-location: classpath:mybatis-config.xml
  mapper-locations: classpath:mapper/*.xml

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>
    <settings>
        <!-- 设置驼峰标识 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 打印SQL句子 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>

三、插件核心代码完成

自定义注解FiledFill

package com.example.mybatisplugin.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author YZK
 * @Date 2024/3/11
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FiledFill {
    enum FillType {
        INSERT, INSERT_UPDATE,
    }
    FillType fill();
}

插件核心代码

package com.example.mybatisplugin.plugin;
import com.example.mybatisplugin.anno.FiledFill;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
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.util.Arrays;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * @Author YZK
 * @Date 2024/3/11
 * @Desc
 */
@Intercepts({@Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class})})
public class MybatisAutoFill implements Interceptor {
    private static final Logger LOGGER = Logger.getLogger(MybatisAutoFill.class.getName());
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        //获取操作类型(INSERT和UPDATE)
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        //拿到SQL中传入的对象
        Object obj = invocation.getArgs()[1];
        //获取其字节对象
        Class<?> clazz = obj.getClass();
        //获取User类声明的一切字段
        Field[] fields = clazz.getDeclaredFields();
        //经过区分SQL的操作来进行不同的字段填充
        if (sqlCommandType == SqlCommandType.INSERT) {
            //是INSERT操作的话就同时填充createTime和updateTime字段
            fillInsertFields(obj, fields);
        } else if (sqlCommandType == SqlCommandType.UPDATE) {
            //是updateTime字段的话就只填充updateTime字段
            fillUpdateFields(obj, fields);
        }
        return invocation.proceed();
    }
    private void fillInsertFields(Object obj, Field[] fields) {
        Arrays.stream(fields)
            	//过滤出一切带有@FiledFill注解的字段
                .filter(field -> field.isAnnotationPresent(FiledFill.class))
                .forEach(field -> {
                    try {
                        //对字段进行填充
                        setFieldValue(obj, field);
                    } catch (IllegalAccessException e) {
                        LOGGER.log(Level.SEVERE, "字段填充过错", e);
                    }
                });
    }
    private void fillUpdateFields(Object obj, Field[] fields) {
        Arrays.stream(fields)
            	//过滤出一切带有@FiledFill注解的字段,以及注解值为INSERT_UPDATE的字段
                .filter(field -> field.isAnnotationPresent(FiledFill.class) &&
                        field.getAnnotation(FiledFill.class).fill() == FiledFill.FillType.INSERT_UPDATE)
                .forEach(field -> {
                    try {
                        //对字段进行填充
                        setFieldValue(obj, field);
                    } catch (IllegalAccessException e) {
                        LOGGER.log(Level.SEVERE, "字段填充过错", e);
                    }
                });
    }
    private void setFieldValue(Object obj, Field field) throws IllegalAccessException {
        //填充字段
        field.setAccessible(true);
        field.set(obj, new Date());
    }
}

mybatis-condig.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>
    <settings>
        <!-- 设置驼峰标识 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 打印SQL句子 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <plugins>
        <plugin interceptor="com.example.mybatisplugin.plugin.MybatisAutoFill"/>
    </plugins>
</configuration>

四、测验

插入操作

@Test
void contextLoads() {
    User user = new User();
    user.setUsername("笑的像个child");
    user.setPassword("123456");
    userDao.insert(user);
}

控制台打印的SQL

手写Mybatis主动填充插件

手写Mybatis主动填充插件

进行插入操作时create_time和update_time字段被同时填充

更新操作

    @Test
    void contextLoads() {
        User user = new User();
        user.setId(33);
        user.setUsername("笑的像个child");
        user.setPassword("12345678");
        userDao.update(user);
    }

控制台打印的SQL

手写Mybatis主动填充插件

手写Mybatis主动填充插件

进行更新时操作时update_time字段被填充

进行删去操作时,如果是硬删去,则记载被删去,软删去时同样是更新操作,字段也会被主动填充。

本插件还有许多需求完善的地方,仅仅主动填充的简略完成,如有需求,能够自己完善。