学会mybatis-plus的使用,做一个快乐的Curd-B

前言

对于mybatis ,很多后端开发已经很熟悉了,因为现在大部分公司用的框架就是mybatis,而Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具。(很多公司也在用这个框架)

在项目里面,你经常是不是这样书写:(如查询) Wrappers.query().lambda().eq(Entity::getXX, entity2.getXX());

网上想找到Mybatis-Plus的文档和案例,其实很简单,在Mybatis-Plus的官网上或者有很多博客上都能找到的。但你有木有相关它是怎么能实现不需要再写xml了(针对写sql),就能针对性的查询/新增/修改/删除的?当你遇到lambda表达式时,会不会想到他是怎么把这个Get方法传入的?下面就来谈谈Mybatis-Plus是怎么使用lambda表达式,自动生成对应的sql语句的。

代码分析

基于Mybatis-Plus的3.0.6 版本,这个框架用到了工厂模式和组合模式 以及拦截过滤器模式。

file

首先:Wrappers.query() 或者Wrappers.update() 其实就是在创建一个QueryWrapper 或UpdateWrapper。然后调用lambda方法就是创建LambdaUpdateWrapper 或者 LambdaUpdateWrapper

file

如图,需要重点关注的是Compare(接口)和AbstractWrapper(类),在Compare接口里面。
file

1
2
3
4
5
6
7
8
9
perl复制代码
public interface Compare<This, R> extends Serializable {

default This eq(R column, Object val) {
return this.eq(true, column, val);
}

This eq(boolean condition, R column, Object val);
}

这里面的This就是代表就是返回自身(这里字面是这个意思,实际也是这样弄的),在3.3.2版本里面这个This用Children给取代了。

在AbstractWrapper类里面,其实已经实现了eq方法(如下图),这个类实现我把其他实现接口去掉了,只留下了Compare接口。(这样看起来比较清晰)

1
2
3
4
5
scala复制代码public abstract class AbstractWrapper<T, R, This extends AbstractWrapper<T, R, This>> extends Wrapper<T> implements Compare<This, R>{
public This eq(boolean condition, R column, Object val) {
return this.addCondition(condition, column, SqlKeyword.EQ, val);
}
}

可能你很疑惑为什么eq /ne 这些方法里面可以直接传递lambda的方法引用(Entity:getXX),而不应该是泛型R?
不要着急。AbstractLambdaWrapper (实现了AbstractWrapper类,此时 AbstractWrapper类的泛型R用接口SFunction来具体化“取代了”,这个SFunction指定了必须是泛型T里面的方法,这点要注意,如果没有指定泛型可能会报Object is not a functional interface 这样的错误)。

1
2
3
4
scala复制代码public abstract class AbstractLambdaWrapper<T, This extends AbstractLambdaWrapper<T, This>>
extends AbstractWrapper<T, SFunction<T, ?>, Children> {
//省略
}

调用上面的addCondition方法,实际会解析这个”接口”,这个是使用流读取,方法在LambdaUtils里面,如果你有需要可以在项目中直接使用这个方法,
这就是看源码的好处。其实这部分就是把当前对象的“数据库”对于列存入缓存(map),将对应列和值也就进行存储。以便到最后面生成sql。(其实在mapper层调用方法时)

file
file
file

自己实现这样的功能 (记录处理列和 对象所有的数据库字段 和串写的方式)

这个里面解析lambda等相关工具从mybatis-plus里面挪了出来,部分功能一重写,还原一个无依赖的项目。

1.继承接口

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
typescript复制代码package interfaces;

import java.io.Serializable;

/**
* <ul>
* <li>Title: Compare</li>
* </ul>
* @author 程序员ken
* @date 2021/4/28 0028 下午 14:48
*/
public interface Compare<This, R> extends Serializable {

This eq(boolean var1, R var2, Object var3);

default This eq(R column, Object val) {
return this.eq(true, column, val);
}

This ne(boolean var1, R var2, Object var3);

default This ne(R column, Object val) {
return this.ne(true, column, val);
}

This gt(boolean var1, R var2, Object var3);

default This gt(R column, Object val) {
return this.gt(true, column, val);
}


This lt(boolean var1, R var2, Object var3);

default This lt(R column, Object val) {
return this.lt(true, column, val);
}

}

2.接口实现类

AbstractWrapper 类 所有核心方法的实现,这里没有判断是不是SFunction,直接强转的,实际项目必须要判断哦

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
ini复制代码package wrapper;


/**
* <ul>
* <li>Title: AbstractWrapper</li>
* <li>Description: TODO </li>
* </ul>
*
* @author 程序员ken
* @date 2021/4/28 0028 下午 16:23
*/
//extends Wrapper<T>
public abstract class AbstractWrapper<T, R, This extends AbstractWrapper>
implements Compare<This, R> {

protected MergeSegmentList expression;
protected Map<String, Object> paramNameValuePairs;

public Class<T> entityClass;
private Map<String, String> columnMap = null;
private boolean initColumnMap = false;



public AbstractWrapper() {
}

//实际实现
@Override
public This eq(boolean condition, R column, Object val) {
String fileName = columnToString((SFunction) column);
MergeSegment segment = new MergeSegment();
segment.setColumName(fileName);
segment.setColumValue(val);
segment.setMatchCondition(MatchCondition.EQ);
expression.add(segment);
paramNameValuePairs.putIfAbsent(fileName,val);
return (This)this;
}

@Override
public This ne(boolean condition, R column, Object val) {
String fileName = columnToString((SFunction) column);
MergeSegment segment = new MergeSegment();
segment.setColumName(fileName);
segment.setColumValue(val);
segment.setMatchCondition(MatchCondition.NE);
expression.add(segment);
paramNameValuePairs.putIfAbsent(fileName,val);
return (This)this;
}

@Override
public This gt(boolean condition, R column, Object val) {
String fileName = columnToString((SFunction) column);
MergeSegment segment = new MergeSegment();
segment.setColumName(fileName);
segment.setColumValue(val);
segment.setMatchCondition(MatchCondition.GT);
expression.add(segment);
paramNameValuePairs.putIfAbsent(fileName,val);
return (This)this;
}

@Override
public This lt(boolean condition, R column, Object val) {
String fileName = columnToString((SFunction) column);
MergeSegment segment = new MergeSegment();
segment.setColumName(fileName);
segment.setColumValue(val);
segment.setMatchCondition(MatchCondition.LT);
expression.add(segment);
paramNameValuePairs.putIfAbsent(fileName,val);
return (This)this;
}

/***
* 功能描述: 获取字段信息
* @return: java.lang.String
* @author: 程序员ken
* @date: 2021/4/28 21:34
*/
protected String columnToString(SFunction<T, ?> column) {
SerializedLambda resolve = LambdaUtils.resolve(column);
return this.getColumn(resolve);
}


private String getColumn(SerializedLambda lambda) {
String fieldName = resolveFieldName(lambda.getImplMethodName());
if (!this.initColumnMap || !this.columnMap.containsKey(fieldName)) {
String entityClassName = lambda.getImplClassName();
try{
Class<T> aClass = (Class<T>)Class.forName(entityClassName.replaceAll("\\\\", "."));
if(entityClass==null){
entityClass = aClass;
}
this.columnMap = getColumnMap(aClass);
//3.0.6 支持 ==>3.3.2 不支持
//this.columnMap = LambdaUtils.getColumnMap(entityClassName);
this.initColumnMap = true;
}catch (Exception ex){

}
}
return fieldName;
}

/**
* 功能描述: 获取当前实体的“数据库”字段
* @param aClass
* @return: java.util.Map<java.lang.String,java.lang.String>
* @author: 程序员ken
* @date: 2021/4/29 0029 下午 12:39
*/
protected Map<String,String> getColumnMap(Class<?> aClass){
Map<String,String> map = new HashMap<String,String>();
//ClassLoader classLoader = aClass.getClassLoader();
Field[] declaredFields = aClass.getDeclaredFields();
TableField tableField =null;
for (Field field:declaredFields) {
tableField = field.getAnnotation(TableField.class);

if(!(tableField!=null && !tableField.exist())){
map.putIfAbsent(field.getName(),field.getName());
}
}

return map;
}

public static String resolveFieldName(String getMethodName) {
if (getMethodName.startsWith("get")) {
getMethodName = getMethodName.substring(3);
} else if (getMethodName.startsWith("is")) {
getMethodName = getMethodName.substring(2);
}
return firstToLowerCase(getMethodName);
}

public static String firstToLowerCase(String param) {
return param==null || "".equals(param.trim()) ? "" :
param.substring(0, 1).toLowerCase() + param.substring(1);
}

}

file
file

3.记录列

file
file

3.枚举类

file

4.注解类

file

5.工具类
工具类的lambda解析的接口,我是指定了解析“继承”了Function这个接口,才会被解析,mybatis-plus里面是写死了 解析SFunction,这样限制性很大,然后脱离了mybatis-plus框架这个解析类的很多功能就用不了。

file

6.接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码package interfaces;

import java.io.Serializable;
import java.util.function.Function;

/**
* <ul>
* <li>Title: SFunction</li>
* <li>Description: TODO </li>
* </ul>
*
* @author 程序员ken
* @date 2021/4/28 0028 下午 14:33
*/

@FunctionalInterface
public interface SFunction<T, R> extends Function<T,R>, Serializable {
}

7.测试:
file

8.其他

另外在own包下我也仿写了一个这样的串写lambda的示例,所有的测试案例在LambadaTest里面doun都能找到。

file

总结:其实本文也并没有深入源码,只是让大致了解这个框架的原理。

【纸上得来终觉浅,绝知此事要躬行】

(多看看优秀的代码,这样你的代码才会有进步哦,不要做一个只会curd的boy哦)

源码地址:gitee.com/ten-ken/myb…

欢迎关注我的公众号:程序员ken,程序之路,让我们一起探索,共同进步。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%