本文除了讨论 MBG 扩展项目之外,还会顺带讨论一下有关 MySQL 配置项 lower_case_table_names
的三个值,以及在开发和生产环境如果配置不同,如何有效避免大小写敏感与否的问题。
本文对应的完整项目
- gitee: https://gitee.com/uncleAndyChen/mybatis-generator-enhance
- github: https://github.com/uncleAndyChen/mybatis-generator-enhance
进入以上项目路径可查看更详细的介绍。
关于该项目
- 本项目最初想要解决数据库表名、字段名以下划线命名法,生成的实体类(Model)与 java 的类、属性驼峰命名法不一致带来的一系列问题
后来发现通过 MyBatis 配置就能解决,下面有说明
- 除此之外,分库分表插件,需要用该项目生成XML映射文件,下面有详细说明,更详细的,请查看: MBG 扩展类 module
- 已添加查询示例,不过仅仅是查询示例,没有考虑到项目架构的合理性。实际项目不会在 web 层直接调用 dal 层,实际项目会有业务层和接口层
本项目(MBG 扩展)的作用
给表名添加MySQL“边界”,用 `(左上角数字键1左边、Tab键上边、Esc键下边的键)引起来。目的是分表时进行表名替换,把每张表的表名当作一个整体。比如需要替换 sys_user
时,可避免把 sys_user_role
中的 sys_user
也替换掉。
自己扩展的好处
- 比起直接修改MBG源代码,这种方式对MBG无代码侵入,方便将来升级MBG。
- 符合面向对象设计的【开闭原则】,即通过增加代码来为软件添加新功能,而不是直接修改原有代码。这一点,MBG做得非常好,除了可以非常方便的扩展之外,还可以写各种插件以实现自己的业务需要。
使用自己的扩展类
在配置文件generatorConfig.xml的context节点,配置runtime值,指向自己的扩展类,需要带包名,如本例:1
<context id="Mysql" targetRuntime="mybatis.generator.enhance.IntrospectedTableEnhanceImpl" defaultModelType="flat">
扩展类的代码很简单,只有十几行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import org.mybatis.generator.codegen.mybatis3.IntrospectedTableMyBatis3Impl;
public class IntrospectedTableEnhanceImpl extends IntrospectedTableMyBatis3Impl {
public String getFullyQualifiedTableNameAtRuntime() {
return getTableNameFromConfigFile();
}
public String getAliasedFullyQualifiedTableNameAtRuntime() {
return getTableNameFromConfigFile();
}
private String getTableNameFromConfigFile() {
String tableName = this.getTableConfiguration().getTableName();
return "`" + tableName + "`";
}
}
两种运行方式
以程序方式运行
仿照官方的org.mybatis.generator.api.ShellRunner
,写一段代码,以程序的方式运行。本工程的代码如下: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
33public class GeneratorEnhanceRun {
public static void main(String[] args) {
try {
List<String> warnings = new ArrayList<>();
// 初始化配置解析器
ConfigurationParser configurationParser = new ConfigurationParser(warnings);
// 获取配置文件信息
InputStream inputStream = GeneratorEnhanceRun.class.getResourceAsStream("/generatorConfig.xml");
// 调用配置解析器创建配置对象
Configuration config = configurationParser.parseConfiguration(inputStream);
// 调用配置解析器创建配置对象
DefaultShellCallback callback = new DefaultShellCallback(true);
// 创建一个MyBatisGenerator对象。MyBatisGenerator类是真正用来执行生成动作的类
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
ProgressCallback progressCallback = new VerboseProgressCallback();
// 执行生成操作
myBatisGenerator.generate(progressCallback);
// 输出警告信息
if (!warnings.isEmpty()) {
System.out.println("--------------- warnings start ---------------");
for (String warning : warnings) {
System.out.println(warning);
}
System.out.println("--------------- warnings end ---------------");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
本工程,直接运行GeneratorEnhanceRun
下的main
方法,在运行之前需要先配置好 generatorConfig.xml,并放到 resources 目录下面
cmd窗口运行jar文件
- 下载 MBG 的jar包,官方 github releases 页, 1.4.2 的 release 版本,解压,找到
mybatis-generator-1.4.2.jar
,备用。 - 执行项目根目录下的
package.bat
,生成的 jar 文件:mybatis-generator-enhance\target\mybatis-generator-enhance-0.0.1.jar
,会用到1
2
3java -Dfile.encoding=UTF-8 -cp mybatis-generator-core-1.4.2.jar;mybatis-generator-enhance\target\mybatis-generator-enhance-0.0.1.jar;mysql-connector-j-8.0.33.jar org.mybatis.generator.api.ShellRunner -configfile mybatis-generator-enhance\src\main\resources\generatorConfig.xml -overwrite -verbose
其中,-verbose 是打印执行过程
这里通过 -cp 指定需要用到的所有jar包,用分号隔开,这样在运行的时候才能找到相应的类。
把相关依赖打到一起,用一个 jar 文件执行,更方便
在项目 mybatis.generator.enhance 的 pom.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<plugin>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-assembly-plugin -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<!-- 通过此标签可覆盖默认命名规则,直接指定输出文件名 -->
<finalName>mybatis-generator-enhance-with-dependencies</finalName>
<!-- 设置为 false 可消除文件名中的分类器部分(如-jar-with-dependencies) -->
<appendAssemblyId>false</appendAssemblyId>
<!-- 此配置可指定打包文件的输出路径,下面的配置,会将 jar 输出到项目根目录 -->
<outputDirectory>${project.build.directory}/../../</outputDirectory>
<archive>
<manifest>
<mainClass>mybatis.generator.enhance.IntrospectedTableEnhanceImpl</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
通过包含相关依赖的 jar 包执行1
java -Dfile.encoding=UTF-8 -cp mybatis-generator-enhance-with-dependencies.jar org.mybatis.generator.api.ShellRunner -configfile mybatis-generator-enhance\src\main\resources\generatorConfig.xml -overwrite -verbose
原理
简单的说,就是自己的实现类IntrospectedTableEnhanceImpl
继承自MBG的一个具体实现类,重写获取表名的方法。
IntrospectedTable是MBG提供的一个比较基础的扩展类,相当于可以重新定义一个runtime。如果要通过继承IntrospectedTable完成扩展,需要自己来实现生成XML和Java代码的所有代码,也可以直接继承IntrospectedTableMyBatis3Impl,重写自己需要的业务逻辑,本模块就是直接继承自该类。
要扩展自己的业务逻辑,建议先仔细阅读IntrospectedTableMyBatis3Impl和IntrospectedTableMyBatis3SimpleImpl,这两个类用得多一些。
在MBG中,提供了几种默认的IntrospectedTable的实现,其实在context上设置的runtime对应的就是不同的IntrospectedTable的实现,下面是几种runtime和对应的实现类:
- MyBatis3 (default):org.mybatis.generator.codegen.mybatis3.IntrospectedTableMyBatis3Impl
- MyBatis3Simple:org.mybatis.generator.codegen.mybatis3.IntrospectedTableMyBatis3SimpleImpl
- Ibatis2Java2:org.mybatis.generator.codegen.ibatis2.IntrospectedTableIbatis2Java2Impl
- Ibatis2Java5:org.mybatis.generator.codegen.ibatis2.IntrospectedTableIbatis2Java5Impl
MySQL 数据库驱动版本与数据库版本问题
用高版本的数据库驱动mysql-connector-java 8.0.13
连接低版本数据库MySQL 5.7.23
,会有以下问题:
报错
1
2Cannot obtain primary key information from the database, generated objects may be incomplete
...生成的 mapper 缺少以下接口:
1
2
3
4deleteByPrimaryKey
selectByPrimaryKey
updateByPrimaryKeySelective
updateByPrimaryKey
解决
数据库驱动与数据库版本匹配即可,作者在以下两个版本(5.x与8.x)测试通过:
- 数据库驱动
mysql-connector-java 8.0.13
连接数据库MySQL 8.0.11
,对应driverClassName: com.mysql.cj.jdbc.Driver
- 数据库驱动
mysql-connector-java 5.1.29
连接数据库MySQL 5.7.23
,对应driverClassName: com.mysql.jdbc.Driver
如果要在MySQL 5.7.x
下运行,只需要修改以下两个地方(注意是5.7.x,其它5.x版本没测试):
- 修改pom.xml中
mysql-connector.version
,改为低版本5.1.39
。 - 本项目的执行,依赖根目录下的
generatorConfig.xml
文件,将其中driverClass
,由com.mysql.cj.jdbc.Driver
改为com.mysql.jdbc.Driver
。
了解一下 lower_case_table_names 参数
官方文档:Identifier Case Sensitivity
- lower_case_table_names是mysql一个大小写敏感设置的属性,此参数不可以动态修改,必须重启数据库。
- unix,linux下lower_case_table_names默认值为 0 .Windows下默认值是 1 .Mac OS X下默认值是 2。
参数说明
lower_case_table_names=0
表名存储为给定的大小写。比较时:区分大小写。大小写敏感(Unix,Linux默认)。库名、表名、字段名会原样保存。如create database TeSt;将会创建一个TeSt的目录,create table AbCCC …将会原样生成AbCCC.frm。SQL语句也会原样解析。
lower_case_table_names=1
表名存储为小写。比较时:不区分大小写。大小写不敏感(Windows默认)。创建的库表时,MySQL将所有的库表名转换成小写存储在磁盘上。SQL语句同样会将库表名转换成小写。如需要查询以前创建的Test_table(生成Test_table.frm文件),即便执行select from Test_table,也会被转换成select from test_table,致使报错表不存在。
lower_case_table_names=2
表名存储为给定的大小写。比较时:小写。库名、表名、字段名会原样保存。但SQL语句会将库名、表名转换成小写。
本文不适用场景
如果开发环境、生产环境均配置成1或者2,则本文中有关大小写敏感的措施都是无意义的。
但是如果分库时依赖表名替换,则又是适用的,见以下【适用场景】中的场景二。
本文适用场景
最终目标:MBG 生成的 XML 映射文件中,sql 脚本里的表名,保持与对应表名在建表时的大小写一致,保持大小写敏感(表名可在 MBG 需要的配置文件中配置,以该配置为准,忽略从数据库读取到的值,因为从数据库读取的值,大小写可能已经变了)。这样可以适应以上lower_case_table_names
的三种配置值。
如果单独配置表名,需要充分测试和特别小心,因为配置一旦错了,又没有测试到位,就会成为“地雷”。相比之下,数据库命名法还是使用下划线命名法最安全,可以省去很多不可控因素带来的麻烦。
为了达到以上目标,运行生成表配置内容的项目,一定要连接参数lower_case_table_names
配置为0或者2的数据库服务器,并且是配置为0或者2之后才创建的数据表,否则,生成的表配置内容的表名,是以全部小写为基准的,并非驼峰式命名法。表配置内容生成好之后,重新生成 mapper 时连接的数据库服务器的lower_case_table_names
配置值,对生成结果没有影响。
适用场景一
- 其中有数据库服务器被设置成大小写不敏感(比如阿里云的云数据库,截至目前2018年12月9号,还不支持配置成大小写敏感),即
lower_case_table_names=1
,且该参数不能修改。 - 假设程序代码遵循驼峰命名法。如果因为历史原因,或者有一个老项目,恰巧数据库也用了驼峰式命名法,包括:数据库名、数据库表名、数据库字段名。这样的话,可以控制
lower_case_table_names
的linux服务器,就可以将该参数设置为0,即大小写敏感。 - 用 MGB 生成的 Mapper 类名,以及 XML 映射文件中的表名,需要与创建表名时的原始大小写一致,以适应在
lower_case_table_names=0
(linux)或者lower_case_table_names=2
(windows)的情况。
当然,读到这里,你可能会觉得奇怪,数据库的库名、表名、字段名,业界都是用下划线命名法,全是小写字母,没有大写字母,所以,该参数被配置成什么都不需要关心。
那么,你说对了,如果数据库命名采用的是下划线命名法,那就且看工作中是否存在【适用场景二】吧。
适用场景二
- 分表,利用 MyBatis 插件,根据业务规则,对表名进行动态替换。
- 例如,表
erp_trade
分成了120个表,那么在某一次业务操作中,需要将erp_trade
替换成erp_trade_xyz
,其中xyz
为从001
到120
的其中一个数字,则需要将 MBG 生成的 XML 映射文件里 sql 脚本中的表名用 `(左上角数字键 1 的左边、Tab 键的上边、Esc 键的下边的键)引起来。 - 把表名用 ` 引起来的目的,是可以避免错误地局部替换,比如需要替换
sys_user
时,可避免把sys_user_role
中的sys_user
也替换掉。
生成表配置信息的 Java 工具类
- MBG 基于一个 xml 配置文件,在这个配置文件里,有跟表相关的配置,如果需要对不同类型的字段做处理,比如 tinyint 类型的字段,MBG 生成规则是:当长度为 1 时默认映射为 Boolean 类型,否则映射为 Byte 类型,在实际开发中,可能 tinyint 保存的是 int 类型,不光是 0 和 1,则需要通过表配置信息修改这个行为,如:
<columnOverride column="status" javaType="java.lang.Integer" jdbcType="INTEGER" />
- 如此一来,存在 tinyint 需要生成 int 属性的表,都需要对应一行配置信息,手写的话,容易遗漏,也容易出错。
- 我写了一个类来自动生成,这样,在增减表,或者别的项目里面,可以简单的运行这个类来生成,解放双手,提高效率。
更详细的,请看:https://gitee.com/uncleAndyChen/mybatis-generator-enhance/tree/master/boot-create-table-property
MBG 需要的配置文件
在工作中实际用到的文件内容,大致如下: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
<generatorConfiguration>
<!--数据库驱动-->
<classPathEntry location="mysql-connector-java-5.1.31.jar"/>
<!--<context id="DB2Tables" targetRuntime="MyBatis3">-->
<!--如果你希望不生成和Example查询有关的内容,那么可以按照如下进行配置:-->
<!--<context id="DB2Tables" targetRuntime="MyBatis3Impl">-->
<context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
<!--<context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">-->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库链接地址账号密码-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://192.168.0.130:3306/mbg?useUnivalue=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false"
userId="root" password="root">
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成Model类存放位置-->
<javaModelGenerator targetPackage="mybatis.generator.model.entity" targetProject="mybatis.generator.model/src/main/java/">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成映射文件存放位置-->
<sqlMapGenerator targetPackage="mappers\original" targetProject="mybatis.generator.dal\src\main\resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成Dao类存放位置
当type=XMLMAPPER时,会生成一个XXX.xml文件内有各种sql语句,是mapper的实现。
当type=ANNOTATEDMAPPER时,会直接在mapper接口上添加注释。
-->
<!--
http://blog.csdn.net/qq_27376871/article/details/51360638
MyBatis3:
ANNOTATEDMAPPER:基于注解的Mapper接口,不会有对应的XML映射文件
MIXEDMAPPER:XML和注解的混合形式,(上面这种情况中的)SqlProvider注解方法会被XML替代。
XMLMAPPER:所有的方法都在XML中,接口调用依赖XML文件。
MyBatis3Simple:
ANNOTATEDMAPPER:基于注解的Mapper接口,不会有对应的XML映射文件
XMLMAPPER:所有的方法都在XML中,接口调用依赖XML文件。
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="mybatis.generator.dal.mapper"
targetProject="mybatis.generator.dal/src/main/java/">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 生成对应表及类名,以下仅为配置示例 -->
<!-- 如何批量自动生成所有表的属性配置?请参考:https://gitee.com/uncleAndyChen/mybatis-generator-enhance/tree/master/boot-create-table-property -->
<table tableName="sys_dept" domainObjectName="SysDept"><generatedKey identity="true" type="post" column="id" sqlStatement="Mysql"/><columnOverride column="status" javaType="java.lang.Integer" jdbcType="INTEGER" /></table>
<table tableName="sys_menu" domainObjectName="SysMenu"><generatedKey identity="true" type="post" column="id" sqlStatement="Mysql"/><columnOverride column="status" javaType="java.lang.Integer" jdbcType="INTEGER" /></table>
</context>
</generatorConfiguration>
注意事项
- 当表结构发生变化时,需要重新运行 MBG 生成新的代码,所以,生成的代码,不能有修改行为,否则下次重新生成后,改过的代码会被覆盖。
- 重新生成时,*Mapper.xml 文件会被追加内容,而不是重新生成该文件,所以是有问题的,应对方法就是每次重新生成之前将旧的文件删除。
MBG 1.3.7 有这个问题,后面的版本,暂时未做测试
- 下面的脚本在window下测试通过,删除脚本和生成脚本一起执行即可。重新生成之后,如果文件内容跟原来一致,文件会被认为无修改。
- 需要注意的是,如果执行删除全部文件操作,需要保证所有表的配置保持与数据库同步,表配置相当重要,有则生成。
经验
如果库里有一张 user,在用 MBG 生成代码文件的时候,可能会遇到类似如下的提示信息:1
2
3
4Table Configuration user matched more than one table (forTest..user,mysql..user,xunwu..user,bbs..user)
Column userId, specified as an identity column in table user, does not exist in the table.
Column userId, specified as an identity column in table user, does not exist in the table.
Column userId, specified as an identity column in table user, does not exist in the table.
意思是,在多个库里都存在 user 表,而其中有三个库中的 user 表不存在 userId 这个字段。
所以,在作业务表设计的时候,最好给所有表名加上能区别业务的前缀,这样可以有效避免与其它库的表名冲突,这样做不光是为了适应 MBG,在做业务开发的时候同样会带来好处。在做类设计的时候,适当的添加前缀或者后缀,也能让人一下子知道类的作用,这需要在实际工作中才能体会到。
当然,针对 MBG 的这个问题,是有解决办法的:
- 解决方案一:在MBG配置文件的
jdbcConnection
项下添加:<property name="nullCatalogMeansCurrent" value="true"/>
可以解决(注:本文中的配置示例已添加)。 - 解决方案二:在 table 配置项添加 catalog 属性,如:
<table catalog="mbg" tableName="sys_dept" domainObjectName="SysDept"><generatedKey identity="true" type="post" column="id" sqlStatement="Mysql"/><columnOverride column="status" javaType="java.lang.Integer" jdbcType="INTEGER" /></table>
更详细的,请参考:解决 mybatis generator 使用新版 mysql 驱动 8.0 版本时会生成用户下多个库里的表的问题