开源MyBatisGenerator组件源码分析 - starmoon1900
source link: https://www.cnblogs.com/starmoon1994/p/16532265.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
开源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();}
调用的组件很分散,先记住几个关键组件
- 【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<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)
@Overridepublic 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做了哪些内容? 下面举例:
- 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/类修饰关键字/具体接口方法/属性 // 完成组装后,附上'}',返回字符串
文件落地到磁盘,没有特殊操作,标准的文件留操作
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生成过程中进行调用,生命周期划分节点非常多。下面举例说明。
- 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. 想增加一个查询方法DeleteListByExampleAndLimit,要怎么做
源码中,主要是4处要扩展,预处理计算、Java类生成、xml生成、plugin扩展生成
- 预处理计算
- org.mybatis.generator.api.IntrospectedTable#calculateXmlAttributes
- Java类生成
- org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator#getCompilationUnits方法增加
- 实现通用接口DeleteListByExampleAndLimitMethodGenerator.java
- org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator#getCompilationUnits方法增加
- xml生成
- org.mybatis.generator.codegen.mybatis3.xmlmapper.XMLMapperGenerator#getSqlMapElement
- DeleteListByExampleAndLimitElementGenerator.java
- org.mybatis.generator.codegen.mybatis3.xmlmapper.XMLMapperGenerator#getSqlMapElement
- plugin扩展生成
- Plugin.java PluginAggregator增加接口
- org.mybatis.generator.internal.PluginAggregator#sqlMapDeleteListByExampleAndLimitElementGenerated
- Plugin.java PluginAggregator增加接口
至此,一个标准的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中取属性。)
Recommend
-
21
1 前言 本文接上篇文章跟大家聊聊我们为什么要学习源码?学习源码对我们有用吗?,那么本篇文章再继续跟小伙伴们聊聊源码这个话题。 在工作之余开始写SpringBoot源码分析专栏前,跟小伙伴们聊聊“分析开源项目源码,我们该如何入手分析?”这个话题,我们就随便扯皮
-
12
1 前言 本文接上篇文章
-
11
之前已经花了大量时间分析同步器框架AQS的源码实现,这篇文章分析一下CountDownLatch的源码实现,本文参看的JDK源码为JDK11,其他版本不一定适合。 CountDownLatch其实是复合名词,由...
-
8
开源的 c 语言网络协程库 state_thread 源码分析 仅限深圳|现场揭秘:腾讯云原生数据库架构探索与实践 >> ...
-
6
阿里的前端整体水平可以说是国内top级别的,相关开源的组件库,尤其ant-design(react版本),在国内外有着较高的使用率。在随着前端技术栈的不断完善,相应匹配的组件库也伴随着版本的迭代。而这些组件库的...
-
3
202208-源码解析springbatch的job是如何运行的? 注,本文中的demo代码节选于图书《Spring Batch批处理框架》的配套源代码,并做并适配springboot升级版本,完全开源。
-
6
之前旁边的小伙伴问我热点数据相关问题,在给他粗略地讲解一波redis数据倾斜的案例之后,自己也顺道回顾了一些关于热点数据处理的方法论,同时也想起去年所学习JD开源项目hotkey——专门用来解决热点数据问题的框架。在这里结合两者所关联到的知识点,通过几个小图和部...
-
8
复现MySQL的索引选择失误以及通过OPTIMIZER_TRACE分析过程 验证环境:MySQL 5.7.39 windows-pc 一、构造数据(生成150万数据) 构建一张账户...
-
3
DRF认证组件(源码分析) 1. 数据库建立用户表 在drf中也给我们提供了 认证组件 ,帮助我们快速实现认证相关的功能,例如: # models.py from django.db import models class Us...
-
5
Django的Message组件(源码分析) # MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage' # MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' MESSAGE_STOR...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK