再解析一个MyBatis Generator 插件

【原创文章,转载请注明原文章地址,谢谢!】

Paste_Image.png

在本文中,我们再来看一个MyBatis Generator插件:
org.mybatis.generator.plugins.MapperConfigPlugin。同样,首先来看一下这个插件的使用方式,
前面我们已经介绍了ToStringPlugin插件的作用,是为KeyClass,Record Class和BlobClass提供toString方法的。首先我们了解一下ToStringPlugin的用法。
在generatorConfig.xml中加上配置:

<plugin type="org.mybatis.generator.plugins.MapperConfigPlugin">
        <property name="targetPackage" value="com._520it.mybatis"/>
        <property name="targetProject" value="src/main/resources"/>
    </plugin>

首先,这个插件的运行必须要配置targetPackage和targetProject两个参数,所以我们可以通过这个插件的代码阅读,学习到参数的使用,并且学到在MBG中对文件路径和package的使用;

运行MBG,在resources下面生成了com._520it.mybatis包,并且正确添加了一个MapperConfig.xml(如果你还记得,这个文件的名字是可以通过fileName参数来设置的,默认的文件名就是MapperConfig.xml),生成的文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" >
<configuration >
  <!--
This file is generated by MyBatis Generator.
This file is the shell of a Mapper Config file - in many cases you     will need to add
  to this file before it is usable by MyBatis.
This file was generated on Thu Sep 03 22:14:48 CST 2015.
  -->
  <mappers >
    <mapper resource="com/_520it/mybatis/mapper/AccountMapper.xml" />
  </mappers>
</configuration>

可以看到,这仅仅只是一个非常空的mybatisConfig.xml文件,仅仅只添加了本次生成的Mapper.xml文件的mapper元素;
同样的,我们先明确我们看代码之前的目标:

  • 前面已经说到,要查看参数的传递和处理方法;
  • 学习处理文件路径相关方法;
  • 学习为MBG添加额外生成的文件;
  • 学习MBG中XML包装DOM的使用;
  • 学习获得本次生成的所有mapper.xml文件的方法;

同理,我们先思考一下这个插件的完成方式:

  • 在plugin生命周期的时候已经提到了,MBG会执行一个生成额外文件的方法contextGenerateAdditionalXmlFiles,可以通过那个方法来添加额外的文件;
  • 生成XML的DOM对象,很明显,这个DOM肯定也是MBG自己封装的;
  • 指定一个文件路径,并添加给MBG;
  • 肯定有一个方法能够获得本次生成的所有mapper.xml文件,保存这些文件,并且输出到生成的mappers元素中;

OK,那么我们进入代码:

public class MapperConfigPlugin extends PluginAdapter {

首先,该类仍然继承PluginAdapter;

private List<String> mapperFiles;
public MapperConfigPlugin() {
    mapperFiles = new ArrayList<String>();
}

接着,该类创建了一个String的集合,根据名字很明显,这里面肯定存放的是本次MBG生成的所有mapper.xml文件的路径;

public boolean validate(List<String> warnings) {
    boolean valid = true;
    //stringHasValue方法是通过静态引入工具类org.mybatis.generator.internal.util.StringUtility的;
    //该方法用于判断传入的参数中是否含有targetProject这个参数;
    //这里要注意两个点,第一,我们在扩展或者使用别人的框架的时候,比如stringHasValue这种方法,我们完全可以自己写一个hasLength方法,
    //但是,使用框架中已经存在的API来完成这些功能,是一个扩展框架的一个良好的实践,这可以保证框架在API级别的一致性;
    //第二,properties属性是Plugin在创建的时候,通过setProperties方法传入的,是一个Properties类型数据;
    if (!stringHasValue(properties
            .getProperty("targetProject"))) { //$NON-NLS-1$
        //如果没有传入必填的参数,就把警告信息添加到传入的warnings列表中,该列表的内容会在MBG运行过程中统一日志;
        //这里需要注意的是getString方法,该方法是通过静态引入org.mybatis.generator.internal.util.messages.Messages
        //这个Messages类是MBG对国际化消息的一个封装,在后面扩展时候会讲到MBG的代码结构;
        warnings.add(getString("ValidationError.18", //$NON-NLS-1$
                "MapperConfigPlugin", //$NON-NLS-1$
                "targetProject")); //$NON-NLS-1$
        valid = false;
    }
    //同理,判断是否传入了targetPackage参数
    if (!stringHasValue(properties
            .getProperty("targetPackage"))) { //$NON-NLS-1$
        warnings.add(getString("ValidationError.18", //$NON-NLS-1$
                "MapperConfigPlugin", //$NON-NLS-1$
                "targetPackage")); //$NON-NLS-1$
        valid = false;
    }
    return valid;
}

因为这个插件需要传入参数,并且有两个必填参数,所以在validate方法中做了验证,通过这个方法,其实我们基本就能明白validate方法怎么书写了;

接着看:

public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles() {

确实实现了contextGenerateAdditionalXmlFiles方法来为MBG添加额外需要生成的文件;进入这个方法:

   public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles() {
    //创建一个XML文档,注意这个Document不是JAVA DOM的,而是org.mybatis.generator.api.dom.xml.Document
    //在这里传入了两个静态常量,这两个常量就是mybatis配置文件需要用到的DTD,
    //在XmlConstants里面还有很多常量,比如MYBATIS3_MAPPER_SYSTEM_ID和MYBATIS3_MAPPER_PUBLIC_ID(看名字应该知道是什么内容吧~)
    Document document = new Document(
            XmlConstants.MYBATIS3_MAPPER_CONFIG_PUBLIC_ID,
            XmlConstants.MYBATIS3_MAPPER_CONFIG_SYSTEM_ID);
    
    //接着创建根目录,<configuration>,和JavaDOM基本一样,就不啰嗦了;
    XmlElement root = new XmlElement("configuration"); //$NON-NLS-1$
    document.setRootElement(root);

    //添加注释,这里做的有点不太规范,最好还是使用MBG提供的context.getCommentGenerator的addComment(XmlElement xmlElement)方法来统一生成注释
    //可能作者想做更多的个性化的注释吧;
    root.addElement(new TextElement("<!--")); //$NON-NLS-1$
    root.addElement(new TextElement("  This file is generated by MyBatis Generator.")); //$NON-NLS-1$
    root.addElement(new TextElement("  This file is the shell of a Mapper Config file - in many cases you will need to add")); //$NON-NLS-1$
    root.addElement(new TextElement("    to this file before it is usable by MyBatis.")); //$NON-NLS-1$
    StringBuilder sb = new StringBuilder();
    sb.append("  This file was generated on "); //$NON-NLS-1$
    sb.append(new Date());
    sb.append('.');
    root.addElement(new TextElement(sb.toString()));
    root.addElement(new TextElement("-->")); //$NON-NLS-1$

    //创建mappers节点;
    XmlElement mappers = new XmlElement("mappers"); //$NON-NLS-1$
    root.addElement(mappers);
    
    //准备根据搜集到的本次生成的mapper.xml文件,为mappers生成mapper子元素
    XmlElement mapper;
    //为每一个mapper.xml文件生成一个对应的mapper子元素;从这里就可以明确的看出,在mapperFiles集合中保存的确实是mapper.xml文件的路径;
    for (String mapperFile : mapperFiles) {
        mapper = new XmlElement("mapper"); //$NON-NLS-1$
        mapper.addAttribute(new Attribute("resource", mapperFile)); //$NON-NLS-1$
        mappers.addElement(mapper);
    }
    
    //信息量非常大的一句代码,通过这句代码可以看出:
    //1,MBG使用GeneratedXmlFile对象来包装一个要生成的XML文件的所有相关内容;
    //2,该对象的构造方法包含了所有需要的信息
    //3,第一个参数,是该XML文件的内容,即Document;
    //4,第二个参数,是该XML文件的文件名,可以很清楚的看到,先得到fileName参数,否则使用默认的MapperConfig.xml命名(所以,后缀名是要自己传给MBG的)
    //5,第三个参数和第四个参数,分别是生成XML文件的targetPackage和targetProject;所以,可以看到MBG把文件的具体生成过程完全包装,只需要我们提供package和project即可;
    //6,第四个参数代表是否合并,
    //7,最后一个参数是提供一个XML文件格式化工具,直接使用上下文的xmlFormatter即可(这个是可以在<context>元素中配置的哦~~)
    GeneratedXmlFile gxf = new GeneratedXmlFile(document, properties
            .getProperty("fileName", "MapperConfig.xml"), //$NON-NLS-1$ //$NON-NLS-2$
            properties.getProperty("targetPackage"), //$NON-NLS-1$
            properties.getProperty("targetProject"), //$NON-NLS-1$
            false, context.getXmlFormatter());
    
    //最后返回要生成的这个文件,交给MBG去生成;
    List<GeneratedXmlFile> answer = new ArrayList<GeneratedXmlFile>(1);
    answer.add(gxf);

    return answer;
}

详细的解释已经在注释中写明,其实可以通过这个代码看出来,MBG对Plugin的封装还是非常到位的,其API中各个类的职责还是非常明确的;这些代码对我们要生成我们自己的XML文件,是有非常大的指导作用的;

最后,那肯定就是搜集本次生成的mapper.xml文件的代码了:

 /**
 * sqlMapGenerated方法,是在本次context中,生成每一个(注意是每一个)mapper.xml文件之后都会回调的方法;
 * 第一个参数GeneratedXmlFile即本次生成的mapper.xml文件对应的XML文件封装对象;
 */
@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap,
        IntrospectedTable introspectedTable) {
    StringBuilder sb = new StringBuilder();
    //得到目标package;
    sb.append(sqlMap.getTargetPackage());
    //添加一个.然后把所有的.替换成/,就变成了mapper.xml文件的目录(原来并没有方法直接得到,还是要自己通过package去替换)
    sb.append('.');
    String temp = sb.toString();
    sb.setLength(0);
    sb.append(temp.replace('.', '/'));
    //接着拼上xml文件的文件名(还记得文件名是包含了后缀的吧),就创建好了这个mapper.xml文件的路径了
    sb.append(sqlMap.getFileName());
    //再添加到mapperFiles中
    mapperFiles.add(sb.toString());
    return true;
}

其实比较重要的也就这个方法的调用时机;通过这个方法的调用,我们应该对Plugin生命周期中类似modelBaseRecordClassGenerated的方法会有很深入的理解了。好了,准备工作完成,我们来做一个非常简单的自定义插件;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容