我们知道,通过使用Mybatis Generator,我们可以快速生成数据库已有表的增删改查方法,极大的提高开发效率。

    然而我们可能会遇到一个问题,我们继承AbstractXmlElementGenerator方法所实现的自定义mapper方法,每次运行都会重复添加,固然我们可以手动删除,但工具不就是让人们得以偷懒吗,而且作为一个强迫症患者兼程序员,这是不可容忍的。

    遇事第一步当然是使用搜索引擎了,也查到通过继承类 PluginAdapter ,重写 sqlMapGenerated 方法,改 GeneratedXmlFile 中成员变量 isMergeable 的值为 false 的解决方法。但经过尝试后发现,使用此种方式,自己新定义的方法就全没了,这可不行,还不如不改呢。

    没办法,看来得研究新的解决办法。通过 IDEA 的快捷键 Ctrl + B, 可以发现在类 org.mybatis.generator.api.MyBatisGenerator 的 writeGeneratedXmlFile 方法中调用了 GeneratedXmlFile 的 isMergeable 方法,代码如下:

    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());
                if (targetFile.exists()) {
                    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());
            }
        }

    其中对 gxf(GeneratedXmlFile) 进行是否合并判断, 如果返回值为 true,那么将调用 XmlFileMergerJaxp.getMergedSource(gxf,targetFile) 方法,而默认值即为 true。

    固需要了解 getMergedSource 方法做了什么,查看代码发现这里调用了重载方法,而在重载方法中我们可以找到如下代码:

            // 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);
                }
            }

    通过注解知道,Mybatis Generator 会删除它自身生成的 mapper 方法,这也是为什么只有我们自定义的 mapper 方法才会有重复的情况。

    再看看代码,发现这里通过 isGeneratedNode 方法判断是否需要删除 mapper 方法,代码如下:

        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;
        }

    在 rc==false 的情况下,有如下关键代码:

        for (String tag : MergeConstants.OLD_ELEMENT_TAGS) {
            if (commentData.contains(tag)) {
                rc = true;
                break;
            }
        }

    联系前文可以知道,只要 mapper 中包含 MergeConstants.OLD_ELEMENT_TAGS 的内容,合并时就会被删除,这不正是我们需要的吗,再看看 MergeConstants.OLD_ELEMENT_TAGS 又是什么

        public static final String NEW_ELEMENT_TAG = "@mbg.generated"; //$NON-NLS-1$
        public static final String[] OLD_ELEMENT_TAGS = {
                "@ibatorgenerated", //$NON-NLS-1$
                "@abatorgenerated", //$NON-NLS-1$
                "@mbggenerated", //$NON-NLS-1$
                "@mbg.generated" }; //$NON-NLS-1$

    这只是一些字符串,这还不容易吗,让我们试验一下,我在自定义的 AbstractXmlElementGenerator 实现类添加了以下代码

                newXmlElement.addElement(new TextElement("<!-- ELEMENT FOR GENERATOR MERGE - " 
                    + MergeConstants.NEW_ELEMENT_TAG + " -->"));

    其中 newXmlElement 为 XmlElement 的实例,因为 NEW_ELEMENT_TAG 也包含在 OLD_ELEMENT_TAGS中,所以这里直接使用 NEW_ELEMENT_TAG ,最后通过 parentElement.addElement(newXmlElement) 添加到父元素中。

    运行发现,果然成功了,旧的被删除了,新的进来,后写的 mapper 方法还在,至此大功告成。

    再次发现,遇到问题查源码是多么重要。

    By the way,这里用的 mybatis-generator-core 的版本是 1.3.5。