? 优质资源分享 ?
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
? Python实战微信订餐小程序 ? | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
?Python量化交易实战? | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
开源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 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); |
| | } |
| | } |
折叠
从入口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(); |
| | } |
| | |
折叠
调用的组件很分散,先记住几个关键组件
- 【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()); |
| | } |
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; |
| | } |
| | |
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中取属性。)
转载请注明:xuhss » 开源MyBatisGenerator组件源码分析