手写Mybatis自动填充插件

目录

  • 一、Mybatis插件简介🥙
  • 二、工程创建及前期准备工作🥫
    • 实现代码
    • 配置文件
  • 三、插件核心代码实现🍗
  • 四、测试🥓

一、Mybatis插件简介🥙

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

MyBatis 是一款优秀的持久层框架,它简化了数据库操作过程,提供了强大的 SQL 映射功能。MyBatis 插件是用来扩展 MyBatis 框架功能的工具,可以通过插件来定制和增强 MyBatis 的功能。

MyBatis 插件可以用来实现一些自定义的功能,比如拦截 SQL 语句、修改 SQL 语句、添加新的功能等。通过插件,我们可以在 MyBatis 框架的各个阶段进行干预和扩展,从而实现更灵活、更强大的功能。

通常情况下,编写一个 MyBatis 插件需要实现 MyBatis 提供的接口,并在配置文件中注册插件。Mybatis只支持针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口

总的来说,MyBatis 插件是一种扩展机制,可以让我们更好地定制和增强 MyBatis 框架的功能,使得我们能够更好地适应各种不同的业务需求。

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

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

PixPin_2024-03-11_14-26-17

PixPin_2024-03-11_14-27-44

创建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

image-20240311225847571

image-20240311225857627

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

更新操作

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

控制台打印的SQL

image-20240311225948551

image-20240311230032331

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

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

本插件还有许多需要完善的地方,只是自动填充的简单实现,如有需要,可以自己完善。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/457814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java8 Stream流详解

文章目录 概念和特点基本流程常见操作过滤&#xff08;filter&#xff09;映射&#xff08;map&#xff09;收集&#xff08;collect&#xff09;归约&#xff08;reduce&#xff09;扁平映射&#xff08;flatMap&#xff09; java案例详解注意事项 概念和特点 Java中的Stream是…

Qt 线程池 QThreadPool

一.Qt 线程池 QThreadPool介绍 Qt线程池是一种管理多个线程的并发编程模型&#xff0c;通过使用线程池可以提高性能、控制并发度、提供任务队列和简化线程管理。 在Qt中&#xff0c;线程池的使用主要涉及以下几个步骤&#xff1a; 创建任务类&#xff1a;需要定义一个任务类&am…

PGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI接收OSD动态字符叠加输出应用本方案的SDI接收HLS多路视频融…

AJAX 03 XMLHttpRequest、Promise、封装简易版 axios

AJAX 学习 AJAX 3 原理01 XMLHttpRequest① XHR 定义② XHR & axios 关系③ 使用 XHR④ XHR查询参数案例&#xff1a;地区查询&#xff08;URLSearchParams&#xff09;⑤ XHR数据提交 POST 02 PromisePromise 使用Promise - 三种状态案例&#xff1a;使用Promise XHR 获取…

sqllab第二十一关通关笔记

知识点&#xff1a; 错误注入 最大长度为32超过需要利用截取函数分段读取cookie注入base64加密会保留符号的原始属性 通过admin admin进行登录发现和第二十关显示的内容一样&#xff0c;猜测应该还是cookie注入&#xff1b; 直接截取带有cookie的数据包&#xff0c;发现uname…

SpringBoot异常:类文件具有错误的版本 61.0, 应为 52.0的解决办法

问题&#xff1a; java: 无法访问org.mybatis.spring.annotation.MapperScan 错误的类文件: /D:/Program Files/apache-maven-3.6.0/repository/org/mybatis/mybatis-spring/3.0.3/mybatis-spring-3.0.3.jar!/org/mybatis/spring/annotation/MapperScan.class 类文件具有错误的…

YOLO_项目环境配置

YOLOv5官方项目地址 https://github.com/ultralytics/yolov5 下载 5.0和1.0源码 5.0 master-Tags-v5.0 Code-Download.ZIP 切换到1.0下载 解压缩提取 打开V5.0 使用Pycharm打开V5.0的文件夹 环境配置 参考 http://t.csdnimg.cn/Zdfh2 http://t.csdnimg.cn/Nqkwr 然后在Pyc…

AVCE - AV Evasion Craft Online 更新 8 种加载方式 - 过 WD 等

免责声明&#xff1a;本工具仅供安全研究和教学目的使用&#xff0c;用户须自行承担因使用该工具而引起的一切法律及相关责任。作者概不对任何法律责任承担责任&#xff0c;且保留随时中止、修改或终止本工具的权利。使用者应当遵循当地法律法规&#xff0c;并理解并同意本声明…

Redis:持久化、线程模型、大 key

Redis持久化方式有什么方式&#xff1f; Redis 的读写操作都是在内存中&#xff0c;所以 Redis 性能才会高&#xff0c;但是当 Redis 重启后&#xff0c;内存中的数据就会丢失&#xff0c;那为了保证内存中的数据不会丢失&#xff0c;Redis 实现了数据持久化的机制&#xff0c…

Linux的一些常用指令

一、文件中 r w x - 的含义 r&#xff08;read&#xff09;是只读权限&#xff0c; w&#xff08;write&#xff09;是写的权限&#xff0c; x&#xff08;execute&#xff09;是可执行权限&#xff0c; -是没有任何权限。 二、一些指令 # 解压压缩包 tar [-zxvf] 压缩包名…

【计算机网络】概述

文章目录 一、Internet 因特网1.1 网络、互联网、因特网1.2 因特网的组成 二、三种交换方式2.1 电路交换 &#xff08;Circuit Switching&#xff09;2.2 *分组交换 &#xff08;Packet Switching&#xff09;2.3 报文交换 &#xff08;Message Switching&#xff09; 三、计算…

2023版IDEA永久破解教程带patch.exe破解程序

2023版IDEA永久破解教程带patch.exe破解程序 第零步&#xff1a;百度云盘获取程序第一步&#xff1a;关闭电脑的病毒和危险防护&#xff08;目的是避免电脑自动清除破解程序&#xff09;1.找到电脑的 病毒和威胁防护2.蓝色按钮表示防护处于开启状态3.关闭成功会展示“实时保护已…

Ansible非标记语言YAML与任务剧本Playbook

前言 上篇介绍了 Ansible 单模块&#xff08;AD-Hoc&#xff09;的相关内容Ansible自动化运维Inventory与Ad-Hoc-CSDN博客&#xff0c;Ad-Hoc 命令是一次性的、即时执行的命令&#xff0c;用于在远程主机上执行特定任务&#xff0c;这些命令通常用于快速执行简单的任务。当需要…

软件无线电系列——模拟无线电、数字无线电、软件无线电

本节目录 一、模拟无线电 二、数字无线电 1、窄带数字无线电 2、宽带数字无线电 三、软件无线电本节内容 一、模拟无线电 20世纪80年代的模拟体制(美国的AMPS/欧洲的TACS)被称为第一代移动通信&#xff0c;简称1G,主要目标是为在大范围内有限的用户提供移动电话服务。最主要的…

React——react 的基本使用

前提&#xff1a;安装全局的脚手架&#xff0c;通过create-creat-app 项目名&#xff0c;我们创建好一个新项目&#xff0c;cd进去&#xff0c;通过npm start去运行该项目 注意&#xff1a;简单看下demo的配置&#xff0c;在根目录我们可以看到&#xff0c;没有任何webpack的…

【b站咸虾米】2 Vue基础(下) 2021最新Vue从基础到实例高级_vue2_vuecli脚手架博客案例

课程地址&#xff1a;【2021最新Vue从基础到实例高级_vue2_vuecli脚手架博客案例】 https://www.bilibili.com/video/BV1pz4y1S7bC/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 2 Vue基础 下 2.8 计算属性 2.8.1 计算属性使用 2.8.2 计算…

整型变量的原子操作

什么是原子操作 原子操作&#xff08;Atomic Operation&#xff09;是指不可中断的操作&#xff0c;即在多线程环境下&#xff0c;当一个线程在执行原子操作时&#xff0c;不会被其他线程的调度和中断所影响。这种操作在多线程编程中尤为重要&#xff0c;因为它能保证操作的原…

Git如何清除账户凭证

场景&#xff1a;一般发生在Git用户变更的情况 1.git base 操作 Git会使用凭证助手 credential.helper来储存账户凭证&#xff0c;通过以下命令移除&#xff1a; git config --system --unset credential.helper 除了system系统级外&#xff0c;还有 global、local范围。 查…

直方图均衡化原理和实现

基本思想 将原始图像的直方图分布转换为一个均匀分布的直方图&#xff0c;这样原图中的高频率亮度值会被展宽&#xff0c;而低频率亮度值则被压缩&#xff0c;从而达到增强图像对比度的效果。 计算过程 假设我们有一个灰度图像&#xff0c;其像素值范围从0到L-1&#xff08;…

云计算 3月11号 (NFS远程共享存储及vsftpd配置)

构建NFS远程共享存储 一、NFS介绍 文件系统级别共享&#xff08;是NAS存储&#xff09; --------- 已经做好了格式化&#xff0c;可以直接用。 速度慢比如&#xff1a;nfs&#xff0c;sambaNFS NFS&#xff1a;Network File System 网络文件系统&#xff0c;NFS 和其他文件…