Mybatis Generator源碼:Generator自動生成框架

目錄

0. 使用方法

1. 配置文件定義

2. Generator框架解析過程分析

2.1 ConfigurationParser配置文件解析

2.2 MyBatisGenerator自動生成過程分析


我們都知道mybatis對於Dao接口中的方法具體實現方式有兩種:

  • 一種是基於註解的方式
  • 另一種是以及xml 動態SQL的方式

其中基於xml實現的方式,自己手寫麻煩複雜,很容易出問題,因此mybaits提供了一個generator的自動生成框架,對於最常用的基本方法(增、刪、改、查)可以自動生成dao、domain、mapper文件,應用簡單方便,大大提升了開發效率,下面主要介紹下generator框架的處理過程;

0. 使用方法

generator框架的使用方法,在框架內部的messages.properties文件中給出了說明,如下:

支持用java命令運行,是因爲包中ShellRunner存在一個main主方法,這裏需要指定一個配置文件,以及其它的幾個可選參數;

當然,實際項目使用一般都是通過mybatis-generator-maven-plugin插件完成的,可以做到一鍵編譯,自動生成;

1. 配置文件定義

典型的配置文件如下,其中主要包含了數據庫鏈接配置、JavaModelGenerator配置(Domain類)、SqlMapGenerator配置(Xml Sql文件)、JavaClientGenerator(Dao接口)配置以及需要生成的數據表table配置等;

2. Generator框架解析過程分析

Generator框架的入口類ShellRunner的main主方法中,完成了配置文件的解析,以及generator文件自動生成的功能,核心代碼如下,下面分別進行說明:

ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configurationFile);

DefaultShellCallback shellCallback = new DefaultShellCallback(
        arguments.containsKey(OVERWRITE));

MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, shellCallback, warnings);

ProgressCallback progressCallback = arguments.containsKey(VERBOSE) ? new VerboseProgressCallback()
        : null;

myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);

2.1 ConfigurationParser配置文件解析

解析過程的代碼如下:

ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configurationFile);

追蹤代碼,找到解析配置文件xml的代碼如下:

private Configuration parseConfiguration(InputSource inputSource)
        throws IOException, XMLParserException {
    parseErrors.clear();
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(true);

    try {
        DocumentBuilder builder = factory.newDocumentBuilder();
        builder.setEntityResolver(new ParserEntityResolver());

        ParserErrorHandler handler = new ParserErrorHandler(warnings,
                parseErrors);
        builder.setErrorHandler(handler);

        Document document = null;
        try {
            document = builder.parse(inputSource);
        } catch (SAXParseException e) {
            throw new XMLParserException(parseErrors);
        } catch (SAXException e) {
            if (e.getException() == null) {
                parseErrors.add(e.getMessage());
            } else {
                parseErrors.add(e.getException().getMessage());
            }
        }

        if (parseErrors.size() > 0) {
            throw new XMLParserException(parseErrors);
        }

        Configuration config;
        Element rootNode = document.getDocumentElement();
        DocumentType docType = document.getDoctype();
        if (rootNode.getNodeType() == Node.ELEMENT_NODE
                && docType.getPublicId().equals(
                        XmlConstants.IBATOR_CONFIG_PUBLIC_ID)) {
            config = parseIbatorConfiguration(rootNode);
        } else if (rootNode.getNodeType() == Node.ELEMENT_NODE
                && docType.getPublicId().equals(
                        XmlConstants.MYBATIS_GENERATOR_CONFIG_PUBLIC_ID)) {
            config = parseMyBatisGeneratorConfiguration(rootNode);
        } else {
            throw new XMLParserException(getString("RuntimeError.5")); //$NON-NLS-1$
        }

        if (parseErrors.size() > 0) {
            throw new XMLParserException(parseErrors);
        }

        return config;
    } catch (ParserConfigurationException e) {
        parseErrors.add(e.getMessage());
        throw new XMLParserException(parseErrors);
    }
}

可以看到,這裏用到了DOM Xml文件解析框架,對解析到的Document獲取到rootNode,然後進一步解析generator框架的配置類,代碼如下,解析類爲MyBatisGeneratorConfigurationParser

public Configuration parseConfiguration(Element rootNode)
        throws XMLParserException {

    Configuration configuration = new Configuration();

    NodeList nodeList = rootNode.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node childNode = nodeList.item(i);

        if (childNode.getNodeType() != Node.ELEMENT_NODE) {
            continue;
        }

        if ("properties".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseProperties(configuration, childNode);
        } else if ("classPathEntry".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseClassPathEntry(configuration, childNode);
        } else if ("context".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseContext(configuration, childNode);
        }
    }

    return configuration;
}

上面分別完成了properties、classPathEntry以及context的解析:

  • properties:主要完成外部屬性文件的解析;
  • classPathEntry:完成外部自定義類加載器的注入;
  • context:generator框架支持解析多個context,每個context可以有不同的數據庫配置等;

下面主要看一下context的解析過程:

private void parseContext(Configuration configuration, Node node) {

    Properties attributes = parseAttributes(node);
    String defaultModelType = attributes.getProperty("defaultModelType"); //$NON-NLS-1$
    String targetRuntime = attributes.getProperty("targetRuntime"); //$NON-NLS-1$
    String introspectedColumnImpl = attributes
            .getProperty("introspectedColumnImpl"); //$NON-NLS-1$
    String id = attributes.getProperty("id"); //$NON-NLS-1$

    ModelType mt = defaultModelType == null ? null : ModelType
            .getModelType(defaultModelType);

    Context context = new Context(mt);
    context.setId(id);
    if (stringHasValue(introspectedColumnImpl)) {
        context.setIntrospectedColumnImpl(introspectedColumnImpl);
    }
    if (stringHasValue(targetRuntime)) {
        context.setTargetRuntime(targetRuntime);
    }

    configuration.addContext(context);

    NodeList nodeList = node.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node childNode = nodeList.item(i);

        if (childNode.getNodeType() != Node.ELEMENT_NODE) {
            continue;
        }

        if ("property".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseProperty(context, childNode);
        } else if ("plugin".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parsePlugin(context, childNode);
        } else if ("commentGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseCommentGenerator(context, childNode);
        } else if ("jdbcConnection".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJdbcConnection(context, childNode);
        } else if ("javaModelGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJavaModelGenerator(context, childNode);
        } else if ("javaTypeResolver".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJavaTypeResolver(context, childNode);
        } else if ("sqlMapGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseSqlMapGenerator(context, childNode);
        } else if ("javaClientGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseJavaClientGenerator(context, childNode);
        } else if ("table".equals(childNode.getNodeName())) { //$NON-NLS-1$
            parseTable(context, childNode);
        }
    }
}

如上,完成了context中各個元素的具體解析,最終構造了Context對象,並保存到Configuration配置類中,Context域變量如下;至此,配置文件的解析過程完畢。

public class Context extends PropertyHolder {
    private String id;

    private JDBCConnectionConfiguration jdbcConnectionConfiguration;

    private SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration;

    private JavaTypeResolverConfiguration javaTypeResolverConfiguration;

    private JavaModelGeneratorConfiguration javaModelGeneratorConfiguration;

    private JavaClientGeneratorConfiguration javaClientGeneratorConfiguration;

    private ArrayList<TableConfiguration> tableConfigurations;

    private ModelType defaultModelType;

    private String beginningDelimiter = "\""; //$NON-NLS-1$

    private String endingDelimiter = "\""; //$NON-NLS-1$

    private CommentGeneratorConfiguration commentGeneratorConfiguration;

    private CommentGenerator commentGenerator;

    private PluginAggregator pluginAggregator;

    private List<PluginConfiguration> pluginConfigurations;

    private String targetRuntime;

    private String introspectedColumnImpl;

    private Boolean autoDelimitKeywords;
    
    private JavaFormatter javaFormatter;

    private XmlFormatter xmlFormatter;
}

2.2 MyBatisGenerator自動生成過程分析

這部分具體過程在實現源碼中通過註釋可以看得很清楚,這裏直接給出源碼實現:

/**
 * This is the main method for generating code. This method is long running,
 * but progress can be provided and the method can be cancelled through the
 * ProgressCallback interface.
 * 
 * @param callback
 *            an instance of the ProgressCallback interface, or
 *            <code>null</code> if you do not require progress information
 * @param contextIds
 *            a set of Strings containing context ids to run. Only the
 *            contexts with an id specified in this list will be run. If the
 *            list is null or empty, than all contexts are run.
 * @param fullyQualifiedTableNames
 *            a set of table names to generate. The elements of the set must
 *            be Strings that exactly match what's specified in the
 *            configuration. For example, if table name = "foo" and schema =
 *            "bar", then the fully qualified table name is "foo.bar". If
 *            the Set is null or empty, then all tables in the configuration
 *            will be used for code generation.
 * @throws InvalidConfigurationException
 * @throws SQLException
 * @throws IOException
 * @throws InterruptedException
 *             if the method is canceled through the ProgressCallback
 */
public void generate(ProgressCallback callback, Set<String> contextIds,
        Set<String> fullyQualifiedTableNames) throws SQLException,
        IOException, InterruptedException {

    if (callback == null) {
        callback = new NullProgressCallback();
    }

    generatedJavaFiles.clear();
    generatedXmlFiles.clear();

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

    // now save the files
    callback.saveStarted(generatedXmlFiles.size()
            + generatedJavaFiles.size());

    for (GeneratedXmlFile gxf : generatedXmlFiles) {
        projects.add(gxf.getTargetProject());

        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();
            }
        } catch (ShellException e) {
            warnings.add(e.getMessage());
            continue;
        }

        callback.checkCancel();
        callback.startTask(getString(
                "Progress.15", targetFile.getName())); //$NON-NLS-1$
        writeFile(targetFile, source, "UTF-8"); //$NON-NLS-1$
    }

    for (GeneratedJavaFile gjf : generatedJavaFiles) {
        projects.add(gjf.getTargetProject());

        File targetFile;
        String source;
        try {
            File directory = shellCallback.getDirectory(gjf
                    .getTargetProject(), gjf.getTargetPackage());
            targetFile = new File(directory, gjf.getFileName());
            if (targetFile.exists()) {
                if (shellCallback.isMergeSupported()) {
                    source = shellCallback.mergeJavaFile(gjf
                            .getFormattedContent(), targetFile
                            .getAbsolutePath(),
                            MergeConstants.OLD_ELEMENT_TAGS,
                            gjf.getFileEncoding());
                } else if (shellCallback.isOverwriteEnabled()) {
                    source = gjf.getFormattedContent();
                    warnings.add(getString("Warning.11", //$NON-NLS-1$
                            targetFile.getAbsolutePath()));
                } else {
                    source = gjf.getFormattedContent();
                    targetFile = getUniqueFileName(directory, gjf
                            .getFileName());
                    warnings.add(getString(
                            "Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
                }
            } else {
                source = gjf.getFormattedContent();
            }

            callback.checkCancel();
            callback.startTask(getString(
                    "Progress.15", targetFile.getName())); //$NON-NLS-1$
            writeFile(targetFile, source, gjf.getFileEncoding());
        } catch (ShellException e) {
            warnings.add(e.getMessage());
        }
    }

    for (String project : projects) {
        shellCallback.refreshProject(project);
    }

    callback.done();
}

如上,主要包含的處理過程有:

  1. 連接數據庫,解析context中配置的各個數據表(context.introspectTables方法中實現)
  2. 生成java文件和xml文件(context.generateFiles方法中實現)
  3. 分別保存生成的java文件和xml文件

另外,在messages.properties的過程過程提示信息中,也可以看出執行過程的一些細節,如下:

 

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