前几章主要是讲的查询相关的功能,这次我们来说说更新相关的功能!
一. 前置准备
先来看看我们用来演示的项目的结构:
.
├── HELP.md
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── cn
│ │ └── justmr
│ │ └── mybatisplus
│ │ └── demo
│ │ ├── DemoApplication.java
│ │ ├── config
│ │ │ └── MybatisPlusConfig.java // 分页配置
│ │ ├── entity
│ │ │ ├── BaseEntity.java
│ │ │ └── User.java
│ │ ├── handler
│ │ │ ├── MyMetaObjectHandler.java // 自动填充 create_time 和 update_time
│ │ └── mapper
│ │ └── UserMapper.java
│ └── resources
│ ├── application.yml
│ ├── static
│ └── templates
└── test
└── java
└── cn
└── justmr
└── mybatisplus
└── demo
└── demo
├── DemoApplicationTests.java
├── UserTests.java
1. build.gradle
plugins {
id 'org.springframework.boot' version '2.3.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
group = 'cn.justmr.mybatis-plus.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
// 为了加快速度,配置阿里镜像仓库
maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
mavenCentral()
}
ext {
set('mybaitsPlusVersion', "3.4.2")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
// 引入mybatis plus
compile "com.baomidou:mybatis-plus-boot-starter:${mybaitsPlusVersion}"
annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
2. application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: test
password: 123456
mybatis-plus:
configuration:
# 开启自动驼峰命名规则(camel case)映射,默认就是true
map-underscore-to-camel-case: true
# 输出日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
insert-strategy: not_null
update-strategy: not_null # 默认全局更新策略
3. 测试数据
二. 更新
首先还是先看一下UserMapper
实现的BaseMapper
的接口:
1. 更新策略
我们先看一下默认的全局更新策略:
# 在application.yml中配置了默认的更新策略
mybatis-plus:
global-config:
db-config:
insert-strategy: not_null # 默认全局插入策略
update-strategy: not_null # 默认全局更新策略
修改全部插入/更新策略,可以参考Mybatis Plus 常用注解.
这个配置说明,在更新的时候,如果T
的属性值为null
则忽略不更新,演示一下:
@Test
@DisplayName("更新测试1")
public void test1() {
User user = new User();
user.setName("shiv");
user.setAge(28);
user.setEmail("22@qq.com");
user.setSex("MAIL");
// 插入一条数据
userMapper.insert(user);
// 更新邮箱
user.setEmail("22@shiv.com");
userMapper.updateById(user);
// 查询更新后结果
User data = userMapper.selectById(user.getId());
Assertions.assertEquals(data.getEmail(), "22@shiv.com");
}
结果:
==> Preparing: INSERT INTO user ( name, age, email, sex, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ? )
==> Parameters: shiv(String), 28(Integer), 22@qq.com(String), MAIL(String), 2021-02-13T22:56:49.267(LocalDateTime), 2021-02-13T22:56:49.271(LocalDateTime)
<== Updates: 1
...
==> Preparing: UPDATE user SET name=?, age=?, email=?, sex=?, create_time=?, update_time=? WHERE id=?
==> Parameters: shiv(String), 28(Integer), 22@shiv.com(String), MAIL(String), 2021-02-13T22:56:49.267(LocalDateTime), 2021-02-13T22:56:49.271(LocalDateTime), 7(Long)
<== Updates: 1
...
==> Preparing: SELECT id,name,age,email,sex,is_delete,create_time,update_time FROM user WHERE id=?
==> Parameters: 7(Long)
<== Columns: id, name, age, email, sex, is_delete, create_time, update_time
<== Row: 7, shiv, 28, 22@shiv.com, MAIL, 0, 2021-02-13 22:56:49, 2021-02-13 22:56:49
<== Total: 1
思考:
其实上面的更新,我只想更新一下email
信息,并不想更行其它字段,但是由于使用的实体和插入的是同一个,导致其他字段也加入到了最终的sql中,从打印的SQL日志上看,完全看不出我是要更新哪个字段!我是比较主张不要为了方便把不需要更新的字段也一并更新掉,增加了很多干扰信息!
所以上面的测试稍微改一下:
@Test
@DisplayName("更新测试2")
public void test2() {
User user = new User();
user.setName("shiv");
user.setAge(28);
user.setEmail("22@qq.com");
user.setSex("MAIL");
// 插入一条数据
userMapper.insert(user);
// 更新邮箱, 其它字段不设置默认为null,不会更新
User u = new User();
u.setId(user.getId());
u.setEmail("22@shiv.com");
userMapper.updateById(u);
// 查询更新后结果
User data = userMapper.selectById(user.getId());
Assertions.assertEquals(data.getEmail(), "22@shiv.com");
}
再对比下打印日志:
==> Preparing: INSERT INTO user ( name, age, email, sex, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ? )
==> Parameters: shiv(String), 28(Integer), 22@qq.com(String), MAIL(String), 2021-02-13T23:07:44.490(LocalDateTime), 2021-02-13T23:07:44.494(LocalDateTime)
<== Updates: 1
...
==> Preparing: UPDATE user SET email=?, update_time=? WHERE id=?
==> Parameters: 22@shiv.com(String), 2021-02-13T23:07:45.378(LocalDateTime), 8(Long)
<== Updates: 1
...
==> Preparing: SELECT id,name,age,email,sex,is_delete,create_time,update_time FROM user WHERE id=?
==> Parameters: 8(Long)
<== Columns: id, name, age, email, sex, is_delete, create_time, update_time
<== Row: 8, shiv, 28, 22@shiv.com, MAIL, 0, 2021-02-13 23:07:44, 2021-02-13 23:07:45
<== Total: 1
可以看到更新邮箱的时候,只给实体u
设置了主键和email
字段,其它的字段默认为null
,set
时就忽略了那些为null
的字段。
然后我们可以更新默认的更新策略,如改为下面的:
# 在application.yml中配置了默认的更新策略
mybatis-plus:
global-config:
db-config:
update-strategy: IGNORED # 忽略判断
重新跑一下单元测试,结果失败,因为数据库有些字段设置了不能为null,我们主要关注一下更新的SQL:
==> Preparing: UPDATE user SET name=?, age=?, email=?, sex=?, is_delete=?, create_time=?, update_time=? WHERE id=?
==> Parameters: null, null, 22@shiv.com(String), null, null, null, 2021-02-13T23:16:00.285(LocalDateTime), 9(Long)
可以看到为null
的字段也进行了更新!
除了修改全局的update-strategy
外,我们也可以在实体类对应字段上添加对应注解@TableField(updateStrategy = FieldStrategy.IGNORED)
,这里就不再演示了!最后将update-strategy
配置还原为not_null
2. updateById(T)
根据 ID 修改
使用方式参考上面的示例
3. update(T, Wrapper)
根据 whereEntity 条件,更新记录
Params: entity – 实体对象 (set 条件值,可以为 null)
updateWrapper – 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
从注释中可以看到两个参数,
entity
主要用于生成set
条件。updateWrapper
则复杂一下,可生成where
语句,也可以生成set
条件值!我们一起来试一试:
示例1
@Test
@DisplayName("更新测试3")
public void test3() {
// entity 为 null 的情况
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.set(User::getEmail, "33@shiv.com")
.eq(User::getId, 8);
userMapper.update(null, wrapper);
}
结果:
==> Preparing: UPDATE user SET email=? WHERE (id = ?)
==> Parameters: 33@shiv.com(String), 8(Integer)
<== Updates: 1
注意:使用这种方式设置某个字段为null
, 不会被忽略的,如下:
@Test
@DisplayName("更新测试4")
public void test4() {
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.set(User::getEmail, null)
.eq(User::getId, 8);
// entity 为 null 的情况
userMapper.update(null, wrapper);
}
结果:
==> Preparing: UPDATE user SET email=? WHERE (id = ?)
==> Parameters: null, 8(Integer)
可见 email
依然会被设置为 null
.所以在更新策略为not_null
的情况下,如果需要将某个字段设置为null,可以采用这种方法(在updateWrapper
里设置)。
示例2
@Test
@DisplayName("更新测试5")
public void test5() {
User u = new User();
u.setId(8L);
u.setEmail("44@shiv.com");
// updateWrapper为空的情况
userMapper.update(u, null);
}
结果:
==> Preparing: UPDATE user SET email=?, update_time=?
==> Parameters: 44@shiv.com(String), 2021-02-13T23:42:25.512(LocalDateTime)
<== Updates: 8
注意:
entity
参数里面为null
的会字段被忽略(更新策略影响)entity
参数不会在where
条件中- 主键不会应用在
set
条件中
示例3
@Test
@DisplayName("更新测试6")
public void test6() {
User u = new User();
u.setEmail("44@shiv.com");
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.eq(User::getId, 8);
userMapper.update(u, wrapper);
}
结果:
==> Preparing: UPDATE user SET email=?, update_time=? WHERE (id = ?)
==> Parameters: 44@shiv.com(String), 2021-02-13T23:50:25.951(LocalDateTime), 8(Integer)
<== Updates: 1
示例4(避免)
entity
和updateWrapper
都对相同字段设置,这种情况尽量避免,不是那么好理解
@Test
@DisplayName("更新测试6")
public void test6() {
// entity和 updateWrapper 都对 email设置值
User u = new User();
u.setEmail("44@shiv.com");
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.set(User::getEmail, "44@shiv.com")
.eq(User::getId, 8);
userMapper.update(u, wrapper);
}
结果email
在set
中出现了两次:
==> Preparing: UPDATE user SET email=?, update_time=?, email=? WHERE (id = ?)
==> Parameters: 44@shiv.com(String), 2021-02-13T23:54:00.213(LocalDateTime), 44@shiv.com(String), 8(Integer)
<== Updates: 1
示例5
利用
updateWrapper
里的entity
生成where
条件
@Test
@DisplayName("更新测试6")
public void test6() {
User entity = new User();
entity.setId(8L);
entity.setName("shiv");
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.set(User::getEmail, "44@shiv.com")
.setEntity(entity);
userMapper.update(null, wrapper);
}
结果:
==> Preparing: UPDATE user SET email=? WHERE id=? AND name=?
==> Parameters: 44@shiv.com(String), 8(Long), shiv(String)
<== Updates: 1
注意:
这种方式更新,不会自动更新update_time
的值,尽管我们设置了update_time
的自动填充功能!填充只能填充到entity里
示例6(避免)
@Test
@DisplayName("更新测试7")
public void test7() {
User entity = new User();
entity.setId(8L); // id
entity.setName("shiv");
LambdaUpdateWrapper<User> wrapper = Wrappers.<User>lambdaUpdate()
.set(User::getEmail, "44@shiv.com")
.eq(User::getId, 8L) // id
.setEntity(entity);
userMapper.update(null, wrapper);
}
结果where
条件中,id
出现了两次:
==> Preparing: UPDATE user SET email=? WHERE id=? AND name=? AND (id = ?)
==> Parameters: 44@shiv.com(String), 8(Long), shiv(String), 8(Long)
<== Updates: 1
4. 链式更新(推荐)
@Test
@DisplayName("更新测试8")
public void test8() {
ChainWrappers.lambdaUpdateChain(userMapper)
.set(User::getEmail, "44@shiv.com")
.eq(User::getId, 8L)
.update();
}
结果:
==> Preparing: UPDATE user SET email=? WHERE (id = ?)
==> Parameters: 44@shiv.com(String), 8(Long)
<== Updates: 1
三. 思考
对于一些更新场景,就针对根据主键更新(updateById(T)
)这个方法来讲,有时我们只想更新我们设置的不为null的字段,有时我们又想传什么值就更新成什么值,包括null
(表单清空情况)。对于这些情况,早期mybatis plus
是通过两个api来做的update
和updateSelective
,其它的框架也是类似的实现,比如 tk.mapper。但是自v2.0.0[2016.12.11]版本版本开始,mybatis plus
合并了所有Selective通用方法(例如:去除之前的insert方法并把之前的insetSelective改名为insert),导致我们没法直接使用这两个方法来处理对应的场景了!其实对于传什么值就更新成什么值(包括null)的场景,利用早期[v1.4.9]的update方法,则会将涉及到的全部字段更新,不管是不是这次需要更新的。而且通常还需要提前将更新前的数据查询出来,设置值,再去更新,这样做虽然方便,但是并不是很好!对于更新字段我还是建议更新哪个字段就只set
哪个字段,不要不管三七二十一的全部set
一遍。
在mybatis plus
中使用更新的建议:
- 采用默认的全局插入,更新策略
not_null
- 如果有某些字段需要更新为
null
,建议采用上面链式更新(推荐)
中的用法,手动调用set
方法!