手写Mybatis自动填充插件

news2024/11/16 2:27:32

目录

  • 一、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字段被填充

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 导入Excel三维坐标数据 生成三维曲面地形图(体) 5-2、线条平滑曲面且可通过面观察柱体变化(二)

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata fro…

类比半导体基于高边驱动方案选型

一、高边驱动简介 高边驱动&#xff0c;也称之为高边开关&#xff0c;其主要用于车内负载的驱动与开关&#xff0c;并对负载进行保护和诊断。高边驱动以高可靠性、灵活性、低功耗以及小型轻量等特点&#xff0c;正逐渐替代传统的保险丝、继电器等方案。 随着新能源汽车的渗透…

【蓝桥杯-单片机】基础模块:数码管

文章目录 【蓝桥杯-单片机】基础模块&#xff1a;数码管01 数码管原理图什么是位选和段选共阳极数码管和共阴极数码管的区分&#xff08;1&#xff09;共阳极数码管&#xff08;Common Anode&#xff09;&#xff1a;&#xff08;2&#xff09;共阴极数码管&#xff08;Common …

ThingsBoard Edge 安装部署(Docker)

文章目录 一、概述1.官方文档2.部署说明3.安装准备3.1. 克隆服务器3.2.安装 Docker3.3.安装 docker-compose3.4.安装 PostgreSQL3.5.创建 Edge 实例 二、Docker Compose 方式部署1.创建 docker-compose.yml2.运行容器3.访问 Edge 三、Docker 直接部署1.创建数据库2.运行容器3.访…

Cassandra经常被问到的问题 Java面试题

1. 请简要介绍一下Cassandra是什么&#xff0c;以及它的主要特点是什么&#xff1f; Cassandra是一个高度可扩展、分布式的NoSQL数据库系统&#xff0c;最初由Facebook开发并开源。它设计用于处理大规模数据&#xff0c;具有以下主要特点&#xff1a; 分布式架构&#xff1a;C…

浏览器事件循环机制、宏任务和微任务

浏览器的事件循环机制&#xff08;重要&#xff09; image-20230608154453933 执行顺序如下&#xff1a; 同步任务&#xff1a;进入主线程后&#xff0c;立即执行。 异步任务&#xff1a;会先进入 Event Table&#xff1b;等时间到了之后&#xff0c;再进入 任务队列 &#x…

探索 Spring 框架:企业级应用开发的强大工具

CSDN-个人主页&#xff1a;17_Kevin-CSDN博客 收录专栏&#xff1a;《Java》 目录 一、引言 二、Spring 框架的历史 三、Spring 框架的核心模块 四、Spring 框架的优势 五、Spring 框架的应用场景 六、结论 一、引言 在当今数字化时代&#xff0c;企业级应用开发的需求日…

腾讯春招后端一面(八股篇)

前言 前几天在网上发了腾讯面试官问的一些问题&#xff0c;好多小伙伴关注&#xff0c;今天对这些问题写个具体答案&#xff0c;博主好久没看八股了&#xff0c;正好复习一下。 面试手撕了三道算法&#xff0c;这部分之后更&#xff0c;喜欢的小伙伴可以留意一下我的账号。 1…

【刷题训练】LeetCode125. 验证回文串

验证回文串 题目要求 示例 1&#xff1a; 输入: s “A man, a plan, a canal: Panama” 输出&#xff1a;true 解释&#xff1a;“amanaplanacanalpanama” 是回文串。 示例 2&#xff1a; 输入&#xff1a;s “race a car” 输出&#xff1a;false 解释&#xff1a;“rac…

elasticsearch8.12 分词器安装

分词器的主要作用将用户输入的一段文本&#xff0c;按照一定逻辑&#xff0c;分析成多个词语的一种工具 分词器下载地址 analysis-ik Releases infinilabs/analysis-ik GitHub 一个简便 安装方式 安装完成之后 会提示重启&#xff0c;重启es即可 ./bin/elasticsearch-pl…

19C 19.22 RAC 2节点一键安装演示

Oracle 一键安装脚本&#xff0c;演示 2 节点 RAC 一键安装过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 GRID/ORALCE PSU/OJVM 补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;Shell脚本安装Oracle数据库 脚本第三代支持 N 节点…

Excel小技巧 (3) - 如何取整

1. 四舍五入 Round&#xff08;对象&#xff0c;小数点后位数&#xff09; 结果 123.1 2.向上取整 Roundup&#xff08;对象&#xff0c;小数点后位数&#xff09; 结果&#xff1a;123.2 3.向下取整 Round&#xff08;对象&#xff0c;小数点后位数&#xff09; 结果123.…

每日一题 2312卖木头快

2312. 卖木头块 题目描述&#xff1a; 给你两个整数 m 和 n &#xff0c;分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices &#xff0c;其中 prices[i] [hi, wi, pricei] 表示你可以以 pricei 元的价格卖一块高为 hi 宽为 wi 的矩形木块。 每一次操作中&am…

AJAX 05 axios拦截器、数据管理平台

AJAX 学习 AJAX 05 黑马头条-数据管理平台项目准备业务1&#xff1a;验证码登录bootstrap提示框实际业务中的验证码登录token 【注】HTML遗落的知识【注】JS遗漏的知识业务2&#xff1a;个人信息设置 & axios拦截器axios请求拦截器axios响应拦截器 业务3&#xff1a;发布文…

网络架构层_服务器上下行宽带

网络架构层_服务器上下行宽带 解释一 云服务器ECS网络带宽的概念、计费、安全及使用限制_云服务器 ECS(ECS)-阿里云帮助中心 网络带宽是指在单位时间&#xff08;一般指的是1秒钟&#xff09;内能传输的数据量&#xff0c;带宽数值越大表示传输能力越强&#xff0c;即在单位…

mysql 主从延迟分析

一、如何分析主从延迟 分析主从延迟一般会采集以下三类信息。 从库服务器的负载情况 为什么要首先查看服务器的负载情况呢&#xff1f;因为软件层面的所有操作都需要系统资源来支撑。 常见的系统资源有四类&#xff1a;CPU、内存、IO、网络。对于主从延迟&#xff0c;一般会…

提前十分钟!有方法论的人和没有方法论的人,谁更从容?弱者不应被错误引导——早读(逆天打工人爬取热门微信文章解读)

熬夜不熬夜&#xff0c;取决于你的生活态度 引言Python 代码第一篇 人民日报 提前十分钟&#xff0c;人生大不同第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 君子如潜龙&#xff0c;藏器待时发 紧握时间的脉搏&#xff0c;提前规划十分钟 既显对他人的敬意&#xff0c;亦…

并发编程之创建线程的几种方式以及运行的详细解析

3.1 创建和运行线程 方法一&#xff0c;直接使用 Thread // 创建线程对象 Thread t new Thread() {public void run() {// 要执行的任务} }; // 启动线程 t.start(); 例如&#xff1a; // 构造方法的参数是给线程指定名字&#xff0c;推荐 Thread t1 new Thread("t1…

04- 基于SpringAMQP封装RabbitMQ,消息队列的Work模型和发布订阅模型

SpringAMQP 概述 使用RabbitMQ原生API在代码中设置连接MQ的参数比较繁琐,我们更希望把连接参数写在yml文件中来简化开发 SpringAMQP是基于AMQP协议定义的一套API规范,将RabbitMQ封装成一套模板用来发送和接收消息 AMQP(Advanced Message Queuing Portocol)是用于在应用程序…

go语言基础笔记

1.基本类型 1.1. 基本类型 bool int: int8, int16, int32(rune), int64 uint: uint8(byte), uint16, uint32, uint64 float32, float64 string 复数&#xff1a;complex64, complex128 复数有实部和虚部&#xff0c;complex64的实部和虚部为32位&#xff0c;complex128的实部…