自定义Mybatis自动生成代码规则
前言
大家都清楚mybatis-generate-core 这个工程提供了获取表信息到生成model、dao、xml这三层代码的一个实现,但是这往往有一个痛点,比如需求来了,某个表需要增加字段,肯定需要重新运行mybatis自动生成的脚本,但是会去覆盖之前的代码,如model,dao的java代码,对于xml文件,目前有两种处理方式,一、覆盖,二、追加,本文用的版本是1.3.5版本,默认的是追加方式,本文的目的就是处理xml的一种合并方式,对于java代码的话,我个人认为无论是增加表字段还是其他情况,相对于xml文件都是比较好维护的,这里就不做讨论。
对于方式一的话,直接覆盖,肯定会导致之前自定义的sql,直接没了,还需要事先拷贝一份出来,最蛋疼的就是,可能还会在自动生成的代码文件中,增加了一些属性(如主键返回,flushCache属性),导致后来人员给忽略了,直到某个时刻才爆发出来。所以本文不采用这种方式,而是采用方式2,对于mybatis自定义的合并规则,看下文介绍。本文会对这个合并规则,进行重写,已达到我们的目标。如下
- 在启用自动生成代码后,原有的自定义sql,一律保留,包括,result|sql|select|delete|update|where|insert等标签,只要不是自动生成的
- 自动生成的标签中,手动添加的一些属性,如主键返回useGeneratedKeys="true" keyColumn="id",刷新一级缓存,flushCache="true"等属性标签也需要保留。
在重写该规则前,肯定是要摸清它的原有流程,下面分为这几个小节进行叙述
一、合并规则原理
二、重写规则
三、简述适用场景
本文采用的数据库是Mysql
一、合并规则原理
先来一段代码,莫慌,这段代码没什么特别,很常见的自动生成代码
package com.qm.mybatis.generate; import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback; import java.io.InputStream;
import java.util.ArrayList;
import java.util.List; public class GenerateTest { public static void main(String[] args) {
List<String> warnings = new ArrayList<String>();
try {
boolean overwrite = true;
// 读取配置文件
InputStream resourceAsStream = GenerateTest.class.getResourceAsStream("/mybatis-generate.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(resourceAsStream);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
} catch (Exception e) { e.printStackTrace();
} warnings.stream().forEach(warn -> {
System.out.println(warn);
});
System.out.println("生成成功!");
}
}
可见,最终的生成逻辑在MybatisGenerator#generate方法中,
// 最终生成代码的地方
public void generate(ProgressCallback callback, Set<String> contextIds,
Set<String> fullyQualifiedTableNames, boolean writeFiles) throws SQLException,
IOException, InterruptedException { if (callback == null) {
callback = new NullProgressCallback();
} generatedJavaFiles.clear();
generatedXmlFiles.clear();
ObjectFactory.reset();
RootClassInfo.reset(); // calculate the contexts to run
List<Context> contextsToRun;
if (contextIds == null || contextIds.size() == 0) {
contextsToRun = configuration.getContexts();
} else {
contextsToRun = new ArrayList<Context>();
for (Context context : configuration.getContexts()) {
if (contextIds.contains(context.getId())) {
contextsToRun.add(context);
}
}
} // setup custom classloader if required
if (configuration.getClassPathEntries().size() > 0) {
ClassLoader classLoader = getCustomClassloader(configuration.getClassPathEntries());
ObjectFactory.addExternalClassLoader(classLoader);
} // now run the introspections...
int totalSteps = 0;
for (Context context : contextsToRun) {
totalSteps += context.getIntrospectionSteps();
}
callback.introspectionStarted(totalSteps); for (Context context : contextsToRun) {
context.introspectTables(callback, warnings,
fullyQualifiedTableNames);
} // now run the generates
totalSteps = 0;
for (Context context : contextsToRun) {
totalSteps += context.getGenerationSteps();
}
callback.generationStarted(totalSteps); for (Context context : contextsToRun) {
context.generateFiles(callback, generatedJavaFiles,
generatedXmlFiles, warnings);
} // 前面各种文件都已经生成完毕,在这里进行保存到具体的文件中
if (writeFiles) {
callback.saveStarted(generatedXmlFiles.size()
+ generatedJavaFiles.size()); // 进行xml文件保存(更新)的地方,也是本文的目标
for (GeneratedXmlFile gxf : generatedXmlFiles) {
projects.add(gxf.getTargetProject());
writeGeneratedXmlFile(gxf, callback);
} // 保存java文件,如model,example,dao
for (GeneratedJavaFile gjf : generatedJavaFiles) {
projects.add(gjf.getTargetProject());
writeGeneratedJavaFile(gjf, callback);
} for (String project : projects) {
shellCallback.refreshProject(project);
}
} callback.done();
}
最终的落实地方就在writeGeneratedXmlFile方法内。
private void writeGeneratedXmlFile(GeneratedXmlFile gxf, ProgressCallback callback)
throws InterruptedException, IOException {
File targetFile;
String source;
try {
File directory = shellCallback.getDirectory(gxf
.getTargetProject(), gxf.getTargetPackage());
targetFile = new File(directory, gxf.getFileName());
// 如果为false,基本上就是第一次生成的时候
if (targetFile.exists()) { /**
* 从这里也可以看出,这个参数决定xml文件的处理方式
* 为true时,会执行getMergedSource,透个底,改造也是改造这个方法
false,会继续后面两种逻辑。实际生成的内容其实是一样的。这里不做讨论
*/
if (gxf.isMergeable()) {
source = XmlFileMergerJaxp.getMergedSource(gxf,
targetFile);
} else if (shellCallback.isOverwriteEnabled()) {
source = gxf.getFormattedContent();
warnings.add(getString("Warning.11", //$NON-NLS-1$
targetFile.getAbsolutePath()));
} else {
source = gxf.getFormattedContent();
targetFile = getUniqueFileName(directory, gxf
.getFileName());
warnings.add(getString(
"Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
}
} else {
source = gxf.getFormattedContent();
} callback.checkCancel();
callback.startTask(getString(
"Progress.15", targetFile.getName())); //$NON-NLS-1$
writeFile(targetFile, source, "UTF-8"); //$NON-NLS-1$
} catch (ShellException e) {
warnings.add(e.getMessage());
}
}
饶了这么多圈,实际上我们要处理的就是重写XmlFileMergerJaxp#getMergedSource方法,或许有的人会提出疑问了,这个类能让你提供扩展吗?让你去继承?然后去改变这个规则,额(⊙o⊙)…,还真没有,这个类实际上就是一个静态方法,那这搞个毛线啊,你即使重写出来了,那你怎么将他插入进去,别告诉你准备重新编译源码。。。。。莫慌,往下看。
说到这,大家可以去了解一下类加载器和其加载的过程,本文不做过多阐述,直接来结论,你要想覆盖一个jar包里的某个方法,你就直接在你项目中,定义这个类(包名和类名需要完全一致),然后运行的时候,自然会执行你定义的这个类,千万别去想着同样的方法去覆盖jdk自带的类,没用,因为第三方jar包和jdk自带的类的类加载器不是同一个。有兴趣的可以去网上搜索一下。说了这么多,我们就是要这样做。做之前,先了解下这个merge方法的代码。
public static String getMergedSource(InputSource newFile,
InputSource existingFile, String existingFileName) throws IOException, SAXException,
ParserConfigurationException, ShellException { DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new NullEntityResolver()); // 这是xml文件的解析结果,这里就暂且称为旧文件和新文件
Document existingDocument = builder.parse(existingFile);
Document newDocument = builder.parse(newFile); DocumentType newDocType = newDocument.getDoctype();
DocumentType existingDocType = existingDocument.getDoctype(); // 比较两个xml文件是不是同一类型
if (!newDocType.getName().equals(existingDocType.getName())) {
throw new ShellException(getString("Warning.12", //$NON-NLS-1$
existingFileName));
} // 获取根节点
Element existingRootElement = existingDocument.getDocumentElement();
Element newRootElement = newDocument.getDocumentElement(); NamedNodeMap attributes = existingRootElement.getAttributes();
int attributeCount = attributes.getLength();
for (int i = attributeCount - 1; i >= 0; i--) {
Node node = attributes.item(i);
existingRootElement.removeAttribute(node.getNodeName());
} // add attributes from the new root node to the old root node
attributes = newRootElement.getAttributes();
attributeCount = attributes.getLength();
for (int i = 0; i < attributeCount; i++) {
Node node = attributes.item(i);
existingRootElement.setAttribute(node.getNodeName(), node
.getNodeValue());
} // remove the old generated elements and any
// white space before the old nodes
List<Node> nodesToDelete = new ArrayList<Node>();
NodeList children = existingRootElement.getChildNodes();
int length = children.getLength();
for (int i = 0; i < length; i++) {
Node node = children.item(i);
if (isGeneratedNode(node)) {
nodesToDelete.add(node);
} else if (isWhiteSpace(node)
&& isGeneratedNode(children.item(i + 1))) {
nodesToDelete.add(node);
}
} for (Node node : nodesToDelete) {
existingRootElement.removeChild(node);
} // add the new generated elements
children = newRootElement.getChildNodes();
length = children.getLength();
Node firstChild = existingRootElement.getFirstChild();
for (int i = 0; i < length; i++) {
Node node = children.item(i);
// don't add the last node if it is only white space
if (i == length - 1 && isWhiteSpace(node)) {
break;
} Node newNode = existingDocument.importNode(node, true);
if (firstChild == null) {
existingRootElement.appendChild(newNode);
} else {
existingRootElement.insertBefore(newNode, firstChild);
}
} // pretty print the result
return prettyPrint(existingDocument);
}
启动的29~43行,的目的是替换mapper节点的namespace,方式重新生成后,namespace有改变

之后的47~62行,就是删除一些节点,其实按照他这意思就是为了删掉特定的节点,具体实现逻辑在isGeneratedNode方法内,由它决定删不删。
65~81行就是将新文件中的所有节点(非空白节点)全部合并至旧文件中。
private static boolean isGeneratedNode(Node node) {
boolean rc = false;
if (node != null && node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String id = element.getAttribute("id"); //$NON-NLS-1$
if (id != null) {
for (String prefix : MergeConstants.OLD_XML_ELEMENT_PREFIXES) {
if (id.startsWith(prefix)) {
rc = true;
break;
}
}
}
if (rc == false) {
// check for new node format - if the first non-whitespace node
// is an XML comment, and the comment includes
// one of the old element tags,
// then it is a generated node
NodeList children = node.getChildNodes();
int length = children.getLength();
for (int i = 0; i < length; i++) {
Node childNode = children.item(i);
if (isWhiteSpace(childNode)) {
continue;
} else if (childNode.getNodeType() == Node.COMMENT_NODE) {
Comment comment = (Comment) childNode;
String commentData = comment.getData();
for (String tag : MergeConstants.OLD_ELEMENT_TAGS) {
if (commentData.contains(tag)) {
rc = true;
break;
}
}
} else {
break;
}
}
}
}
return rc;
}
逻辑其实也很简单,4~14行的逻辑就是删除id属性值带有一些特定前缀的节点,如果没找到,这删除commentNode节点,看到这,结果就出来了,按照正常情况下,根本不会把之前的就节点给删除掉。还是完完全全的保留。至此,就是我们常说的追加。
二、重写规则
从上述内容中,熟悉了原有的代码合并规则,接下来就是自定义规则了,本文就不放代码了,那样感觉很啰嗦,就直接简述一下实现思路,具体代码会在文末贴出github链接,可自行查看。
一、遍历新文件的所有非空白节点,遍历同时获取到对应旧文件中的节点,这里不考虑旧文件中有删除自动生成的节点情况,若获取到了,则遍历属性,有无增加,若增加,则移植到新文件中对应节点上,同时对该旧文件中的节点进行标记,等遍历完删掉。
二、第一个步骤完成后,然后再将新文件中的所有节点全部移植到旧文件中,最后视情况,需不需要格式化一下xml文件。
具体规则,就是图中红框处的文件

具体效果,大家可自行尝试,这里不贴效果图了。毕竟眼见为实。
注:不保证该规则适用于所有格式的xml文件,这块需要实地尝试。
三、适用场景
本文这种方式,只适用于代码来生成文件的方式,对于适用maven插件,并不适用,如果需要,这里提供一种无奈方案,就是获取对应源码,替换掉该类,重新编译成jar包,放入到本地仓库里。
四、最后
如果还有其它比较好的方案。欢迎交流。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
转载请注明出处
自定义Mybatis自动生成代码规则的更多相关文章
- 【MyBatis】MyBatis自动生成代码查询之爬坑记
前言 项目使用SSM框架搭建Web后台服务,前台后使用restful api,后台使用MyBatisGenerator自动生成代码,在前台使用关键字进行查询时,遇到了一些很宝贵的坑,现记录如下.为展示 ...
- Mybatis 自动生成代码,数据库postgresql
最近做了一个项目,使用Mybatis自动生成代码,下面做一下总结,被以后参考: 一.提前准备: 1.工具类:mybatis-generator-core-1.3.2.jar 2.postgresql驱 ...
- mybatis自动生成代码插件mybatis-generator使用流程(亲测可用)
mybatis-generator是一款在使用mybatis框架时,自动生成model,dao和mapper的工具,很大程度上减少了业务开发人员的手动编码时间 坐着在idea上用maven构建spri ...
- mybatis 自动生成代码(mybatis generator)
pom.xml 文件配置 引入 mybatis generator <properties> <mysql.connector.version>5.1.44</mysql ...
- springboot mybatis 自动生成代码(maven+IntelliJ IDEA)
1.在pom文件中加入需要的依赖(mybatis-generator-core) 和 插件(mybatis-generator-maven-plugin) <dependency> < ...
- MyBatis自动生成代码示例
在项目中使用到mybatis时,都会选择自动生成实体类,Mapper,SqlMap这三个东东. 手头上在用的又不方便,找了下网上,其实有很多文章,但有些引用外部文件时不成功,也不方便,所以重新整理了下 ...
- mybatis自动生成代码工具(逆向工程)
MyBatis自动生成实体类(逆向工程) MyBatis属于一种半自动的ORM框架,它需要我们自己编写sql语句和映射文件,但是编写映射文件和sql语句很容易出错,所以mybatis官方提供了Gene ...
- mybatis自动生成代码
使用maven集成mybatis-generator插件生成Mybatis的实体类,DAO接口和Map映射文件 本例中,使用的是mysql数据库 前提:表已经建好 mybatis框架的jar包,数据 ...
- Mybatis 自动生成代码
准备条件: 将下面的文件放入同一目录下 操作步骤: 1/ 在 generatorConfig.xml 中配置相关的参数,与需要被自动生成的表 也可以 执行项目中的MybatisConfigAutoGe ...
随机推荐
- 3dTiles 数据规范详解[1] 介绍
版权:转载请带原地址.https://www.cnblogs.com/onsummer/p/12799366.html @秋意正寒 Web中的三维 html5和webgl技术使得浏览器三维变成了可能. ...
- java并发编程系列原理篇--JDK中的通信工具类Semaphore
前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...
- python_lesson1 数学与随机数 (math包,random包)
math包 math包主要处理数学相关的运算.math包定义了两个常数: math.e # 自然常数e math.pi # 圆周率pi 此外,math包还有各种运算函数 (下面函数的功能可以 ...
- 前台页面id为空--驼峰命名映射
错误: 前台页面id为空,或其他数据映射问题(方案2) 原因: java的bean类属性和数据库字段命名不一致,查询的时候就不能把数据封装进bean类里, 在数据库字段命名规范中,通常使用下划线“_ ...
- shellcode 免杀(一)
工具免杀 选择了几个简单或者近期还有更新的免杀工具进行学习 ShellcodeWrapper https://github.com/Arno0x/ShellcodeWrapper 该工具的原理是使用异 ...
- js Date format(日期格式化:yyyy-MM-dd HH:mm:ss.S)
今天在做日期显示的时候,那个显示格式困扰了很久,各种组件都尝试了,总是不如意,最后自己网上找了一个,然后稍微修改一下,感觉这个Util挺常用的,这里mark一下 Date.prototype.form ...
- 前端进阶笔记之核心基础知识---那些HTML标签你熟悉吗?
目录 1.交互实现 1.1 meta标签:自动刷新/跳转 1.2 title标签:消息提醒 2.性能优化 2.1 script标签:调整加载顺序提升渲染速度 2.2 link标签:通过预处理提升渲染速 ...
- cbitmap 获取RGB
CBitMap的用法 MFC提供了位图处理的基础类CBitmap,可以完成位图(bmp图像)的创建.图像数据的获取等功能.虽然功能比较少,但是在对位图进行一些简单的处理时,CBitmap类还是可以 ...
- jQuery动态生成<select>下拉框
前一阵在项目里需要动态生成下拉框,找了一下用jQuery实现比较方便,这里整理一下. 下文所述方法只是本人在项目中遇到问题的解决方法,场景较为简单,也希望能帮助有需要的朋友 1.动态生成下拉框的两种方 ...
- 初至cnblogs —— 博客搬迁
感觉写博客是一种总结.分享知识的有效方式,于是打算坚持通过博客这一载体来提升自己. 最初通过 Hexo + GitHub Page 来搭建个人博客,但是通过这种方式搭建的博客基本没有访问量.个人感觉没 ...