目录
- 一、Mybatis插件简介🥙
- 二、工程创建及前期准备工作🥫
- 实现代码
- 配置文件
- 三、插件核心代码实现🍗
- 四、测试🥓
一、Mybatis插件简介🥙
Mybatis插件运行原理及自定义插件_简述mybatis的插件运行原理,以及如何编写一个插件-CSDN博客
MyBatis 是一款优秀的持久层框架,它简化了数据库操作过程,提供了强大的 SQL 映射功能。MyBatis 插件是用来扩展 MyBatis 框架功能的工具,可以通过插件来定制和增强 MyBatis 的功能。
MyBatis 插件可以用来实现一些自定义的功能,比如拦截 SQL 语句、修改 SQL 语句、添加新的功能等。通过插件,我们可以在 MyBatis 框架的各个阶段进行干预和扩展,从而实现更灵活、更强大的功能。
通常情况下,编写一个 MyBatis 插件需要实现 MyBatis 提供的接口,并在配置文件中注册插件。Mybatis只支持针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口
总的来说,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
进行插入操作时create_time和update_time字段被同时填充
更新操作
@Test
void contextLoads() {
User user = new User();
user.setId(33);
user.setUsername("笑的像个child");
user.setPassword("12345678");
userDao.update(user);
}
控制台打印的SQL
进行更新时操作时update_time字段被填充
进行删除操作时,如果是硬删除,则记录被删除,软删除时同样是更新操作,字段也会被自动填充。
本插件还有许多需要完善的地方,只是自动填充的简单实现,如有需要,可以自己完善。