Mybatis Generator源码:批量插入mapper接口自动实现

目录

0. 现实背景

1. 方案调研

1.1 自动添加mapper接口方法

1.2 自动生成xml文件

2. 方案实现

2.1 批量插入插件BatchInsertPlugin实现

2.2 AdditionalXMLMapperGenerator Document生成器实现

2.3 BatchInsertElementGenerator 动态SQL生成


0. 现实背景

在项目开发中,业务数据落库时,为了减少和数据库之间的通信,频繁占用数据库链接资源,我们一般都会需要一个批量插入的mapper接口方法;但遗憾的是,mybatis generator框架中没有提供这样的方法,目前一般的解决方案是,我们手动在mapper接口中添加批量插入的方法,然后再手工添加一个批量插入的动态SQL与之关联,但这样的问题在于:

  • 每个table手写批量插入的动态SQL要花费一定的时间,手写完也不能保证是没有问题的,一次通过;

  • 每次table 新增字段时,其他的mapper接口,都可以重新编译生成,然后覆盖,但批量插入的动态SQL还需要我们手动去添加维护,有时也很容易遗漏,出现问题;

为了解决上述问题,批量插入mapper接口方法以及动态SQL xml文件 也需要做到一次编译,自动生成;

1. 方案调研

1.1 自动添加mapper接口方法

在上篇文章中Mybatis Generator源码:Generator自动生成框架,我们知道在JavaClientGenerator配置中有一个rootInterface的属性配置,该配置可以使生成的Domain对象自动实现某个接口方法,这里的实现源码如下(JavaMapperGenerator方法getCompilationUnits中):

@Override
    public List<CompilationUnit> getCompilationUnits() {
        progressCallback.startTask(getString("Progress.17", //$NON-NLS-1$
                introspectedTable.getFullyQualifiedTable().toString()));
        CommentGenerator commentGenerator = context.getCommentGenerator();

        FullyQualifiedJavaType type = new FullyQualifiedJavaType(
                introspectedTable.getMyBatis3JavaMapperType());
        Interface interfaze = new Interface(type);
        interfaze.setVisibility(JavaVisibility.PUBLIC);
        commentGenerator.addJavaFileComment(interfaze);

        String rootInterface = introspectedTable
            .getTableConfigurationProperty(PropertyRegistry.ANY_ROOT_INTERFACE);
        if (!stringHasValue(rootInterface)) {
            rootInterface = context.getJavaClientGeneratorConfiguration()
                .getProperty(PropertyRegistry.ANY_ROOT_INTERFACE);
        }

        if (stringHasValue(rootInterface)) {
            FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(
                    rootInterface);
            interfaze.addSuperInterface(fqjt);
            interfaze.addImportedType(fqjt);
        }
        
        addCountByExampleMethod(interfaze);
        addDeleteByExampleMethod(interfaze);
        addDeleteByPrimaryKeyMethod(interfaze);
        addInsertMethod(interfaze);
        addInsertSelectiveMethod(interfaze);
        addSelectByExampleWithBLOBsMethod(interfaze);
        addSelectByExampleWithoutBLOBsMethod(interfaze);
        addSelectByPrimaryKeyMethod(interfaze);
        addUpdateByExampleSelectiveMethod(interfaze);
        addUpdateByExampleWithBLOBsMethod(interfaze);
        addUpdateByExampleWithoutBLOBsMethod(interfaze);
        addUpdateByPrimaryKeySelectiveMethod(interfaze);
        addUpdateByPrimaryKeyWithBLOBsMethod(interfaze);
        addUpdateByPrimaryKeyWithoutBLOBsMethod(interfaze);

        List<CompilationUnit> answer = new ArrayList<CompilationUnit>();
        if (context.getPlugins().clientGenerated(interfaze, null,
                introspectedTable)) {
            answer.add(interfaze);
        }
        
        List<CompilationUnit> extraCompilationUnits = getExtraCompilationUnits();
        if (extraCompilationUnits != null) {
            answer.addAll(extraCompilationUnits);
        }

        return answer;
    }

因此,可以把batchInsert方法加入到某个接口中,然后rootInterface指定该接口即可;

1.2 自动生成xml文件

在上篇文章Mybatis Generator源码:Generator自动生成框架中生成xml文件阶段调用了context的generateFiles方法,下面看一下该方法的内部实现:

public void generateFiles(ProgressCallback callback,
            List<GeneratedJavaFile> generatedJavaFiles,
            List<GeneratedXmlFile> generatedXmlFiles, List<String> warnings)
            throws InterruptedException {

        pluginAggregator = new PluginAggregator();
        for (PluginConfiguration pluginConfiguration : pluginConfigurations) {
            Plugin plugin = ObjectFactory.createPlugin(this,
                    pluginConfiguration);
            if (plugin.validate(warnings)) {
                pluginAggregator.addPlugin(plugin);
            } else {
                warnings.add(getString("Warning.24", //$NON-NLS-1$
                        pluginConfiguration.getConfigurationType(), id));
            }
        }

        if (introspectedTables != null) {
            for (IntrospectedTable introspectedTable : introspectedTables) {
                callback.checkCancel();

                introspectedTable.initialize();
                introspectedTable.calculateGenerators(warnings, callback);
                generatedJavaFiles.addAll(introspectedTable
                        .getGeneratedJavaFiles());
                generatedXmlFiles.addAll(introspectedTable
                        .getGeneratedXmlFiles());

                generatedJavaFiles.addAll(pluginAggregator
                        .contextGenerateAdditionalJavaFiles(introspectedTable));
                generatedXmlFiles.addAll(pluginAggregator
                        .contextGenerateAdditionalXmlFiles(introspectedTable));
            }
        }

        generatedJavaFiles.addAll(pluginAggregator
                .contextGenerateAdditionalJavaFiles());
        generatedXmlFiles.addAll(pluginAggregator
                .contextGenerateAdditionalXmlFiles());
    }

这里同时完成了java文件和xml文件的生成,其中pluginAggregator.contextGenerateAdditionalXmlFiles(introspectedTable)提供了应用generator插件生成其它xml文件的扩展接口,这样,我们就可以自定义一个generator插件来完成 batchInsert xml动态Sql的自动生成;

2. 方案实现

自动添加mapper接口batchInsert方法比较简单,这里不再进行说明,下面主要介绍xml动态SQL自动生成的设计实现:

2.1 批量插入插件BatchInsertPlugin实现

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

    @Override
    public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles(IntrospectedTable introspectedTable) {

        AbstractXmlGenerator xmlGenerator = new AdditionalXMLMapperGenerator();

        xmlGenerator.setContext(introspectedTable.getContext());
        xmlGenerator.setIntrospectedTable(introspectedTable);

        Document document = xmlGenerator.getDocument();

        List<GeneratedXmlFile> answer = Lists.newArrayListWithCapacity(1);

        if (document != null) {
            GeneratedXmlFile gxf = new GeneratedXmlFile(document, introspectedTable.getMyBatis3XmlMapperFileName(),
                    introspectedTable.getMyBatis3XmlMapperPackage(),
                    context.getSqlMapGeneratorConfiguration().getTargetProject(), true, context.getXmlFormatter());

            answer.add(gxf);
        }

        return answer;
    }

    @Override
    public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
        return true;
    }
}

这里通过自定义的AdditionalXMLMapperGenerator完成Document对象的构建,进而完成了批量插入xml动态SQL文件GeneratedXmlFile的自动生成,下面看下AdditionalXMLMapperGenerator Document生成器的实现:

2.2 AdditionalXMLMapperGenerator Document生成器实现

AdditionalXMLMapperGenerator具体实现代码如下,借助BatchInsertElementGenerator完成了Document文档对象的构建:

public class AdditionalXMLMapperGenerator extends AbstractXmlGenerator {
    public AdditionalXMLMapperGenerator() {
        super();
    }

    @Override
    public Document getDocument() {
        Document document =
                new Document(XmlConstants.MYBATIS3_MAPPER_PUBLIC_ID, XmlConstants.MYBATIS3_MAPPER_SYSTEM_ID);
        document.setRootElement(getSqlMapElement());

        if (!context.getPlugins().sqlMapDocumentGenerated(document, introspectedTable)) {
            document = null;
        }

        return document;
    }

    private XmlElement getSqlMapElement() {
        XmlElement root = new XmlElement("mapper"); //$NON-NLS-1$
        String namespace = introspectedTable.getMyBatis3SqlMapNamespace();
        root.addAttribute(new Attribute("namespace", //$NON-NLS-1$
                namespace));

        context.getCommentGenerator().addRootComment(root);

        root.addElement(new TextElement("<!-- This sql is auto-generated by MyBatis Generator. -->")); //$NON-NLS-1$

        addBatchInsertElement(root);

        return root;
    }

    private void addBatchInsertElement(XmlElement parentElement) {
        if (introspectedTable.getRules().generateInsert()) {
            AbstractXmlElementGenerator elementGenerator = new BatchInsertElementGenerator();
            initializeAndExecuteGenerator(elementGenerator, parentElement);
        }
    }

    private void initializeAndExecuteGenerator(AbstractXmlElementGenerator elementGenerator, XmlElement parentElement) {
        elementGenerator.setContext(context);
        elementGenerator.setIntrospectedTable(introspectedTable);
        elementGenerator.addElements(parentElement);
    }
}

2.3 BatchInsertElementGenerator 动态SQL生成

如下,BatchInsertElementGenerator完成了批量插入动态SQL的构造,这里就是我们比较熟悉的动态sql语句了;

public class BatchInsertElementGenerator extends AbstractXmlElementGenerator {
    private static final String BATCH_INSERT_STATEMENT_ID = "batchInsert";
    private static final int NEW_LINE_CHARACTER_LIMIT = 80;

    BatchInsertElementGenerator() {
        super();
    }

    @Override
    public void addElements(XmlElement parentElement) {
        XmlElement answer = new XmlElement("insert");

        answer.addAttribute(new Attribute("id", BATCH_INSERT_STATEMENT_ID));

        answer.addAttribute(
                new Attribute("parameterType", FullyQualifiedJavaType.getNewListInstance().getFullyQualifiedName()));

        context.getCommentGenerator().addComment(answer);

        GeneratedKey gk = introspectedTable.getGeneratedKey();
        if (gk != null) {
            IntrospectedColumn introspectedColumn = introspectedTable.getColumn(gk.getColumn());
            if (introspectedColumn != null) {
                if (gk.isJdbcStandard()) {
                    answer.addAttribute(new Attribute("useGeneratedKeys", "true"));
                    answer.addAttribute(new Attribute("keyProperty", introspectedColumn.getJavaProperty()));
                } else {
                    answer.addElement(getSelectKey(introspectedColumn, gk));
                }
            }
        }

        StringBuilder insertClause = new StringBuilder();
        StringBuilder valuesClause = new StringBuilder();

        insertClause.append("insert into ");
        insertClause.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
        insertClause.append(" (");

        List<String> valuesClauses = Lists.newArrayList();
        valuesClauses.add("values ");
        valuesClauses.add("<foreach collection=\"list\" separator=\",\" index=\"index\" item=\"item\">");
        valuesClauses.add("  (");

        Iterator<IntrospectedColumn> iter = introspectedTable.getAllColumns().iterator();
        while (iter.hasNext()) {
            IntrospectedColumn introspectedColumn = iter.next();
            if (introspectedColumn.isIdentity()) {
                // cannot set values on identity fields
                continue;
            }

            insertClause.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));

            if (introspectedColumn.getDefaultValue() == null) {
                getValueClauseWhenNoDefaultValue(valuesClause, valuesClauses, iter, introspectedColumn, 1);
            } else {
                getChooseString(valuesClause, valuesClauses, introspectedColumn, iter);
            }

            appendCommaWhenHasNext(insertClause, iter);

            if (insertClause.length() > NEW_LINE_CHARACTER_LIMIT) {
                answer.addElement(new TextElement(insertClause.toString()));
                insertClause.setLength(0);
                OutputUtilities.xmlIndent(insertClause, 1);
            }
        }

        insertClause.append(')');
        answer.addElement(new TextElement(insertClause.toString()));

        valuesClause.append(')');
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);
        valuesClause.append("</foreach>");
        valuesClauses.add(valuesClause.toString());

        for (String clause : valuesClauses) {
            answer.addElement(new TextElement(clause));
        }

        if (context.getPlugins().sqlMapInsertElementGenerated(answer, introspectedTable)) {
            parentElement.addElement(answer);
        }
    }

    private void getValueClauseWhenNoDefaultValue(StringBuilder valuesClause, List<String> valuesClauses,
            Iterator<IntrospectedColumn> iter, IntrospectedColumn introspectedColumn, int indentLevel) {
        OutputUtilities.xmlIndent(valuesClause, indentLevel);
        valuesClause.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn));
        insertItemAfterLeftBrace(valuesClause);
        appendCommaWhenHasNext(valuesClause, iter);
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);
    }

    private void getChooseString(StringBuilder valuesClause, List<String> valuesClauses,
            IntrospectedColumn introspectedColumn, Iterator iter) {
        OutputUtilities.xmlIndent(valuesClause, 1);
        valuesClause.append("<choose>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("<when test=\"item.");
        valuesClause.append(introspectedColumn.getJavaProperty(null));
        valuesClause.append(" != null\" >");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        getValueClauseWhenNoDefaultValue(valuesClause, valuesClauses, iter, introspectedColumn, 3);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("</when>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("<otherwise>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 3);
        valuesClause.append(wrapQuotaPair(introspectedColumn.getDefaultValue()));
        appendCommaWhenHasNext(valuesClause, iter);
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 2);
        valuesClause.append("</otherwise>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);

        OutputUtilities.xmlIndent(valuesClause, 1);
        valuesClause.append("</choose>");
        valuesClauses.add(valuesClause.toString());
        valuesClause.setLength(0);
    }

    private void appendCommaWhenHasNext(StringBuilder insertClause, Iterator<IntrospectedColumn> iter) {
        if (iter.hasNext()) {
            insertClause.append(", ");
        }
    }

    private void insertItemAfterLeftBrace(StringBuilder valuesClause) {
        int index = valuesClause.indexOf("{");
        if (index != -1) {
            valuesClause.insert(index + 1, "item.");
        }
    }

    private String wrapQuotaPair(String defaultValue) {
        return "'" + defaultValue + "'";
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章