学习链接
【mybatis plus源码解析】(一)mybatis plus执行原理,mybatis plus是如何实现自动注入CRUD操作
【mybatis plus源码解析】(二)详解SQL注入器底层原理,mybatis plus是如何实现自动注入CRUD操作
【mybatis plus源码解析】(三)自定义SQL注入器,教你如何自定义扩展BaseMapper接口方法,实现更多查询
代码生成 & p6spy打印sql
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zzhua</groupId>
<artifactId>demo-mybatisplus</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-mybatisplus</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!--swagger2依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version> 2.7.0</version>
</dependency>
<!--swagger-ui第三方依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. application.yml
spring:
datasource:
# driver-class-name: com.mysql.jdbc.Driver
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/mytest?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
3. spy.properties
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
注意事项
- driver-class-name 为 p6spy 提供的驱动类
- url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
- 打印出 sql 为 null,在 excludecategories 增加 commit
- 批量操作不打印 sql,去除 excludecategories 中的 batch
- 批量操作打印重复的问题请使用 MybatisPlusLogFactory (3.2.1 新增)
- 该插件有性能损耗,不建议生产环境使用。
4. MyBatisPlusGenCode
package com.zzhua.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
/**
* @description
* @Author: zzhua
* @Date 2022/8/15
*/
public class MyBatisPlusGenCode {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:p6spy:mysql://127.0.0.1:3306/mytest?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true",
"root", "root")
.globalConfig(builder -> {
builder.author("zzhua") // 设置作者
.fileOverride() // 覆盖已生成文件
.enableSwagger() // 开启 swagger 模式
.dateType(DateType.ONLY_DATE)
.disableOpenDir()
.outputDir("D:\\projects\\demo-mybatisplus\\src\\main\\java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.zzhua") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D:\\projects\\demo-mybatisplus\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("my_user") // 设置需要生成的表名
// .addTablePrefix("t_", "c_"); // 设置过滤表前缀
.entityBuilder()
.enableFileOverride()
.enableLombok()
// .enableTableFieldAnnotation()
.naming(NamingStrategy.underline_to_camel)
.idType(IdType.AUTO)
.convertFileName(name->name.concat("Entity"))
.controllerBuilder()
.enableFileOverride()
.enableRestStyle()
.mapperBuilder()
.enableFileOverride()
.enableBaseColumnList()
.enableBaseResultMap()
;
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
mybatis日志打印
原理分析
-
当以上配好确定了日志的构造器后,mybatis中的所有打印日志的地方,都会直接的从Mybatis自己的日志工厂类中获取日志输出对象。
-
而确定构造器的过程就是查找当前项目中,所能够支持的具体实现。项目一启动,mybatis在LogFactory的静态方法中,就会去按左边的列表,挨个尝试,看哪个能够创建成功,第一个创建成功的,则会设置为确定的构造器。
-
也可以手动设置Configuration的logImpl,实际上就等价于在修改LogFactory的logConstructor的实现。
还有重要的一点,要留意到:在MappedStatement.Builder中给MappedStatement设置statementLog对象的时候,就是直接使用的LogFactory.getLog(logId)
,其中的这个logId就是当前sql语句所在的命名空间拼接上当前sql的id,后面很多打印sql相关的日志,其实就是把这个statementLog传来传去的在用statementLog。并且,mybatis的日志打印实际上它还通过动态代理的方式,给原来的对象包了一层,将日志打印的逻辑嵌入到了其中,具体的日志打印交给了以BaseJdbcLogger为抽象父类,子类有:StatementLogger、ConnectionLogger、ResultSetLogger、PreparedStatementLogger
打印的。
当以上mybatis日志相关的配置原理理解了之后,我们发现真正的日志打印,mybatis其实并没有帮我们做,它只是写了个类,日志的具体输出就桥接给了真正的日志实现。所以开始初学mybatis时,也是比较头疼的问题,有的时候,能打印出日志,有的时候打印不出来日志,现在就知道为啥了。
示例
在项目里面,我们可以直接配置configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
,将sql直接打印输出在控制台,它里面的实现就是不管什么级别的输出日志,就是调用System.out.println(s) 或 System.err.println(s);直接打印在控制台上。这个实现感觉也不那么好用:它的trace是打开的,也就是说在查询语句中,它老是把Columns:…,Row:…全特么给打印出来,有的时候,数据一多,看着不爽,而且我们没办法改变它里面的属性。我们有2种办法解决这个问题,1:可以自己复制它这个类,然后手动改了它的默认方法,比如把traceEnabled的返回值改为false,然后指定 为我们的实现类。2:换个org.apache.ibatis.logging.slf4j.Slf4jImpl
的实现,然后,就可以结合logback来玩了(可参考:LogBack日志
),也可以把sql输出到控制到,把sql日志单独输出到一个文件中也可以,还可以指定日志器的输出级别,特别方便,推荐使用这种方式。
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_BASE_PATH" value="logs" />
<property name="maxFileSize" value="5MB"/>
<property name="maxHistory" value="30"/>
<!--<property name="commonPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS}-${PID}-[%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-B3-ParentSpanId}]-[%thread] %-5level %logger{30} [%file:%line] - %msg%n"/>-->
<!--控制台日志, 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--文件日志, 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_BASE_PATH}/vue-springboot.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_BASE_PATH}/%d/xxx.log.%d{yyyy-MM-dd}-%i.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>${maxHistory}</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 在这里可以修改日志级别 -->
<logger name="com.zzhua.mapper" level="trace" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE"/>
</root>
</configuration>
application.yml
server:
port: 8085
servlet:
context-path:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/vue-springboot?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8
username: root
password: root
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
mybatis-plus:
mapper-locations: classpath:/mapper/**.xml
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
使用
@Tableld
/**
* 表主键标识
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
/**
* 字段名(该值可无)
*/
String value() default "";
/**
* 主键类型
* {@link IdType}
*/
IdType type() default IdType.NONE;
}
@TableField
/**
* 表字段标识
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableField {
/**
* 数据库字段值
* <p>
* 不需要配置该值的情况:
* <li> 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 true 时,
* (mp下默认是true,mybatis默认是false), 数据库字段值.replace("_","").toUpperCase() == 实体属性名.toUpperCase() </li>
* <li> 当 {@link com.baomidou.mybatisplus.core.MybatisConfiguration#mapUnderscoreToCamelCase} 为 false 时,
* 数据库字段值.toUpperCase() == 实体属性名.toUpperCase() </li>
*/
String value() default "";
/**
* 是否为数据库表字段
* <p>
* 默认 true 存在,false 不存在
*/
boolean exist() default true;
/**
* 字段 where 实体查询比较条件
* <p>
* 默认 {@link SqlCondition#EQUAL}
*/
String condition() default "";
/**
* 字段 update set 部分注入, 该注解优于 el 注解使用
* <p>
* 例1:@TableField(.. , update="%s+1") 其中 %s 会填充为字段
* 输出 SQL 为:update 表 set 字段=字段+1 where ...
* <p>
* 例2:@TableField(.. , update="now()") 使用数据库时间
* 输出 SQL 为:update 表 set 字段=now() where ...
*/
String update() default "";
/**
* 字段验证策略之 insert: 当insert操作时,该字段拼接insert语句时的策略
* <p>
* IGNORED: 直接拼接 insert into table_a(column) values (#{columnProperty});
* NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
* NOT_EMPTY: insert into table_a(<if test="columnProperty != null and columnProperty!=''">column</if>) values (<if test="columnProperty != null and columnProperty!=''">#{columnProperty}</if>)
* NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
*
* @since 3.1.2
*/
FieldStrategy insertStrategy() default FieldStrategy.DEFAULT;
/**
* 字段验证策略之 update: 当更新操作时,该字段拼接set语句时的策略
* <p>
* IGNORED: 直接拼接 update table_a set column=#{columnProperty}, 属性为null/空string都会被set进去
* NOT_NULL: update table_a set <if test="columnProperty != null">column=#{columnProperty}</if>
* NOT_EMPTY: update table_a set <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
* NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
*
* @since 3.1.2
*/
FieldStrategy updateStrategy() default FieldStrategy.DEFAULT;
/**
* 字段验证策略之 where: 表示该字段在拼接where条件时的策略
* <p>
* IGNORED: 直接拼接 column=#{columnProperty}
* NOT_NULL: <if test="columnProperty != null">column=#{columnProperty}</if>
* NOT_EMPTY: <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
* NOT_EMPTY 如果针对的是非 CharSequence 类型的字段则效果等于 NOT_NULL
*
* @since 3.1.2
*/
FieldStrategy whereStrategy() default FieldStrategy.DEFAULT;
/**
* 字段自动填充策略
* <p>
* 在对应模式下将会忽略 insertStrategy 或 updateStrategy 的配置,等于断言该字段必有值
*/
FieldFill fill() default FieldFill.DEFAULT;
/**
* 是否进行 select 查询
* <p>
* 大字段可设置为 false 不加入 select 查询范围
*/
boolean select() default true;
/**
* 是否保持使用全局的 columnFormat 的值
* <p>
* 只生效于 既设置了全局的 columnFormat 也设置了上面 {@link #value()} 的值
* 如果是 false , 全局的 columnFormat 不生效
*
* @since 3.1.1
*/
boolean keepGlobalFormat() default false;
/**
* {@link ResultMapping#property} and {@link ParameterMapping#property}
*
* @since 3.4.4
*/
String property() default "";
/**
* JDBC类型 (该默认值不代表会按照该值生效),
* 只生效于 mp 自动注入的 method,
* 建议配合 {@link TableName#autoResultMap()} 一起使用
* <p>
* {@link ResultMapping#jdbcType} and {@link ParameterMapping#jdbcType}
*
* @since 3.1.2
*/
JdbcType jdbcType() default JdbcType.UNDEFINED;
/**
* 类型处理器 (该默认值不代表会按照该值生效),
* 只生效于 mp 自动注入的 method,
* 建议配合 {@link TableName#autoResultMap()} 一起使用
* <p>
* {@link ResultMapping#typeHandler} and {@link ParameterMapping#typeHandler}
*
* @since 3.1.2
*/
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
/**
* 只在使用了 {@link #typeHandler()} 时判断是否辅助追加 javaType
* <p>
* 一般情况下不推荐使用
* {@link ParameterMapping#javaType}
*
* @since 3.4.0 @2020-07-23
*/
boolean javaType() default false;
/**
* 指定小数点后保留的位数,
* 只生效于 mp 自动注入的 method,
* 建议配合 {@link TableName#autoResultMap()} 一起使用
* <p>
* {@link ParameterMapping#numericScale}
*
* @since 3.1.2
*/
String numericScale() default "";
}
mybatisplus 插入/修改 用法
插入(或新增)
userMapper.insert(userEntity)
- 将会插入userEntity对象中属性值不为null的字段
userService.save(new UserEntity())
- 将会插入userEntity对象中属性值不为null的字段 ,其实就是调用userMapper.insert(userEntity)
修改(或更新)
userMapper.updateById(userEntity)
- 以where id = #{userEntity.id} 为条件,userEntity对象中 属性值不为null的字段作为set,组成sql
userMapper.update(userEntity, new UpdateWrapper<userEntity>().lambda().eq( UserEntity::getNickname, "zzhua"))
;- 以后面updateWrapper指定的字段作为where条件,并且以userEntity对象中 属性值不为null的字段作为set,组成sql
- 后面的updateWrapper不仅可以指定作为where条件的字段,还可以设置sql字段的值(这也会拼接到最终的sql上,并且这如果和userEntity对象的属性重复了,这里的顺序会在后面,所以会以这里的生效),可以考虑使用QueryWrapper,这样就没有设置sql字段的方法了。
- 注意上面Wrapper需要设置表对应的实体类 作为泛型
userService.updateById(userEntity)
- 将会以where id = #{userEntity.id} 为条件,插入userEntity对象中属性值不为null的字段 ,其实就是调用userMapper.insert(userEntity)
userService.update(user, new UpdateWrapper<User>().lambda().eq( User::getNickname, "zzhua"))
- 其实就是调用
userMapper.update(userEntity, new UpdateWrapper<userEntity>().lambda().eq( UserEntity::getNickname, "zzhua"))
,所以用法完全与它相同
- 其实就是调用
userService.update(new UpdateWrapper<User>().lambda().eq(User::getNickname, "zzhua195").set(User::getIsV, 10))
- 其实就是调用 userService.update(user,updateWrapper)),只不过这里的user是null,即等价于:userService.update(null , updateWrapper))
- 直接以UpdateWrapper调用方法确定更新的where条件 和 使用set更新字段的值
userService.lambdaUpdate().set(User::getIsV, 55).eq(User::getNickname, "zzhua195").update()
- 以链式的方式使用mybatisplus
- 这种写法有两次出现update()方法哦,但不是同一个update()方法 ,前面的update()方法是开启链式调用的开端,后面的update一定要调用,否则不会执行sql
- 最后面的update方法中可以传入一个userEntity对象,其中userEntity对象中的属性值不为null的属性将会设置作为更新的字段。当然,也可以不传入。如果传入了一个userEntity对象,并且遇到与前面设置的字段有冲突,最后都会加入到sql中,但是前面设置的会出现在sql的后面位置,因此,前面设置更优先。
userService.update().eq("nickname", "zzhua195").set("avatarUrl", "urll").update()
- 以链式的方式使用mybatisplus,这种用法需要把字段名写准确(不能写错,因此
不推荐使用
,万一后面改字段名了,就废了),可以简单理解为:不带lambda的话,那就要写具体的数据库字段名 - 这种写法有两次出现update()方法哦,但不是同一个update()方法 ,前面的update()方法是开启链式调用的开端,后面的update一定要调用,否则不会执行sql
- 最后面的update方法中可以传入一个userEntity对象,其中userEntity对象中的属性值不为null的属性将会设置作为更新的字段。当然,也可以不传入。如果传入了一个userEntity对象,并且遇到与前面设置的字段有冲突,最后都会加入到sql中,但是前面设置的会出现在sql的后面位置,因此,前面设置更优先。
- 以链式的方式使用mybatisplus,这种用法需要把字段名写准确(不能写错,因此
插入或修改
userService.saveOrUpdate(userEntity)
- 先去获取userEntity的主键,如果主键为null或者为空字符串(即没有指定主键),那么就一定是插入,使用baseMapper.insert(userEntity)插入数据。如果指定了主键,那么会调用baseMapper.selectById(id)方法,根据主键查询是否存在,如果不存在,则执行baseMapper.insert(userEntity)插入数据,如果存在,则执行baseMapper.updateById(userEntity)
userService.saveOrUpdate(userEntity, new UpdateWrapper<UserEntity>().lambda().eq(UserEntity::getNickname, "zzhua195").set(UserEntity::getCreateTime, new Date()))
- 先尝试调用
baseMapper.update(entity,updateWrapper)
,如果这个结果返回的是的数量大于等于1,那么直接返回true,这个saveOrUpdate就执行完了。但是如果这个结果返回的数量没有超过1(未更新到数据),那么就会去执行userService.saveOrUpdate(userEntity)
,注意这个时候就跟updateWrapper没什么关系了。
- 先尝试调用
FieldStrategy
IGNORED
NOT_NULL
NOT_EMPTY
DEFAULT
NEVER
/**
* 字段策略枚举类
* <p>
* 如果字段是基本数据类型则最终效果等同于 {@link #IGNORED}
*
* @author hubin
* @since 2016-09-09
*/
public enum FieldStrategy {
/**
* 忽略判断
*/
IGNORED,
/**
* 非NULL判断
*/
NOT_NULL,
/**
* 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
*/
NOT_EMPTY,
/**
* 默认的,一般只用于注解里
* <p>1. 在全局里代表 NOT_NULL</p>
* <p>2. 在注解里代表 跟随全局</p>
*/
DEFAULT,
/**
* 不加入 SQL
*/
NEVER
}
FieldFill
/**
* 字段填充策略枚举类
*
* <p>
* 判断注入的 insert 和 update 的 sql 脚本是否在对应情况下忽略掉字段的 if 标签生成
* <if test="...">......</if>
* 判断优先级比 {@link FieldStrategy} 高
* </p>
*/
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}
自动填充字段
在数据表的设计中,经常需要加一些字段,如:创建时间,最后修改时间等。
- 一种是在数据库中设置
创建时间的默认值为CURRENT_TIMESTAMP
,更新时间勾选根据当前时间戳更新 - 现在可以使用mybatisplus提供的字段填充功能来实现了
UserEntity
@Data
@TableName(value = "`user`")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value = "nickname")
private String nickname;
@TableField(value = "is_v")
private Integer isV;
@TableField(value = "avatar_url")
private String avatarUrl;
@TableField(value = "create_time",fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time",fill = FieldFill.UPDATE)
private Date updateTime;
}
定义MetaObjectHandler
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MetaObjectHandler() {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
};
}
使用
// 插入数据,自动设置create_time (这里全部为null,因此只会插入create_time字段)
userService.save(new UserEntity());
// 相当于调用userService(null, updateWrapper); 这种情况不会自动设置updateTime
userService.update(new UpdateWrapper<UserEntity>().lambda().eq(UserEntity::getNickname,"zzhua").set(UserEntity::getAvatarUrl,"uuurrrlll"));
// 更新数据, 自动设置updateTime
userService.update(new User(), new UpdateWrapper<User>().lambda().eq(User::getNickname, "zzhua").set(User::getAvatarUrl, "uuurrrlll"));
注意事项
- 填充原理是直接给
entity
的属性设置值!!! - 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
- MetaObjectHandler提供的默认方法的策略均为:
如果属性有值则不覆盖,如果填充值为null则不填充
(也就是,如果我们设置了的话,那自动填充不会干涉到我们的操作,) - 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
- 填充处理器MyMetaObjectHandler在 Spring Boot 中
需要声明@Component或@Bean注入
- 要想
根据注解FieldFill.xxx和字段名以及字段类型来区分
必须使用父类的strictInsertFill或者strictUpdateFill方法 不需要根据任何来区分
可以使用父类的fillStrategy方法- update(T t,Wrapper updateWrapper)时
t不能为空
,否则自动填充失效