开源MyBatisGenerator组件源码分析

开源MyBatisGenerator组件源码分析

看源码前,先了解Generator能做什么?

MyBatisGenerator是用来生成mybatis的Mapper接口和xml文件的工具,提供多种启用方式,如Java类启动、shell启动、mavenPlugin启动等

具体点,可以连接DB,读取表信息,生成Model对象、JavaMapper、xmlMapper文件等。

整体代码工程分层

    org.mybatis.generator     ----api  内外部使用的主要接口,关键类MyBatisGenerator     ----codegen  代码生成的实际类,如XMLMapperGenerator/BaseRecordGenerator/JavaMapperGenerator     ------ibatis2  适配ibatis2     ------mybatis3  适配mybatis3     ----config  配置处理(1)xml配置读取/转化(2)如JavaClientGeneratorConfiguration配置生成文件目录、PluginConfiguration配置扩展插件     ----exception     ----internal  内部扩展和工具类,     ----logging     ----plugins  所有的扩展插件,如ToStringPlugin(生成ToString方法)     ----ant  适配ant编译工具 

写个demo看看怎么调用

/**     * 极简版【Java类启动】生成     */ public static void simpleGenModelAndMapper(String tableName, String modelName) {     Context context = new Context(ModelType.FLAT);     context.setId("starmoon");     context.setTargetRuntime("MyBatis3");  // MyBatis3Simple 是不带Example类的生成模式      JDBCConnectionConfiguration connection = new JDBCConnectionConfiguration();     connection.setConnectionURL(JDBC_URL);     connection.setUserId(JDBC_USERNAME);     connection.setPassword(JDBC_PASSWORD);     connection.setDriverClass(JDBC_DIVER_CLASS_NAME);     context.setJdbcConnectionConfiguration(connection);      JavaModelGeneratorConfiguration c1 = new JavaModelGeneratorConfiguration();     c1.setTargetProject(PROJECT_PATH + JAVA_PATH);     c1.setTargetPackage(MODEL_PACKAGE);     context.setJavaModelGeneratorConfiguration(c1);      SqlMapGeneratorConfiguration s1 = new SqlMapGeneratorConfiguration();     s1.setTargetProject(PROJECT_PATH + RESOURCES_PATH);     s1.setTargetPackage("mapper");     context.setSqlMapGeneratorConfiguration(s1);      JavaClientGeneratorConfiguration j1 = new JavaClientGeneratorConfiguration();     j1.setTargetProject(PROJECT_PATH + JAVA_PATH);     j1.setTargetPackage(MAPPER_PACKAGE);     j1.setConfigurationType("XMLMAPPER"); // XMLMAPPER     context.setJavaClientGeneratorConfiguration(j1);      PluginConfiguration toStringPluginConf = new PluginConfiguration();     toStringPluginConf.setConfigurationType("org.mybatis.generator.plugins.ToStringPlugin");     toStringPluginConf.addProperty("useToStringFromRoot", "true");     context.addPluginConfiguration(toStringPluginConf);      TableConfiguration tableConfiguration = new TableConfiguration(context);     tableConfiguration.setTableName(tableName);     context.addTableConfiguration(tableConfiguration);      try {         Configuration config = new Configuration();         config.addContext(context);         config.validate();         List<String> warnings = new ArrayList<String>();         MyBatisGenerator generator = new MyBatisGenerator(config, new DefaultShellCallback(true), warnings);         // 开始生成         generator.generate(null);         if (generator.getGeneratedJavaFiles().isEmpty() || generator.getGeneratedXmlFiles().isEmpty()) {             throw new RuntimeException("生成Model和Mapper失败:" + warnings);         }     } catch (Exception e) {         throw new RuntimeException("生成Model和Mapper失败", e);     } } 

从入口MyBatisGenerator.generate()看看做了什么?

MyBatisGenerator调用过程

// 精简了不重要的代码 public void generate(ProgressCallback callback, Set<String> contextIds,         Set<String> fullyQualifiedTableNames, boolean writeFiles) throws SQLException,         IOException, InterruptedException {      // 清理缓存中,上一次生成内容     generatedJavaFiles.clear();     generatedXmlFiles.clear();     ObjectFactory.reset();     RootClassInfo.reset();      // 计算需运行的配置组 (这里有些过度设计,一般情况单次运行一个Context就足够)     // 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);             }         }     }      // 加载指定的Classloader (暂时没看到使用场景)     // 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); // 预留的钩子 (暂时没看到使用场景)      // 【重要1】通过配置,加工表信息,形成内部表数据     for (Context context : contextsToRun) {         context.introspectTables(callback, warnings, fullyQualifiedTableNames);         // (1)连接db,获取链接          // (2)通过connection的MetaData,拿到所有表信息         // (3)针对要生成的表,加工内部表数据         // (4)释放链接     }      // now run the generates      totalSteps = 0;     for (Context context : contextsToRun) {         totalSteps += context.getGenerationSteps();     }     callback.generationStarted(totalSteps);      // 开始组长文件内容信息(此处还不会写到文件中)     for (Context context : contextsToRun) {         // 【重要2】Java文件内容组装、XML文件内容组装、各类plugin调用         context.generateFiles(callback, generatedJavaFiles, generatedXmlFiles, warnings);     }      // 创建文件、内容写入文件到磁盘中     // now save the files       if (writeFiles) {         callback.saveStarted(generatedXmlFiles.size() + generatedJavaFiles.size());          for (GeneratedXmlFile gxf : generatedXmlFiles) {             // 【重要3】按指定目录 写入xml             projects.add(gxf.getTargetProject());             writeGeneratedXmlFile(gxf, callback);         }          for (GeneratedJavaFile gjf : generatedJavaFiles) {             // 【重要4】按指定目录 写入Java类  Mapper文件、DO文件             projects.add(gjf.getTargetProject());             writeGeneratedJavaFile(gjf, callback);         }          for (String project : projects) {             shellCallback.refreshProject(project);         }     }      callback.done(); }  

调用的组件很分散,先记住几个关键组件

  1. 【API入口】org.mybatis.generator.api.MyBatisGenerator 生成代码的主入口API
  2. 【配置与上下文】Configuration 存配置的容器 / Context 存放运行期数据的容器
  3. 【文件内容生成】XMLMapperGenerator JavaMapperGenerator等
  4. 【扩展插件】org.mybatis.generator.api.Plugin 在代码生成过程中,通过不同生命周期接口,个性化处理生成内容,如init、contextGenerateAdditionalJavaFiles、contextGenerateAdditionalXmlFiles

再详细看看表信息,Table信息如何转化为Java类元信息

org.mybatis.generator.config.Context#generateFiles

// 生成文件内容 public void generateFiles(ProgressCallback callback,         List<GeneratedJavaFile> generatedJavaFiles, // 存放结构化的Java生成内容         List<GeneratedXmlFile> generatedXmlFiles,  // 存放结构化的Xml生成内容         List<String> warnings)         throws InterruptedException {      // 加载plugin,装载到Aggregator集合中,在内容生成的各个生命周期,plugin方法会被调用                 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));         }     }      // 表信息加工,生成Java对象和xml内容     if (introspectedTables != null) {         for (IntrospectedTable introspectedTable : introspectedTables) {             callback.checkCancel();                          // 根据给定的表信息,初始化(如类名、xml文件名),执行插件生命周期【initialized】             // 选定生成规则 如FlatModelRules(控制example、单独PrimaryKey类型是否生成)             introspectedTable.initialize();                // 预加载需调用的Generator  此处的组件更小,例如PrimaryKey生成、ExampleExample处理             introspectedTable.calculateGenerators(warnings, callback);              // 开始生成Java文件内容,将表信息转换成文件内容,后文详解             generatedJavaFiles.addAll(introspectedTable.getGeneratedJavaFiles());             // 开始生成Xml文件内容             generatedXmlFiles.addAll(introspectedTable.getGeneratedXmlFiles());              // 仅有回调plugin             generatedJavaFiles.addAll(pluginAggregator.contextGenerateAdditionalJavaFiles(introspectedTable));             generatedXmlFiles.addAll(pluginAggregator.contextGenerateAdditionalXmlFiles(introspectedTable));         }     }      // 仅有回调plugin     generatedJavaFiles.addAll(pluginAggregator.contextGenerateAdditionalJavaFiles());     generatedXmlFiles.addAll(pluginAggregator.contextGenerateAdditionalXmlFiles()); } 

introspectedTable.getGeneratedJavaFiles()解析 (IntrospectedTableMyBatis3Impl)

@Override public List<GeneratedJavaFile> getGeneratedJavaFiles() {     List<GeneratedJavaFile> answer = new ArrayList<GeneratedJavaFile>();      // javaModelGenerators/clientGenerators 在前面calculate过程中,已初始化      // 常用类 ExampleGenerator BaseRecordGenerator     for (AbstractJavaGenerator javaGenerator : javaModelGenerators) {         // 此处生成不同结果单元,很关键。不同类,处理不同数据         List<CompilationUnit> compilationUnits = javaGenerator.getCompilationUnits();         for (CompilationUnit compilationUnit : compilationUnits) {             GeneratedJavaFile gjf = new GeneratedJavaFile(compilationUnit,                     context.getJavaModelGeneratorConfiguration()                             .getTargetProject(),                             context.getProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING),                             context.getJavaFormatter());             // 将CompilationUnit装载到Java文件信息中                             answer.add(gjf);         }     }     // 常用类 JavaMapperGenerator     for (AbstractJavaGenerator javaGenerator : clientGenerators) {         List<CompilationUnit> compilationUnits = javaGenerator.getCompilationUnits();         for (CompilationUnit compilationUnit : compilationUnits) {             GeneratedJavaFile gjf = new GeneratedJavaFile(compilationUnit,                     context.getJavaClientGeneratorConfiguration()                             .getTargetProject(),                             context.getProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING),                             context.getJavaFormatter());             answer.add(gjf);         }     }      // 一般生成3个GeneratedJavaFile( DO/Example/Mapper )此时的answer内容已经是处理完成的Java信息     // 如果isStatic / isFinal /annotations     return answer; }  

AbstractJavaGenerator.getCompilationUnits做了哪些内容? 下面举例:

  1. org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator
    1. 组装各类待生成方法的属性,如CountByExample/InsertSelective
  2. BaseRecordGenerator 组装各类基本属性、构造器

从源码还能看出mybatis,在不同版本,对数据库操作层的不同命名,ibatis2中叫[DAO/DAOImpl],对应DAOGenerator,mybatis3中叫[Mapper],对应JavaMapperGenerator

到此为止,仍然没有生成具体的code内容文本,mybatis3中在后面写文件过程时才会组装,例如org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator。文本在后续getFormattedContent中才会组装。

但ibatis2在此时已经组装了code内容文本(例如org.mybatis.generator.codegen.ibatis2.model.ExampleGenerator)

很明显,mybatis3的设计分层更多,隔离性更好,但是复杂度也很高

再看code如何拼接出来

前面从generatedJavaFiles.addAll(introspectedTable.getGeneratedJavaFiles())看进来,会发现一路调用到几个小组件,

    org.mybatis.generator.api.dom.DefaultJavaFormatter#getFormattedContent         org.mybatis.generator.api.dom.java.TopLevelClass#getFormattedContent  // 拼接import/package信息             org.mybatis.generator.api.dom.java.InnerClass#getFormattedContent  // 拼接Javadoc/类修饰关键字/具体接口方法/属性                 // 完成组装后,附上'}',返回字符串 

文件落地到磁盘,没有特殊操作,标准的文件留操作

    private void writeFile(File file, String content, String fileEncoding) throws IOException {         FileOutputStream fos = new FileOutputStream(file, false);         OutputStreamWriter osw;         if (fileEncoding == null) {             osw = new OutputStreamWriter(fos);         } else {             osw = new OutputStreamWriter(fos, fileEncoding);         }                  BufferedWriter bw = new BufferedWriter(osw);         bw.write(content);         bw.close();     } 

plugin体系

框架中,通过PluginAdapter和Plugin接口定义插件的各个生命周期,并在code生成过程中进行调用,生命周期划分节点非常多。下面举例说明。

  1. ToStringPlugin
    1. modelBaseRecordClassGenerated() 在DO生成时被调用,用于组装【toString方法】
  2. SerializablePlugin
    1. modelPrimaryKeyClassGenerated() 在PrimaryKey生成时被调用,用于组装【类实现序列化接口】

可以通过各类plugin,在各个节点做些个性化处理,如统一增加copyright。

常用配置项

        Context context = new Context(ModelType.HIERARCHICAL);           // HIERARCHICAL FLAT CONDITIONAL (一般使用CONDITIONAL即可,也是默认配置)         1. HIERARCHICAL 层次模式,(1)生成独立的主键类 (2)针对text大字段,生成xxxWithBLOBs包装类         2. FLAT  扁平模式,不生成独立的主键类         3. CONDITIONAL  条件模式,(1)可选生成独立的主键类(单一字段主键不生成独立类,非单一字段则生成(联合主键)) (2)有2个以上text大字段,生成xxxWithBLOBs包装类           context.setTargetRuntime("MyBatis3");  // MyBatis3Simple 是不带Example类的生成模式  MyBatis3 带有example           // 隐藏默认注释         CommentGeneratorConfiguration commentGeneratorConfiguration = new CommentGeneratorConfiguration();         context.setCommentGeneratorConfiguration(commentGeneratorConfiguration);         commentGeneratorConfiguration.addProperty("suppressDate", "true");         commentGeneratorConfiguration.addProperty("suppressAllComments", "true");         // Java类 生成toString方法         PluginConfiguration toStringPluginConf = new PluginConfiguration();         toStringPluginConf.setConfigurationType("org.mybatis.generator.plugins.ToStringPlugin");         toStringPluginConf.addProperty("useToStringFromRoot", "true");         context.addPluginConfiguration(toStringPluginConf);         // Java类 实现serializable接口         PluginConfiguration serializablePluginConf = new PluginConfiguration();         serializablePluginConf.setConfigurationType("org.mybatis.generator.plugins.SerializablePlugin");         context.addPluginConfiguration(serializablePluginConf); 

如何扩展

1. 想增加一个查询方法DeleteListByExampleAndLimit,要怎么做

源码中,主要是4处要扩展,预处理计算、Java类生成、xml生成、plugin扩展生成

  1. 预处理计算
    1. org.mybatis.generator.api.IntrospectedTable#calculateXmlAttributes
  2. Java类生成
    1. org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator#getCompilationUnits方法增加
      1. 实现通用接口DeleteListByExampleAndLimitMethodGenerator.java
  3. xml生成
    1. org.mybatis.generator.codegen.mybatis3.xmlmapper.XMLMapperGenerator#getSqlMapElement
      1. DeleteListByExampleAndLimitElementGenerator.java
  4. plugin扩展生成
    1. Plugin.java PluginAggregator增加接口
      1. org.mybatis.generator.internal.PluginAggregator#sqlMapDeleteListByExampleAndLimitElementGenerated

总结

至此,一个标准的Java文件完成组装、文件生成。

回头看,整个思路其实很简单,读取db信息、加工成内部标准格式数据、通过数据生成DO/Mapper。但复杂的是,去适配不同的配置模式,动态的组装、拼接。

Generato只能做code生成吗? 再想想还可以做什么?拿到db信息后,进一步生成service接口、controller接口)

表信息一定要连DB吗? 从DDL文件中读? 从ERM读? 进而扩展到,在源头上管理表结构和JavaDO的映射)

其他可以借鉴的内容

可以学习其中的Configuration组织模式,适配上PropertyHolder,属性做到了高内聚。

(思考,CommentGeneratorConfiguration用的suppressDate属性,为何不直接定义在类中,而是放在PropertyHolder? 可能是使用方的接口已经定义org.mybatis.generator.api.CommentGenerator#addConfigurationProperties,只能从Properties中取属性。)

商匡云商
Logo
对比商品
  • 合计 (0)
对比
0
购物车