MyBatis Generator

配置文件详细配置


generatorConfig.xml

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<!-- 可以用于加载配置项或者配置文件,在整个配置文件中就可以使用${propertyKey}的方式来引用配置项
resource:配置资源加载地址,使用resource,MBG从classpath开始找,
比如com/myproject/generatorConfig.properties
url:配置资源加载地质,使用URL的方式,
比如file:///C:/myfolder/generatorConfig.properties.
注意,两个属性只能选址一个;

另外,如果使用了mybatis-generator-maven-plugin,
那么在pom.xml中定义的properties都可以直接在generatorConfig.xml中
使用<properties resource="" url="" />
-->

<!-- 在MBG工作的时候,需要额外加载的依赖包
location属性指明加载jar/zip包的全路径
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
-->

<!--
context:生成一组对象的环境
id:必选,上下文id,用于在生成错误时提示
defaultModelType:指定生成对象的样式
1,conditional:类似hierarchical;
2,flat:所有内容(主键,blob)等全部生成在一个对象中;
3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,
其他简单属性在一个对象中(record class)
targetRuntime:
1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
introspectedColumnImpl:类全限定名,用于扩展MBG
-->
<context id="mysql" defaultModelType="hierarchical" targetRuntime="MyBatis3Simple" >

<!-- 自动识别数据库关键字,默认false,如果设置为true,
根据SqlReservedWords中定义的关键字列表;一般保留默认值,遇到数据库关键字(Java关键字),
使用columnOverride覆盖
-->
<property name="autoDelimitKeywords" value="false"/>
<!-- 生成的Java文件的编码 -->
<property name="javaFileEncoding" value="UTF-8"/>
<!-- 格式化java代码 -->
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化XML代码 -->
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

<!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,
比如ORACLE就是双引号,MYSQL默认是`反引号; -->
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>

<!-- 必须要有的,使用这个配置链接数据库
@TODO:是否可以扩展
-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql:///pss" userId="root" password="admin">
<!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 -->
</jdbcConnection>

<!-- java类型处理器
用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型;
-->
<javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
<!--
true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
false:默认,
scale>0;length>18:使用BigDecimal;
scale=0;length[10,18]:使用Long;
scale=0;length[5,9]:使用Integer;
scale=0;length<5:使用Short;
-->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>


<!-- java模型创建器,是必须要的元素负责:
1,key类(见context的defaultModelType);
2,java类;
3,查询类
targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,
如果目录不存在,MBG不会自动建目录
-->
<javaModelGenerator targetPackage="com._520it.mybatis.domain" targetProject="src/main/java">
<!-- for MyBatis3/MyBatis3Simple
自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter;
-->
<property name="constructorBased" value="false"/>

<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,
最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>

<!-- for MyBatis3 / MyBatis3Simple
是否创建一个不可变的类,如果为true,
那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类
-->
<property name="immutable" value="false"/>

<!-- 设置一个根对象,
如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;
在Table的rootClass属性中可以覆盖该选项
注意:如果在key class或者record class中有root class相同的属性,
MBG就不会重新生成这些属性了,包括:
1,属性名相同,类型相同,有相同的getter/setter方法;
-->
<property name="rootClass" value="com._520it.mybatis.domain.BaseDomain"/>

<!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>


<!-- 生成SQL map的XML文件生成器,
注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),
或者只使用Mapper接口+Annotation,所以,
如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
targetPackage/targetProject:同javaModelGenerator
-->
<sqlMapGenerator targetPackage="com._520it.mybatis.mapper" targetProject="src/main/resources">
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,
最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>


<!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,
那么默认不会生成Mapper接口 targetPackage/targetProject:同javaModelGenerator
type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),
不会生成对应的XML;
2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,
但是XML会生成在XML中;
3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER
-->
<javaClientGenerator targetPackage="com._520it.mybatis.mapper" type="ANNOTATEDMAPPER" targetProject="src/main/java">
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,
最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>

<!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查
<property name="rootInterface" value=""/>
-->
</javaClientGenerator>

<!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
选择的table会生成一下文件:
1,SQL map文件
2,生成一个主键类;
3,除了BLOB和主键的其他字段的类;
4,包含BLOB的类;
5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
6,Mapper接口(可选)

tableName(必要):要生成对象的表名;
注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,
在一般情况下,MBG会根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,
就精确的使用指定的大小写格式去查询;
2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
4,否则,使用指定的大小写格式查询;
另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,
就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;

可选:
1,schema:数据库的schema;
2,catalog:数据库的catalog;
3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,
列名会变成:alias_actualColumnName
4,domainObjectName:生成的domain类的名字,如果不设置,
直接使用表名作为domain类的名字;可以设置为somepck.domainName,
那么会自动把domainName类再放到somepck包里面;
5,enableInsert(默认true):指定是否生成insert语句;
6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
11,enableCountByExample(默认true):MyBatis3Simple为false,
指定是否生成动态查询总条数语句(用于分页的总条数查询);
12,enableUpdateByExample(默认true):MyBatis3Simple为false,
指定是否生成动态修改语句(只修改对象中不为空的属性);
13,modelType:参考context元素的defaultModelType,相当于覆盖;
14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,
如果类似MYSQL这样的数据库,使用的是`(反引号,
那么还需要设置context的beginningDelimiter和endingDelimiter属性)
15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。
默认为false,delimitIdentifiers参考context的属性

注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写;
-->
<table tableName="userinfo" >

<!-- 参考 javaModelGenerator 的 constructorBased属性-->
<property name="constructorBased" value="false"/>

<!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; -->
<property name="ignoreQualifiersAtRuntime" value="false"/>

<!-- 参考 javaModelGenerator 的 immutable 属性 -->
<property name="immutable" value="false"/>

<!-- 指定是否只生成domain类,如果设置为true,只生成domain类,
如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 -->
<property name="modelOnly" value="false"/>

<!-- 参考 javaModelGenerator 的 rootClass 属性
<property name="rootClass" value=""/>
-->

<!-- 参考javaClientGenerator 的 rootInterface 属性
<property name="rootInterface" value=""/>
-->

<!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,
而不是table元素上的catalog
<property name="runtimeCatalog" value=""/>
-->

<!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,
而不是table元素上的schema
<property name="runtimeSchema" value=""/>
-->

<!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,
而不是table元素上的tablename
<property name="runtimeTableName" value=""/>
-->

<!-- 注意,该属性只针对MyBatis3Simple有用;
如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,
如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件;
-->
<property name="selectAllOrderByClause" value="age desc,username asc"/>

<!-- 如果设置为true,生成的model类会直接使用column本身的名字,
而不会再使用驼峰命名方法,比如BORN_DATE,生成的属性名字就是BORN_DATE,
而不会是bornDate -->
<property name="useActualColumnNames" value="false"/>


<!-- generatedKey用于生成生成主键的方法,
如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,
该元素可选 column:主键的列名;
sqlStatement:要生成的selectKey语句,有以下可选项:
Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
DB2 :相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
DB2_MF :相当于selectKey的SQL为:
SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
Derby :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL()
HSQLDB :相当于selectKey的SQL为:CALL IDENTITY()
Informix :相当于selectKey的SQL为:
select dbinfo('sqlca.sqlerrd1') from systables where tabid=1
MySql :相当于selectKey的SQL为:SELECT LAST_INSERT_ID()
SqlServer :相当于selectKey的SQL为:SELECT SCOPE_IDENTITY()
SYBASE :相当于selectKey的SQL为:SELECT @@IDENTITY
JDBC :相当于在生成的insert元素上添加useGeneratedKeys="true"
和keyProperty属性
<generatedKey column="" sqlStatement=""/>
-->

<!--
该元素会在根据表中列名计算对象属性名之前先重命名列名,
非常适合用于表中的列都有公用的前缀字符串的时候,
比如列名为:CUST_ID,CUST_NAME,CUST_EMAIL,CUST_ADDRESS等;
那么就可以设置searchString为"^CUST_",并使用空白替换,
那么生成的Customer对象中的属性名称就不是
custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email;

注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的,
如果使用了columnOverride元素,该属性无效;

<columnRenamingRule searchString="" replaceString=""/>
-->


<!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性;
column:要重新设置的列名;
注意,一个table元素中可以有多个columnOverride元素哈~
-->
<columnOverride column="username">
<!-- 使用property属性来指定列要生成的属性名称 -->
<property name="property" value="userName"/>

<!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名
<property name="javaType" value=""/>
-->

<!-- jdbcType用于指定该列的JDBC类型
<property name="jdbcType" value=""/>
-->

<!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名
注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler
只会生成类似:where id =
#{id,jdbcType=BIGINT,typeHandler=com._520it.mybatis.MyTypeHandler}的参数描述
<property name="jdbcType" value=""/>
-->

<!-- 参考table元素的delimitAllColumns配置,默认为false
<property name="delimitedColumnName" value=""/>
-->
</columnOverride>

<!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,
生成的SQL中,都不会有该列出现
column:指定要忽略的列的名字;
delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false

注意,一个table元素中可以有多个ignoreColumn元素
<ignoreColumn column="deptId" delimitedColumnName=""/>
-->
</table>

</context>

</generatorConfiguration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
</dependencies>
</plugin>
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
<!-- 3.创建 sqlSEssion -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="mySqlSession">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mappers/*"/>
<property name="typeAliasesPackage" value="cn.lizhaoloveit.crm"/>
<property name="configurationProperties">
<value>
lazyLoadingEnabled=true
aggressiveLazyLoading=false
lazyLoadTriggerMethods=equals,clone,hashCode
</value>
</property>
<!-- 注意其他配置 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--当pageNum<=0时,将pageNum设置为1-->
<!--当pageNum>pages时,将pageNum设置为pages-->
<value>
reasonable=true
</value>
</property>
</bean>
</array>
</property>
</bean>

或者将参数配置在 mybatis.xml 中 然后再 SqlSession 中引入 xml 文件

1
2
3
4
5
6
7
<!-- 3.创建 sqlSEssion -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="mySqlSession">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mappers/*"/>
<property name="typeAliasesPackage" value="cn.lizhaoloveit.crm"/>
<property name="configuration" value="classpath:mybatis.xml">
</bean>

自定义 domain、mapper、xml文件


自定义逆向生成的文件有两种可行的方案,一种是修改 mybatis-generator 包。我个人觉得最好不要动别人写好的 jar 包。那么我选择使用第二种方式,动态扩展 mybatis-generator

jdbc 项目目录结构如下:

在项目中导入 mybatis-generator-core ,这里我使用的是 1.3.5 版本。

在实战中,我们希望 domain 集成 lombok,而且需要清除讨厌的 get set 方法。这里对时间采用了 DATE_FORMAT 处理,DATE_FORMAT 只做输出的处理。不做索引的处理。

注:在查询时,索引键上避免使用 mysql 函数,会导致索引失效。

如果要动态扩展生成类,需要采用 main 函数的方式运行 generator,如果要使用插件运行,则需要修改 jar 包。

1
2
3
4
5
6
7
8
9
10
11
12
public class TestGenerator {
public static void main(String[] args) throws Exception {
File configFile = new ClassPathResource("generatorConfig.xml").getFile();
List<String> warnings = new ArrayList<String>();
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(true);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
System.out.println("代码生成完毕>>>>>>>>>>>>");
}
}

下面是配置文件:generatorConfig.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<context id="mysql" defaultModelType="hierarchical" targetRuntime="MyBatis3Simple">

<!-- 自动识别数据库关键字,默认 false,如果设置为 true,根据 SqlReservedWords 中定义的关键字列表;
一般保留默认值,遇到数据库关键字(Java关键字),使用 columnOverride 覆盖
-->
<property name="autoDelimitKeywords" value="false"/>
<!-- 生成的 Java 文件的编码 -->
<property name="javaFileEncoding" value="UTF-8"/>
<!-- 格式化 java 代码 -->
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化 XML 代码 -->
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>

<!-- 集成 lombok -->
<plugin type="com.pcacce.jdbc.plugins.mybatis.generator.LombokPlugin">
<property name="hasLombok" value="true"/>
</plugin>


<plugin type="com.pcacce.jdbc.plugins.mybatis.generator.AMResultMapPlugin"></plugin>

<!-- 注释生成器 -->
<commentGenerator type="com.pcacce.jdbc.plugins.mybatis.generator.MyCommentGeneratorPlugin">
<!-- <property name="suppressAllComments" value="true"/>-->
</commentGenerator>

<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/pc_acce_admin?characterEncoding=UTF-8&amp;useSSL=false&amp;useUnicode=true&amp;nullCatalogMeansCurrent=true"
userId="xxx"
password="xxx.">
</jdbcConnection>

<!-- <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">-->
<!-- <property name="forceBigDecimals" value="false"/>-->
<!-- </javaTypeResolver>-->
<javaTypeResolver type="com.pcacce.jdbc.plugins.mybatis.generator.resolver.AMJavaTypeResolver">
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>

<javaModelGenerator targetPackage="com.pcacce.jdbc.domain.admin"
targetProject="/Users/Ammar/Desktop/work/pcacce/pcacce-jdbc/src/main/java">
<property name="constructorBased" value="false"/>
<property name="enableSubPackages" value="false"/>
<property name="immutable" value="false"/>
<property name="trimStrings" value="true"/>
<property name="rootClass" value="com.pcacce.common.base.BaseDomain"/>
</javaModelGenerator>

<sqlMapGenerator targetPackage="mappers/admin"
targetProject="/Users/Ammar/Desktop/work/pcacce/pcacce-jdbc/src/main/resources">
<!-- targetProject="src/main/resources">-->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>


<javaClientGenerator targetPackage="com.pcacce.jdbc.mapper.admin" type="XMLMAPPER"
targetProject="/Users/Ammar/Desktop/work/pcacce/pcacce-jdbc/src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>

<table tableName="admin_user" domainObjectName="AdminUser"
enableUpdateByPrimaryKey="false"
enableDeleteByPrimaryKey="false"
enableSelectByPrimaryKey="false"
selectByPrimaryKeyQueryId="false"
enableInsert="false">

<property name="constructorBased" value="false"/>
<property name="ignoreQualifiersAtRuntime" value="false"/>
<property name="immutable" value="false"/>
<property name="modelOnly" value="false"/>

<!-- 想要将这个属性合并到entity中需要在xml添加如下配置即可,这样就不会生成WithBLOBS.java文件了 -->
<!-- <columnOverride column="feedback_text" javaType="java.lang.String" jdbcType="VARCHAR" />-->

<!-- 设置使用驼峰命名 false 是使用驼峰命名 -->
<property name="useActualColumnNames" value="false"/>
<generatedKey column="id" sqlStatement="JDBC"/>
</table>
</context>
</generatorConfiguration>

AMJavaTypeResolver 类中仅修改了所有的 Date 类型在转为 model 时变为 String 类型。

MyCommentGeneratorPlugin

作用:注释类的修改

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
148
package com.pcacce.jdbc.plugins.mybatis.generator;

import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.InnerClass;
import org.mybatis.generator.api.dom.java.InnerEnum;
import org.mybatis.generator.api.dom.java.JavaElement;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.internal.util.StringUtility;

import java.util.Properties;

import static org.mybatis.generator.internal.util.StringUtility.isTrue;

public class MyCommentGeneratorPlugin implements CommentGenerator {

private Properties properties;
private boolean suppressDate;
private boolean suppressAllComments;

public MyCommentGeneratorPlugin() {
super();
properties = new Properties();
suppressDate = false;
suppressAllComments = false;
}

public void addJavaFileComment(CompilationUnit compilationUnit) {
return;
}


public void addComment(XmlElement xmlElement) {
if (suppressAllComments) {
return;
}
}

public void addRootComment(XmlElement rootElement) {
return;
}

public void addConfigurationProperties(Properties properties) {
this.properties.putAll(properties);

suppressDate = isTrue(properties
.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_DATE));

suppressAllComments = isTrue(properties
.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_ALL_COMMENTS));
}

protected void addJavadocTag(JavaElement javaElement,
boolean markAsDoNotDelete) {
if (suppressAllComments) {
return;
}
}


public void addClassComment(InnerClass innerClass,
IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
}

public void addEnumComment(InnerEnum innerEnum,
IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
}

public void addFieldComment(Field field,
IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
/*if (suppressAllComments) {
return;
}*/

StringBuilder sb = new StringBuilder();

field.addJavaDocLine("/**"); //$NON-NLS-1$

sb.append(" * "); //$NON-NLS-1$
sb.append(introspectedColumn.getRemarks());
field.addJavaDocLine(sb.toString());

field.addJavaDocLine(" */"); //$NON-NLS-1$

String column = introspectedColumn.getActualColumnName();
if (StringUtility.stringContainsSpace(column) || introspectedTable.getTableConfiguration().isAllColumnDelimitingEnabled()) {
column = introspectedColumn.getContext().getBeginningDelimiter()
+ column
+ introspectedColumn.getContext().getEndingDelimiter();
}
}

public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
}

@Override
public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
}

public void addGeneralMethodComment(Method method,
IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
}

public void addGetterComment(Method method,
IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
}

public void addSetterComment(Method method,
IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
}

public void addClassComment(InnerClass innerClass,
IntrospectedTable introspectedTable, boolean markAsDoNotDelete) {
if (suppressAllComments) {
return;
}
}
}

LombokPlugin.java

作用是将 domain 类加入 lombok,且去掉 get、set

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
package com.pcacce.jdbc.plugins.mybatis.generator;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;

import java.util.List;

/**
* DESCRIPTION:
* Author: ammar
* Date: 2020-07-26
* Time: 23:09
*/
public class LombokPlugin extends PluginAdapter {
@Override
public boolean validate(List<String> list) {
return true;
}

@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

//添加domain的import
topLevelClass.addImportedType("lombok.Getter");
topLevelClass.addImportedType("lombok.Setter");
topLevelClass.addImportedType("lombok.NoArgsConstructor");

//添加domain的注解
topLevelClass.addAnnotation("@Setter");
topLevelClass.addAnnotation("@Getter");
topLevelClass.addAnnotation("@NoArgsConstructor");
return true;
}

@Override
public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
//不生成getter
return false;
}

@Override
public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
//不生成setter
return false;
}
}

以下是生成的 domain 类

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
package com.pcacce.jdbc.domain.admin;

import com.pcacce.common.base.BaseDomain;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
public class AdminUser extends BaseDomain {
/**
* 用户ID
*/
private Long id;

/**
* 账户
*/
private String account;

/**
* 密码
*/
private String password;

/**
* 账户类型(是否是admin)
*/
private Boolean admin;

/**
* 状态(true可用 false不可用)
*/
private Boolean state;

/**
* 性别(0女 1男)
*/
private Integer sex;

/**
* 描述
*/
private String remark;

/**
* 添加时间
*/
private String createAt;

/**
* 更改时间
*/
private String updateAt;

/**
* 登录令牌
*/
private String token;

/**
* 登录时间
*/
private String loginAt;
}

AMResultMapPlugin

接下来自定义 sqlMap(xml)、client(Mapper.java) 插件即可,由于代码比较简单,看看就明白了。

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
package com.pcacce.jdbc.plugins.mybatis.generator;

import com.pcacce.common.exception.MybatisPluginException;
import com.pcacce.common.util.StringUtil;
import org.apache.logging.log4j.util.Strings;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;

import java.util.List;

/**
* DESCRIPTION:
* Author: ammar
* Date: 2020-07-28
* Time: 00:32
*/
public class AMResultMapPlugin extends PluginAdapter {

private static String publicSet = "publicSet";
private static String publicOut = "publicOut";

@Override
public boolean validate(List<String> warnings) {
return true;
}

public boolean sqlMapResultMapWithoutBLOBsElementGenerated(
XmlElement element, IntrospectedTable introspectedTable) {
return false;
}

public boolean sqlMapSelectAllElementGenerated(XmlElement element,
IntrospectedTable introspectedTable) {
return false;
}

public boolean clientSelectAllMethodGenerated(Method method,
Interface interfaze, IntrospectedTable introspectedTable) {
return false;
}


@Override
public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

// 表是否有主键
if (introspectedTable.getPrimaryKeyColumns() == null
|| introspectedTable.getPrimaryKeyColumns().size() == 0) {
throw new MybatisPluginException("无主键!");
}

String qualifiedType = introspectedTable.getBaseRecordType();
String simpleType = qualifiedType.substring(qualifiedType.lastIndexOf(".") + 1);
IntrospectedColumn primary = introspectedTable.getPrimaryKeyColumns().get(0);
interfaze.addImportedType(new FullyQualifiedJavaType(qualifiedType));

// insert
Method insertMethod = new Method("insert");
insertMethod.setReturnType(FullyQualifiedJavaType.getIntInstance());
insertMethod.addParameter(new Parameter(new FullyQualifiedJavaType(simpleType),
StringUtil.firstToLowerCase(simpleType)));
context.getCommentGenerator().addGeneralMethodComment(insertMethod, introspectedTable);
interfaze.addMethod(insertMethod);

// update
Method updateMethod = new Method("update");
updateMethod.setReturnType(FullyQualifiedJavaType.getIntInstance());
updateMethod.addParameter(new Parameter(new FullyQualifiedJavaType(simpleType),
StringUtil.firstToLowerCase(simpleType)));
context.getCommentGenerator().addGeneralMethodComment(updateMethod, introspectedTable);
interfaze.addMethod(updateMethod);

// selectByPrimaryKey
Method selectMethod = new Method("selectByPrimaryKey");
selectMethod.setReturnType(new FullyQualifiedJavaType(simpleType));
selectMethod.addParameter(new Parameter(primary.getFullyQualifiedJavaType(), primary.getJavaProperty()));
context.getCommentGenerator().addGeneralMethodComment(selectMethod, introspectedTable);
interfaze.addMethod(selectMethod);

return true;
}

@Override
public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {

// 表是否有主键
if (introspectedTable.getPrimaryKeyColumns() == null
|| introspectedTable.getPrimaryKeyColumns().size() == 0) {
throw new MybatisPluginException("无主键!");
}

// 一些后面会用到的变量
XmlElement parentElement = document.getRootElement();
List<IntrospectedColumn> baseColumns = introspectedTable.getBaseColumns();
String parameterType = introspectedTable.getBaseRecordType();
IntrospectedColumn primary = introspectedTable.getPrimaryKeyColumns().get(0);
String tableName = introspectedTable.getFullyQualifiedTableNameAtRuntime();
String dbUrl = introspectedTable.getContext().getJdbcConnectionConfiguration().getConnectionURL();
String dbName = dbUrl.substring(dbUrl.lastIndexOf("/") + 1, dbUrl.indexOf("?"));
XmlElement includeSetElement = new XmlElement("include");
includeSetElement.addAttribute(new Attribute("refid", publicSet));
XmlElement includeOutElement = new XmlElement("include");
includeOutElement.addAttribute(new Attribute("refid", publicOut));
String wherePrimaryKeyStr = "where " + primary.getActualColumnName() + " = " + "#{" + primary.getJavaProperty() + "}";

StringBuffer outSqlStr = new StringBuffer();
StringBuffer insertSqlStr = new StringBuffer();
StringBuffer valueSqlStr = new StringBuffer();

// resultMap
XmlElement result = new XmlElement("resultMap");
result.addAttribute(new Attribute("id", "BaseResultMap"));
result.addAttribute(new Attribute("type", parameterType));
XmlElement id = new XmlElement("id");
id.addAttribute(new Attribute("column", primary.getActualColumnName()));
id.addAttribute(new Attribute("property", primary.getJavaProperty()));
result.addElement(id);

for (int i = 0; i < introspectedTable.getBaseColumns().size(); i++) {
IntrospectedColumn introspectedColumn = introspectedTable.getBaseColumns().get(i);
boolean isLastCol = (i == introspectedTable.getBaseColumns().size() - 1);
XmlElement columnElement = new XmlElement("result");
columnElement.addAttribute(new Attribute("column", introspectedColumn.getActualColumnName()));
columnElement.addAttribute(new Attribute("property", introspectedColumn.getJavaProperty()));
result.addElement(columnElement);

insertSqlStr.append(getInsertSqlStr(introspectedColumn, isLastCol));
valueSqlStr.append(getValuesSqlStr(introspectedColumn, isLastCol));
if ((i + 1) % 3 == 0) {
insertSqlStr.append("\n\t\t");
valueSqlStr.append("\n\t\t");
}
}

// updateSql
XmlElement updateSql = new XmlElement("sql");
updateSql.addAttribute(new Attribute("id", publicSet));

// 添加 publicOut 和 publicSet
for (int i = 0; i < introspectedTable.getAllColumns().size(); i++) {
IntrospectedColumn introspectedColumn = introspectedTable.getAllColumns().get(i);
boolean isLastCol = (i == introspectedTable.getAllColumns().size() - 1);
outSqlStr.append(getOutSqlStr(introspectedColumn, tableName, isLastCol));
updateSql.addElement(getSetSqlStr(introspectedColumn, isLastCol));
}

// outSql
XmlElement outSql = new XmlElement("sql");
outSql.addAttribute(new Attribute("id", publicOut));
outSql.addElement(new TextElement(outSqlStr.toString()));

// insert
XmlElement insertElement = new XmlElement("insert");
insertElement.addAttribute(new Attribute("id", "insert"));
insertElement.addAttribute(new Attribute("parameterType", parameterType));
insertElement.addAttribute(new Attribute("keyColumn", primary.getActualColumnName()));
insertElement.addAttribute(new Attribute("keyProperty", primary.getJavaProperty()));
insertElement.addAttribute(new Attribute("useGeneratedKeys", "true"));
String insertStr = "insert into " + dbName + "." + tableName + " (" + insertSqlStr + ")\n\t";
String valueStr = "values (" + valueSqlStr + ")";
insertElement.addElement(new TextElement(insertStr + valueStr));

// update
XmlElement updateElement = new XmlElement("update");
updateElement.addAttribute(new Attribute("id", "update"));
updateElement.addElement(new TextElement("update " + dbName + "." + tableName));
XmlElement setElement = new XmlElement("set");
setElement.addElement(includeSetElement);
updateElement.addElement(setElement);
updateElement.addElement(new TextElement(wherePrimaryKeyStr));

// selectByPrimaryKey
XmlElement selectElement = new XmlElement("select");
selectElement.addAttribute(new Attribute("id", "selectByPrimaryKey"));
selectElement.addAttribute(new Attribute("resultMap", "BaseResultMap"));
selectElement.addElement(new TextElement("select"));
selectElement.addElement(includeOutElement);
selectElement.addElement(new TextElement("from " + dbName + "." + tableName + "\n\t" + wherePrimaryKeyStr));

parentElement.addElement(new TextElement(""));
parentElement.addElement(result);
parentElement.addElement(new TextElement(""));
parentElement.addElement(outSql);
parentElement.addElement(new TextElement(""));
parentElement.addElement(updateSql);
parentElement.addElement(new TextElement(""));
parentElement.addElement(insertElement);
parentElement.addElement(new TextElement(""));
parentElement.addElement(updateElement);
parentElement.addElement(new TextElement(""));
parentElement.addElement(selectElement);

return true;
}

private String getValuesSqlStr(IntrospectedColumn introspectedColumn, boolean isLastCol) {
String property = introspectedColumn.getJavaProperty();
String sqlStr = "#{" + property + "}";
if (isLastCol) return sqlStr;
return sqlStr + ", ";
}

private String getInsertSqlStr(IntrospectedColumn introspectedColumn, boolean isLastCol) {
String columnName = introspectedColumn.getActualColumnName();
if (isLastCol) return columnName;
return columnName + ", ";
}

private String getOutSqlStr(IntrospectedColumn introspectedColumn, String tableName, boolean isLastCol) {
String columnName = introspectedColumn.getActualColumnName();
String sqlStr = tableName + "." + columnName;
if (introspectedColumn.getJdbcTypeName().equalsIgnoreCase("TIMESTAMP")
|| introspectedColumn.getJdbcTypeName().equalsIgnoreCase("TIME")
|| introspectedColumn.getJdbcTypeName().equalsIgnoreCase("DATE")) {
sqlStr = "DATE_FORMAT(" + sqlStr + ",'%Y-%m-%d %H:%i:%s') AS " + columnName;
}
if (isLastCol) return "\t" + sqlStr;
return "\t" + sqlStr + ",\n\t";
}

private XmlElement getSetSqlStr(IntrospectedColumn introspectedColumn, boolean isLastCol) {
String columnName = introspectedColumn.getActualColumnName();
String sqlStr = columnName;
XmlElement ifElement = new XmlElement("if");
ifElement.addAttribute(new Attribute("test", introspectedColumn.getJavaProperty() + " != null"));
ifElement.addElement(new TextElement(introspectedColumn.getActualColumnName()
+ " = " + "#{" + introspectedColumn.getJavaProperty() + (isLastCol ? "}" : "},")));
return ifElement;
}
}

最终生成的样式

mapper.java

1
2
3
4
5
6
7
8
9
10
11
package com.pcacce.jdbc.mapper.admin;

import com.pcacce.jdbc.domain.admin.AdminUser;

public interface AdminUserMapper {
int insert(AdminUser adminUser);

int update(AdminUser adminUser);

AdminUser selectByPrimaryKey(Long id);
}

xml

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pcacce.jdbc.mapper.admin.AdminUserMapper">

<resultMap id="BaseResultMap" type="com.pcacce.jdbc.domain.admin.AdminUser">
<id column="id" property="id" />
<result column="account" property="account" />
<result column="password" property="password" />
<result column="admin" property="admin" />
<result column="state" property="state" />
<result column="sex" property="sex" />
<result column="remark" property="remark" />
<result column="create_at" property="createAt" />
<result column="update_at" property="updateAt" />
<result column="token" property="token" />
<result column="login_at" property="loginAt" />
</resultMap>

<sql id="publicOut">
admin_user.id,
admin_user.account,
admin_user.password,
admin_user.admin,
admin_user.state,
admin_user.sex,
admin_user.remark,
DATE_FORMAT(admin_user.create_at,'%Y-%m-%d %H:%i:%s') AS create_at,
DATE_FORMAT(admin_user.update_at,'%Y-%m-%d %H:%i:%s') AS update_at,
admin_user.token,
DATE_FORMAT(admin_user.login_at,'%Y-%m-%d %H:%i:%s') AS login_at
</sql>

<sql id="publicSet">
<if test="id != null">
id = #{id},
</if>
<if test="account != null">
account = #{account},
</if>
<if test="password != null">
password = #{password},
</if>
<if test="admin != null">
admin = #{admin},
</if>
<if test="state != null">
state = #{state},
</if>
<if test="sex != null">
sex = #{sex},
</if>
<if test="remark != null">
remark = #{remark},
</if>
<if test="createAt != null">
create_at = #{createAt},
</if>
<if test="updateAt != null">
update_at = #{updateAt},
</if>
<if test="token != null">
token = #{token},
</if>
<if test="loginAt != null">
login_at = #{loginAt}
</if>
</sql>

<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.pcacce.jdbc.domain.admin.AdminUser" useGeneratedKeys="true">
insert into pc_acce_admin.admin_user (account, password, admin,
state, sex, remark,
create_at, update_at, token,
login_at)
values (#{account}, #{password}, #{admin},
#{state}, #{sex}, #{remark},
#{createAt}, #{updateAt}, #{token},
#{loginAt})
</insert>

<update id="update">
update pc_acce_admin.admin_user
<set>
<include refid="publicSet" />
</set>
where id = #{id}
</update>

<select id="selectByPrimaryKey" resultMap="BaseResultMap">
select
<include refid="publicOut" />
from pc_acce_admin.admin_user
where id = #{id}
</select>
</mapper>

以上,大家也可以参考我的代码自定义自己的xml和mapper.java类。

文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2020/07/28/MyBatis-Generator/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论