Mybatis Generator源码:Generator自动生成框架

6 篇文章 0 订阅
2 篇文章 0 订阅

目录

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的过程过程提示信息中,也可以看出执行过程的一些细节,如下:

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【目的】如果您希望弄懂框架的原理,但是苦于不知道如何读框架码?希望该课程能够通过带领大家阅读码的方式,了解码是如何阅读的。该课程选择了一个比较小,比较简单的框架入门,相信大家应该也用过该工具——mybatis generator【内容】该课程内容如下,列出了具体时间,便于复习:第一课:mybatis-generator码分析-解析配置这节课分为如下几段:【00:00-05:00】:介绍码阅读的方法【05:00-08:00】:简介mybatis-generator的使用 【08:00-27:30】:mybatis-generator包一览 【27:30-结束】:解析配置码解读 【总结】所在的位置:1.第一次总结【34:15】。2.第二次总结【52:40】 涉及的【设计思路】和【设计模式】:1.模板模式【15:30】。2.xml解析的对象设计+组合模式:【37:00】。3.策略模式:【45:40】 第二课:mybatis-generator码分析-生成文件 这节课分为如下几段:        1. 【00:00-10:20】:上节课内容回顾       2. 【10:20-42:20】:如何从数据库中获取元数据信息       3. 【42:20-结束】:生成文件 【总结】所在的位置:1.第一次总结【37:45】。2.第二次总结【56:25】 涉及的【设计思路】和【设计模式】:1、简单工厂方法模式【35:20】。2、聚合的设计思想【44:00】。 第三课:mybatis-generator码分析-总结 这节课分为如下几段:        1. 【00:00-01:00】: 设计思路总结         2. 【01:00-02:50】:配置解析总结         3. 【02:50-03:55】: 从数据库获取信息总结         4. 【03:55-结束】: 文件的生成总结         
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值