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 + "'";
    }
}

 

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