Mybatis-plus进阶篇-八

一.持久层接口补充

1.Mapper 层选装件

选装件是 Mybatis-Plus 提供的一些扩展方法,它们位于 com.baomidou.mybatisplus.extension.injector.methods 包下。这些方法需要配合Sql 注入器使用,以扩展 Mapper 接口的功能。

使用这些选装件前,需要确保已经正确配置了 Sql 注入器。

选装件说明

1>alwaysUpdateSomeColumnById

1
int alwaysUpdateSomeColumnById(T entity);
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.baomidou.mybatisplus.extension.injector.methods;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.function.Predicate;

/**
* 根据 ID 更新固定的那几个字段(但是不包含逻辑删除)
*
* <p>
* 自己的通用 mapper 如下使用:
* <pre>
* int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity);
* </pre>
* </p>
*
* <p> 如何筛选字段参考请 {@link InsertBatchSomeColumn} 里面的注释 </p>
*
* @author hubin
* @since 2019-04-12
*/
public class AlwaysUpdateSomeColumnById extends AbstractMethod {

/**
* 字段筛选条件
*/
@Setter
@Accessors(chain = true)
private Predicate<TableFieldInfo> predicate;

/**
* @param name 方法名
* @param predicate 筛选条件
* @since 3.5.0
*/
public AlwaysUpdateSomeColumnById(String name, Predicate<TableFieldInfo> predicate) {
super(name);
this.predicate = predicate;
}

public AlwaysUpdateSomeColumnById() {
super("alwaysUpdateSomeColumnById");
}

/**
* @param predicate 筛选条件
*/
public AlwaysUpdateSomeColumnById(Predicate<TableFieldInfo> predicate) {
super("alwaysUpdateSomeColumnById");
this.predicate = predicate;
}

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.UPDATE_BY_ID;
final String additional = optlockVersion(tableInfo) + tableInfo.getLogicDeleteSql(true, true);
String sqlSet = this.filterTableFieldInfo(tableInfo.getFieldList(), getPredicate(),
i -> i.getSqlSet(true, ENTITY_DOT), NEWLINE);
sqlSet = SqlScriptUtils.convertSet(sqlSet);
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlSet,
tableInfo.getKeyColumn(), ENTITY_DOT + tableInfo.getKeyProperty(), additional);
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return addUpdateMappedStatement(mapperClass, modelClass, methodName, sqlSource);
}

private Predicate<TableFieldInfo> getPredicate() {
Predicate<TableFieldInfo> noLogic = t -> !t.isLogicDelete();
if (predicate != null) {
return noLogic.and(predicate);
}
return noLogic;
}

}
功能:

这个方法用于在更新操作时,无论实体对象的某些字段是否有变化,都会强制更新这些字段。这在某些业务场景下非常有用,比如更新时间戳字段,确保每次更新操作都会更新该字段。

使用场景:

当你需要在每次更新记录时,都更新某些特定的字段(如更新时间、版本号等),即使这些字段在实体对象中没有变化。

2>insertBatchSomeColumn

1
int insertBatchSomeColumn(List<T> entityList);
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.baomidou.mybatisplus.extension.injector.methods;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.List;
import java.util.function.Predicate;

/**
* 批量新增数据,自选字段 insert
* <p> 不同的数据库支持度不一样!!! 只在 mysql 下测试过!!! 只在 mysql 下测试过!!! 只在 mysql 下测试过!!! </p>
* <p> 除了主键是 <strong> 数据库自增的未测试 </strong> 外理论上都可以使用!!! </p>
* <p> 如果你使用自增有报错或主键值无法回写到entity,就不要跑来问为什么了,因为我也不知道!!! </p>
* <p>
* 自己的通用 mapper 如下使用:
* <pre>
* int insertBatchSomeColumn(List<T> entityList);
* </pre>
* </p>
*
* <li> 注意: 这是自选字段 insert !!,如果个别字段在 entity 里为 null 但是数据库中有配置默认值, insert 后数据库字段是为 null 而不是默认值 </li>
*
* <p>
* 常用的 {@link Predicate}:
* </p>
*
* <li> 例1: t -> !t.isLogicDelete() , 表示不要逻辑删除字段 </li>
* <li> 例2: t -> !t.getProperty().equals("version") , 表示不要字段名为 version 的字段 </li>
* <li> 例3: t -> t.getFieldFill() != FieldFill.UPDATE) , 表示不要填充策略为 UPDATE 的字段 </li>
*
* @author miemie
* @since 2018-11-29
*/
public class InsertBatchSomeColumn extends AbstractMethod {

/**
* 字段筛选条件
*/
@Setter
@Accessors(chain = true)
private Predicate<TableFieldInfo> predicate;

/**
* 默认方法名
*/
public InsertBatchSomeColumn() {
super("insertBatchSomeColumn");
}

/**
* 默认方法名
*
* @param predicate 字段筛选条件
*/
public InsertBatchSomeColumn(Predicate<TableFieldInfo> predicate) {
super("insertBatchSomeColumn");
this.predicate = predicate;
}

/**
* @param name 方法名
* @param predicate 字段筛选条件
* @since 3.5.0
*/
public InsertBatchSomeColumn(String name, Predicate<TableFieldInfo> predicate) {
super(name);
this.predicate = predicate;
}

@SuppressWarnings("Duplicates")
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
List<TableFieldInfo> fieldList = tableInfo.getFieldList();
String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, null, false) +
this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, ENTITY_DOT, false) +
this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
String valuesScript = SqlScriptUtils.convertForeach(insertSqlProperty, "list", null, ENTITY, COMMA);
String keyProperty = null;
String keyColumn = null;
// 表包含主键处理逻辑,如果不包含主键当普通字段处理
if (tableInfo.havePK()) {
if (tableInfo.getIdType() == IdType.AUTO) {
/* 自增主键 */
keyGenerator = Jdbc3KeyGenerator.INSTANCE;
keyProperty = tableInfo.getKeyProperty();
// 去除转义符
keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
} else {
if (null != tableInfo.getKeySequence()) {
keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, tableInfo, builderAssistant);
keyProperty = tableInfo.getKeyProperty();
keyColumn = tableInfo.getKeyColumn();
}
}
}
String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), columnScript, valuesScript);
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass, methodName, sqlSource, keyGenerator, keyProperty, keyColumn);
}

}
功能:

这个方法用于批量插入实体对象,但只插入实体对象中指定的某些字段。这在需要批量插入数据,但又不希望插入所有字段时非常有用。

使用场景:

当你需要批量插入数据,并且希望只插入实体对象中的部分字段,以提高插入效率或保护敏感数据。

3>logicDeleteByIdWithFill

1
int logicDeleteByIdWithFill(T entity);
源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.baomidou.mybatisplus.extension.injector.methods;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.List;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

/**
* 根据 id 逻辑删除数据,并带字段填充功能
* <p>注意入参是 entity !!! ,如果字段没有自动填充,就只是单纯的逻辑删除</p>
* <p>
* 自己的通用 mapper 如下使用:
* <pre>
* int deleteByIdWithFill(T entity);
* </pre>
* </p>
*
* @author miemie
* @since 2018-11-09
* @deprecated 3.5.0 {@link com.baomidou.mybatisplus.core.injector.methods.DeleteById}
*/
@Deprecated
public class LogicDeleteByIdWithFill extends AbstractMethod {

public LogicDeleteByIdWithFill() {
super("deleteByIdWithFill");
}

/**
* @param name 方法名
* @since 3.5.0
*/
public LogicDeleteByIdWithFill(String name) {
super(name);
}

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql;
SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID;
if (tableInfo.isWithLogicDelete()) {
List<TableFieldInfo> fieldInfos = tableInfo.getFieldList().stream()
.filter(TableFieldInfo::isWithUpdateFill)
.filter(f -> !f.isLogicDelete())
.collect(toList());
if (CollectionUtils.isNotEmpty(fieldInfos)) {
String sqlSet = "SET " + fieldInfos.stream().map(i -> i.getSqlSet(EMPTY)).collect(joining(EMPTY))
+ tableInfo.getLogicDeleteSql(false, false);
sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlSet, tableInfo.getKeyColumn(),
tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true));
} else {
sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),
tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
tableInfo.getLogicDeleteSql(true, true));
}
} else {
sqlMethod = SqlMethod.DELETE_BY_ID;
sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),
tableInfo.getKeyProperty());
}
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return addUpdateMappedStatement(mapperClass, modelClass, methodName, sqlSource);
}

}
功能:

这个方法用于逻辑删除记录,并填充实体对象中的某些字段。逻辑删除意味着不是真正从数据库中删除记录,而是通过更新某个字段(如 deleted 字段)来标记记录已被删除。

使用场景:

当你需要实现逻辑删除功能,并且希望在删除操作时自动填充实体对象中的某些字段(如删除时间、删除人等)。

使用提示

  1. 在使用这些选装件之前,确保你的项目中已经正确配置了 Sql 注入器。
  2. 这些方法通常需要在 Mapper 接口中显式声明,以便 Mybatis-Plus 能够识别并生成相应的 SQL 语句。
  3. 每个选装件都有其特定的使用场景,根据业务需求选择合适的方法。
  4. 在实际使用中,可能需要结合实体对象的注解(如 @TableField、@TableLogic 等)来实现更复杂的功能。