• 开源MyBatisGenerator组件源码分析


    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编译工具
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    写个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 warnings = new ArrayList();
        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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

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

    MyBatisGenerator调用过程

    // 精简了不重要的代码
    public void generate(ProgressCallback callback, Set contextIds,
    Set fullyQualifiedTableNames, boolean writeFiles) throws SQLException,
    IOException, InterruptedException {

    // 清理缓存中,上一次生成内容
    generatedJavaFiles.clear();
    generatedXmlFiles.clear();
    ObjectFactory.reset();
    RootClassInfo.reset();
    
    // 计算需运行的配置组 (这里有些过度设计,一般情况单次运行一个Context就足够)
    // calculate the contexts to run
    List contextsToRun;
    if (contextIds == null || contextIds.size() == 0) {
        contextsToRun = configuration.getContexts();
    } else {
        contextsToRun = new ArrayList();
        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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    }

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

    【API入口】org.mybatis.generator.api.MyBatisGenerator 生成代码的主入口API
    【配置与上下文】Configuration 存配置的容器 / Context 存放运行期数据的容器
    【文件内容生成】XMLMapperGenerator JavaMapperGenerator等
    【扩展插件】org.mybatis.generator.api.Plugin 在代码生成过程中,通过不同生命周期接口,个性化处理生成内容,如init、contextGenerateAdditionalJavaFiles、contextGenerateAdditionalXmlFiles
    再详细看看表信息,Table信息如何转化为Java类元信息
    org.mybatis.generator.config.Context#generateFiles

    // 生成文件内容
    public void generateFiles(ProgressCallback callback,
    List generatedJavaFiles, // 存放结构化的Java生成内容
    List generatedXmlFiles, // 存放结构化的Xml生成内容
    List 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());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    }
    introspectedTable.getGeneratedJavaFiles()解析 (IntrospectedTableMyBatis3Impl)

    @Override
    public List getGeneratedJavaFiles() {
    List answer = new ArrayList();

    // javaModelGenerators/clientGenerators 在前面calculate过程中,已初始化
    
    // 常用类 ExampleGenerator BaseRecordGenerator
    for (AbstractJavaGenerator javaGenerator : javaModelGenerators) {
        // 此处生成不同结果单元,很关键。不同类,处理不同数据
        List 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 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    }

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

    org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator
    组装各类待生成方法的属性,如CountByExample/InsertSelective
    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/类修饰关键字/具体接口方法/属性
                // 完成组装后,附上'}',返回字符串
    
    • 1
    • 2
    • 3
    • 4

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

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    ToStringPlugin
    modelBaseRecordClassGenerated() 在DO生成时被调用,用于组装【toString方法】
    SerializablePlugin
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如何扩展

    1. 想增加一个查询方法DeleteListByExampleAndLimit,要怎么做
      源码中,主要是4处要扩展,预处理计算、Java类生成、xml生成、plugin扩展生成

    预处理计算
    org.mybatis.generator.api.IntrospectedTable#calculateXmlAttributes
    Java类生成
    org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator#getCompilationUnits方法增加
    实现通用接口DeleteListByExampleAndLimitMethodGenerator.java
    xml生成
    org.mybatis.generator.codegen.mybatis3.xmlmapper.XMLMapperGenerator#getSqlMapElement
    DeleteListByExampleAndLimitElementGenerator.java
    plugin扩展生成
    Plugin.java PluginAggregator增加接口
    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中取属性。)

  • 相关阅读:
    springboot系列(二十四):如何实现Excel文件导出?这你得会 | 超级详细,建议收藏
    C语言 memset
    stable diffusion安装踩坑之clip安装、git报错
    第五章第三节:数和二叉树的应用(二叉排序树和哈夫曼树及哈夫曼编码)
    【小程序项目开发 -- 京东商城】uni-app 商品分类页面(下)
    Linux[高级管理]——使用源码包编译安装Apache网站
    Macs处理ChIP_Seq数据
    安塔利斯升级php8
    Vue复习笔记 (三)mixin
    SpringBoot+Vue项目校园台球厅人员与设备管理系统
  • 原文地址:https://blog.csdn.net/weixin_43214644/article/details/126069092