前言
说到ORM工具,Mybatis无疑是当下最流行的一款。搭建一个新的项目首先就要集成Mybatis,通过Mybatis Generator逆向生成基本的增删查改xml文件和Mapper接口文件,代码中可以直接使用其进行数据库操作可以说非常方便。但是基本的增删查改不能满足复杂的业务需求,当我们在xml文件和Mapper接口文件中编写了大量的自定义方法后,有一天需求需要变更字段,这时就需要重新生成xml文件和Mapper接口文件,如果在原项目目录中直接生成原来文件会被覆盖掉,我们编写的大量的自定义方法会因此而丢失。之前的做法是换一个目录生成,将生成的新文件内容拷贝到原来的文件中,这样做是可行的,但是原来的文件已经被自定义方法污染严重,生成的方法和自定义的方法交融在一起,需要很仔细的去寻找一不小心就回删除过多代码,导致程序出错。
本文将以一种全新的方式解决上面的问题,不需要人工拷贝代码替换。具体做法是通过开发Mybatis Generator Plugin的方式对原来的Mybatis Generator功能进行增强,使其完美解决各种现实开发场景遇到问题。
Mybatis Generator 插件介绍
Mybatis Generator工具除了具有基本的生成代码功能,还提供了插件功能,用户拓展其功能,工具内置了许多拓展插件,如下:
其中CachePlugin插件将为生成的Xml添加缓存支持相关代码,如MybatisGerneratorConfig.xml文件中添加代码如下:
<plugin type="org.mybatis.generator.plugins.CachePlugin" >
<property name="cache_type" value="org.mybatis.caches.ehcache.LoggingEhcache"/>
</plugin>
添加代码后,MybatisGerneratorConfig.xml文件内容片段如下:
最终生成的xml文件片段如下:
接下来通过开发自己的Plugin来解决数据库变更后,再次使用Mybatis Generator生成代码导致文件覆盖的问题。
开发自己的插件
以生成t_test表为例,pojo类为Test.java,表结构如下:
CREATE TABLE `t_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`msg` varchar(32) DEFAULT NULL COMMENT '内容',
PRIMARY KEY (`id`)
);
- 开发思路
pojo类处理
增强代码使其生成的pojo类作为基类BaseTest.java,同时生成Test.java类继承至BaseTest.java,代码中pojo的引用全部为子类,自定义方法全部写在子类中,插件只更新基类不更新子类,当表结构变动时生成的pojo会覆盖BaseTest.java,而Test.java中的自定义方法全部保留。
mapper类处理
增强代码使其生成的Mapper接口作为基类BaseTestMapper.java,同时生成TestMapper.java类继承至BaseTestMapper.java,代码中Mapper的引用全部为子类,自定义方法全部写在子类中,插件只更新基类不更新子类,当表结构变动时生成的Mapper会覆盖BaseTestMapper.java,而TestMapper.java中的自定义方法全部保留。
xml文件处理
通过动态解析xml的方式获取文件中所有节点,通过节点Id属性判断节点是否为工具生成,是则替换,不是则表示节点为自定义节点保留,然后将新生成的节点和自定义节点合并后替换原来文件即可。
- 编写插件
定义插件
名称为:BaseClassPlugin
继承至 org.mybatis.generator.api.PluginAdapter
;为插件提供两个属性:useBaseEntity
默认值为true
表示采用基类的方式生成pojo类,false
表示不使用基类方式生成pojo类;useBaseMapper
默认值true
表示采用基类方式生成mapper接口,false
表示不使用基类形式生成mapper接口。
处理pojo类
重写org.mybatis.generator.api.PluginAdapter
的public void initialized(IntrospectedTable introspectedTable)
方法,获取原pojo类全路径重置类全路径为基类全路径,创建子类并继承至基类,代码如下:
if(useBaseEntity){
String baseRecordType = introspectedTable.getBaseRecordType();
String[] split = baseRecordType.split("\.");
StringBuilder sb = new StringBuilder();
for (int i = 0,len = split.length; i < len; i++) {
if(i == len - 1){
sb.append("Base");
}
sb.append(split[i]);
if(i != len - 1){
sb.append(".");
}
}
baseEntityName = sb.toString();
introspectedTable.setBaseRecordType(baseEntityName);
subEntityClass = new TopLevelClass(baseRecordType);
subEntityClass.setVisibility(JavaVisibility.PUBLIC);
subEntityClass.setSuperClass(baseEntityName);
}
重写org.mybatis.generator.api.PluginAdapter
的public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable)
方法,将子类添加到带生成Java文件集合中,代码如下:
List<GeneratedJavaFile> awser = new ArrayList<>(2);
String targetProject = introspectedTable.getContext().getJavaModelGeneratorConfiguration().getTargetProject();
File subEntityFile = new File(targetProject + "/" + subEntityClass.getType().getFullyQualifiedName().replace(".", "/") + ".java");
if(useBaseEntity && !subEntityFile.exists()){
GeneratedJavaFile javaEntityFile = new GeneratedJavaFile(subEntityClass,targetProject,new DefaultJavaFormatter());
awser.add(javaEntityFile);
}
return awser;
处理mapper类
重写org.mybatis.generator.api.PluginAdapter
的public void initialized(IntrospectedTable introspectedTable)
方法,获取原mapper类全路径重置类全路径为基类全路径,创建子类并继承至基类,代码如下:
if(useBaseMapper){
String myBatis3JavaMapperType = introspectedTable.getMyBatis3JavaMappe