开源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();
}
调用的组件很分散,先记住几个关键组件
- 【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)
@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做了哪些内容? 下面举例:
- 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中取属性。)
开源MyBatisGenerator组件源码分析的更多相关文章
- Django-restframework 源码之认证组件源码分析
Django-restframework 源码之认证组件源码分析 一 前言 之前在 Django-restframework 的流程分析博客中,把最重要的关于认证.权限和频率的方法找到了.该方法是 A ...
- element-ui 组件源码分析整理笔记目录
element-ui button组件 radio组件源码分析整理笔记(一) element-ui switch组件源码分析整理笔记(二) element-ui inputNumber.Card .B ...
- ceph-csi组件源码分析(1)-组件介绍与部署yaml分析
更多ceph-csi其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 ceph-csi组件源码分析(1)-组件介绍与部署yaml分析 基于tag v3.0.0 ht ...
- element-ui button组件 radio组件源码分析整理笔记(一)
Button组件 button.vue <template> <button class="el-button" @click="handleClick ...
- element-ui MessageBox组件源码分析整理笔记(十二)
MessageBox组件源码,有添加部分注释 main.vue <template> <transition name="msgbox-fade"> < ...
- Django REST framework —— 权限组件源码分析
在上一篇文章中我们已经分析了认证组件源码,我们再来看看权限组件的源码,权限组件相对容易,因为只需要返回True 和False即可 代码 class ShoppingCarView(ViewSetMix ...
- Django REST framework —— 认证组件源码分析
我在前面的博客里已经讲过了,我们一般编写API的时候用的方式 class CoursesView(ViewSetMixin,APIView): pass 这种方式的有点是,灵活性比较大,可以根据自己的 ...
- element-ui input组件源码分析整理笔记(六)
input 输入框组件 源码: <template> <div :class="[ type === 'textarea' ? 'el-textarea' : 'el-in ...
- element-ui Message组件源码分析整理笔记(八)
Message组件源码: main.js import Vue from 'vue'; import Main from './main.vue'; import { PopupManager } f ...
随机推荐
- 并发编程系列之Lock锁可重入性与公平性
一.相似之处:Lock锁 vs Synchronized 代码块 Lock锁是一种类似于synchronized 同步代码块的线程同步机制.从Java 5开始java.util.concurrent. ...
- Java高并发-概念
一.为什么需要并行 业务要求 http处理多个客户端请求 java虚拟机启动多个线程 进程开销比线程大的多 性能 多线程在多核系统比单线程要好的多 摩尔定律失效 二.几个重要概念 2.1 同步和异步 ...
- 奶盖拌饭 NKOJ8457
题意:一个无向图,每次询问给出一条边,问这条边的最大值满足这条边一定在这个图的最小生成树上,如果没有上限输出-1. 思路:考场上想过的,将分为两类,(非)树边. 1.亿点性质 非树边:加上这条边所构成 ...
- C++:数的变化
数的变化 时间限制 : 1.000 sec 内存限制 : 128 MB 题目描述: 小明给你提出了一个问题,即给出两个整数 a 和 b,每次操作可以 a+1 或 a×2,问至少进行多少次 ...
- 意味着JNPF迈入新时代的3.4版本,与3.3.3版本有着哪些功能区别呢?
在线开发 3.3.3版本 同一个功能分功能设计和移动设计 功能设计没有更换模式 功能设计没有同步菜单 功能设计和移动设计无表模式 3.4.1版本 同一个功能可以在功能设计里面设计,根据客户需求自己选 ...
- Mysql优化基础之Explain工具
字段解释 id:代表sql中查询语句的序列号,序列号越大则执行的优先级越高,序号一样谁在前谁先执行.id为null则最后执行 select_type:查询类型,表示当前被分析的sql语句的查询的复杂度 ...
- python基础学习7
python基础学习7 内容概要 字符串的内置方法 字符串的内置方法(补充) 列表的内置方法 可变类型与不可变类型 队列与堆栈 内容详情 字符串的内置方法 # 1.strip 移除字符串首尾的指定字符 ...
- 技术分享 | Appium 用例录制
原文链接 下载及安装 下载地址: https://github.com/appium/appium-desktop/releases 下载对应系统的 Appium 版本,安装完成之后,点击 " ...
- LVGL库入门教程04-样式
LVGL样式 LVGL样式概述 创建样式 在 LVGL 中,样式都是以对象的方式存在,一个对象可以描述一种样式.每个控件都可以独立添加样式,创建的样式之间互不影响. 可以使用 lv_style_t 类 ...
- Kubernetes-23:详解如何将CPU Manager做到游刃有余
k8s中为什么要用CPU Manager? 默认情况下,kubelet 使用CFS配额来执行 Pod 的 CPU 约束.Kubernetes的Node节点会运行多个Pod,其中会有部分的Pod属于CP ...