在 MyBatis-Plus 3.x 中,自动填充的插件方式发生了变化。现在推荐使用 MetaObjectHandler 接口的实现类来定义字段的填充逻辑。以下是使用 MyBatis-Plus 3.x 自动填充的基本步骤:
1.基本配置
1.1添加 Maven 依赖:
确保你的 Maven 依赖中使用的是 MyBatis-Plus 3.x 版本。
<dependencies>
<!-- MyBatis-Plus 核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
<!-- 其他依赖... -->
</dependencies>
1.2 配置 MyBatis-Plus 自动填充
1.2.1使用@Component注解
创建一个实现 MetaObjectHandler 接口的配置类,用于定义字段填充逻辑。
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
这种方式是 MyBatis-Plus 3.x 中推荐的字段自动填充方式。在这个示例中,MyMetaObjectHandler 类上添加了 @Component 注解,确保 Spring Boot 能够自动扫描到并注册为 Bean。这样,MyBatis-Plus 在执行插入和更新操作时会自动调用 MetaObjectHandler 中的对应方法进行字段填充
1.2.1 使用@Bean+@Configuration注解
当然也可以使用@bean的方式在mybatisConfig里面注入
import com.sky.handler.MyMetaObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MyMetaObjectHandler myMetaObjectHandler() {
return new MyMetaObjectHandler();
}
}
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入时自动填充");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新时自动填充");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
但是用这种方式的时候需要注意MyMetaObjectHandler这个类前面不能加Component,否则会造成bean冲突。
定义实体类:
在实体类中定义需要自动填充的字段:
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("your_table_name")
public class YourEntity {
@TableId
private Long id;
private String name;
// 其他字段省略...
// createTime 和 updateTime 字段将由 MyBatis-Plus 自动填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
确保你的实体类中的字段类型和配置的 MetaObjectHandler 中的类型一致。
。
这样达到的实际效果就是
我们在封装好入库的对象的时候,没有setUpdateTime这个属性,那么执行intsert 和 update操作的之后,数据库中也会向UpdateTime存入值。
下面我们需要对一些细节说明一下
2. 严格模式与非严格模式
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
在这个示例中,MyMetaObjectHandler 实现了 MetaObjectHandler 接口,并在 insertFill 和 updateFill 方法中定义了字段填充的逻辑。strictInsertFill 和 strictUpdateFill 方法用于严格模式的字段填充。
非严格模式: 在非严格模式下,字段填充通常会在任何情况下都执行,即使字段的值已经被手动设置或者数据库中已经有了一个值。这样可能导致字段值被多次更新,。
严格模式: 相比之下,严格模式会更加谨慎。在严格模式下,字段填充只会在满足一定条件的情况下才执行。例如,在插入操作时,只有在字段的值为 null 时才会进行填充。在更新操作时,只有在字段的值为 null 或者被标记为需要更新时才会进行填充。
3.自动插入的时机
上述的自动填充操作是发生在数据库层面的
具体说是在 MyBatis 执行 SQL 语句时,MyBatis-Plus 框架会拦截这些 SQL 操作,根据配置的自动填充规则来动态生成相应的字段值,然后执行相应的 SQL 操作。这样可以在数据库层面确保这些字段的值符合预期。
插入操作: 当执行插入 SQL 语句时,MyBatis-Plus 拦截器会在插入前执行 MetaObjectHandler 的 insertFill 方法,填充相应字段的值,然后将填充后的 SQL 语句发送给数据库执行。
更新操作: 同理,对于更新 SQL 语句,MyBatis-Plus 会在更新前执行 MetaObjectHandler 的 updateFill 方法,填充相应字段的值,然后将填充后的 SQL 语句发送给数据库执行。
这样做的好处是在数据库层面确保了字段填充的一致性,避免了手动在 Service 层面或者 Controller 层面进行填充,减少了代码冗余和错误的可能性。这也是 MyBatis-Plus 提供的一种便捷的开发方式,使得开发者可以更专注于业务逻辑而不用过多关心数据库层面的操作。
注意:上述操作并不是废话,我们来看这样一个例子(这个例子是我实际项目中的例子,我们只需要关注自动填充相关的部分就可以)
实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("employee")
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)//自增主键
private Long id;
@TableField(insertStrategy = FieldStrategy.NOT_EMPTY)
private String username;
@TableField(insertStrategy = FieldStrategy.NOT_EMPTY)
private String name;
@TableField(insertStrategy = FieldStrategy.NOT_EMPTY)
private String password;
@TableField(insertStrategy = FieldStrategy.NOT_EMPTY)
private String phone;
@TableField(insertStrategy = FieldStrategy.NOT_EMPTY)
@EnumValue()
private String sex;
@TableField(value = "id_number",insertStrategy = FieldStrategy.NOT_NULL)
private String idNumber;
private EmployeeStatus status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)//表示该字段只会在插入的填充
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)//表示这个字段只会在传入和更改的时候都会填充
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)//表示该字段只会在插入的填充
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)//表示该字段只会在插入的填充
private Long updateUser;
}
}
我的自动填充是这样写的
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入时自动填充");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class,LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新时自动填充");
this.strictUpdateFill(metaObject, "updateTime",LocalDateTime.class, LocalDateTime.of(2022,12,30,5,6,0));
}
}
我这里写了一个测试类
@SpringBootTest
class SkyApplicationTest {
@Resource
EmployeeMapper employeeMapper;
@Test
public void test() {
LambdaUpdateWrapper<Employee> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(Employee::getName,"张三");
lambdaUpdateWrapper.set(Employee::getPhone,"2222222222");
Employee employee = new Employee();
employee.setPhone("111111111");
employeeMapper.update(employee,lambdaUpdateWrapper);
System.out.println("");
}
}
那么这个sql执行的结果是什么?
UPDATE
employee
SET
phone='111111111',
update_time='2022-12-30T05:06',
update_user=null,
phone='2222222222'
WHERE
(
name = '张三'
);
这个sql为什么是这样的
我们先看phone这个字段
phone这个字段是没有设置自动填充的,但是两个入参,实体类Employee,和更新条件LambdaUpdateWrapper都对phone都对phone设置了值。
通过sql我们不难发现,mybatis-plus会先根据实体类中不为null的值进行set,然后再写入更新条件中的set
所以,最后更新到数据库里,谁写在SQL语句的最后,数据库里的值就会是谁。
下面
update_time='2022-12-30T05:06',
update_user=null,
这两个字段都是自动填充设置的,其中update_time=‘2022-12-30T05:06’,是我设置了固定值,而update_user=null,是因为我没在MyMetaObjectHandler里设置值的原因,才会赋值为null.
那么现在的问题是自动插入的时机在哪呢?
我们不妨构建这样的例子
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始插入时自动填充");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class,LocalDateTime.now());
this.strictInsertFill(metaObject,"updateUser",Long.class,1L);
this.strictInsertFill(metaObject,"createUser",Long.class,1L);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始更新时自动填充");
this.strictUpdateFill(metaObject, "updateTime",LocalDateTime.class, LocalDateTime.of(2022,12,30,5,6,0));
this.strictInsertFill(metaObject,"updateUser",Long.class,1L);
}
自动填充的时候,uptdateUser会被填入1L
@Test
public void test() {
LambdaUpdateWrapper<Employee> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(Employee::getName,"张三");
lambdaUpdateWrapper.set(Employee::getUpdateUser,2L);
Employee employee = new Employee();
employee.setUpdateUser(3L);
employeeMapper.update(employee,lambdaUpdateWrapper);
System.out.println("");
}
而在测试类中。实体类Employee中的updateUser值是3L
查询条件中的updateUser是2L
那么形成的SQL是什么样的呢?
/*17 2023-12-17 16:52:32 */
UPDATE
employee
SET
update_time='2022-12-30T05:06',
update_user=3,
update_user=2
WHERE
(
name = '张三'
);
先update_user = 3,后update_user = 2,这个之前就解释过了
但是update = 1怎么没有呢?
这是因为前面说的我们在设置自动填充时遵循的时严格模式的插入,当执行update操作的时候,如果该实体类中的updat_user不为null,就不会触发字段填充。
严格模式下的填充规则
插入时填充规则(INSERT):
当执行插入操作时,只有在实体类的字段的值为 null 时才进行填充。
如果实体类字段的值不为 null,则填充操作会被忽略。
更新时填充规则(UPDATE):
当执行更新操作时,只有在字段的值为 null 或者字段被标记为需要更新时才进行填充。
如果字段的值不为 null,且字段没有被标记为需要更新,填充操作会被忽略。
一定一定注意,是实体类中的字段值为null
我们将测试类更改一下:
@SpringBootTest
class SkyApplicationTest {
@Resource
EmployeeMapper employeeMapper;
@Test
public void test() {
LambdaUpdateWrapper<Employee> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(Employee::getName,"张三");
lambdaUpdateWrapper.set(Employee::getUpdateUser,2L);
Employee employee = new Employee();
// employee.setUpdateUser(3L);
employeeMapper.update(employee,lambdaUpdateWrapper);
System.out.println("");
}
}
我们将实体类中updateUser设置为null
你会发现生成的SQL语言是
UPDATE
employee
SET
update_time='2022-12-30T05:06',
update_user=1,
update_user=2
WHERE
(
name = '张三'
);
会发现,自动填充是触发了的。
并且在lambdaUpdateWrapper这个更新条件的前面。这是因为本质上,自动填充是给实体类的update_time赋值的。
此外还有一个注意点,有一种情况也会导致自动填充失效。
在mybatispluss的官网也有说明。
https://baomidou.com/pages/4c6bcf/
看下面这个例子
@SpringBootTest
class SkyApplicationTest {
@Resource
EmployeeMapper employeeMapper;
@Test
public void test() {
LambdaUpdateWrapper<Employee> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(Employee::getName,"张三");
lambdaUpdateWrapper.set(Employee::getUpdateUser,2L);
// Employee employee = new Employee();
// employee.setUpdateUser(3L);
employeeMapper.update(null,lambdaUpdateWrapper);
System.out.println("");
}
}
他的sql实际为
UPDATE
employee
SET
update_user=2
WHERE
(
name = '张三'
);
之所以会这样,update的实体类入参是null,自动填充根本就没有启动。
所以当我们的实体类在定义的时候使用了@TableField(fill = FieldFill.*****)的时候,使用mybatis-plus自带的update方法的时候一定不能传null的实体类。可以传new my_entity()过来。