说明:本文将会同一个代码生成的实际案例出发来讲解为什么需要代码生成器?这个根据可以帮助我们生成什么东西?以及它的工作原理是什么等问题。其中主要讲解的部分是代码生成器的原理,因为只有理解了原理,我们才可以随心所欲的去编写自己想要的模板,从而为项目开发节省大量时间。
代码参考:https://github.com/YuFeiHou/GenerateCode
问题1:现在有很多代码生成工具,为什么我们还要自己去实现一个呢?
答:相信大家也都用过不少的优质代码生成器了,而这些代码生成器的操作也是很简单的,那我们为什么不直接使用这个“轮子”而非要重新造一个轮子出来呢?这样多费事呀!等各种想吐槽的声音就会开始......
其实,用过别人的代码生成器之后,第一感觉就是“爽”!然后就会产生一种好奇心理,他们是如何实现的?可拓展吗?我想要生成一个与本次现象相关的业务代码,做到做少的修改可以吗?那么这样就会驱使我们去学习代码生成器的原理。
问题2:为什么我们需要代码生成器?
答:首先我们来分析一下一个项目开发的流程,从流程中我们会就会找到合理的答案
- 创建数据库表
- 根据表字段设计实体类
- 编写增删改查dao
- 根据业务写service层
- web层代码和前台页面
在项目开发过程中,关注点更多是在业务功能的开发及保证业务流程的正确性上,对于重复性的代码编写占据了程 序员大量的时间和精力,而这些代码往往都是具有规律的。就如controller、service、serviceImpl、dao、 daoImpl、model、jsp的结构,用户、角色、权限等等模块都有类似的结构。针对这部分代码,就可以使用代码生 成器,让计算机自动帮我们生成代码,将我们的双手解脱出来,减小了手工的重复劳动。
因此,通常只需要知道了一个表的结构,增删改查的前后台页面的代码格式就是固定的,剩下的就是复杂的业务。而代码生成工具的目标就是自动生成那部分固定格式的增删改查的代码.
问题3:那代码生成器的主要实现思路是什么?
答:数据 + 模板 = 生成的代码文件 这就是代码生成器的核心思路。
数据:通过数据库解析获取数据库中表的名称、表字段等属性 + 用户自定义的配置文件(包路径,模板位置,生成路径等)
模板:通过已经实现的业务去抽取公共的基础代码模板 + 需要替换的占位符内容(使用${ 数据库中取到的值 })
代码文件:将上面获取到的元数据(从数据库中解析出来的数据)以及抽取的模板用FreeMarker解析器文件输出即可。
4. FreeMarker语法有些什么?
答:相信大家已经对FreeMarker有所了解,在这然我们重新复习一下FreeMarker的常用语法吧。
1. ${…}:称为interpolations,FreeMarker会在输出时用实际值进行替代。
- ${name}可以取得root中key为name的value。 (节点概念在这不做解释)
- ${person.name}可以取得成员变量为person的name属性
2. <#...>:FTL标记(FreeMarker模板语言标记):类似于HTML标记,为了与HTML标记区分
3.<@> :宏,自定义标签
4. <#-- -->:注释,包含<#-- -->在之间
5.被替换的内容就是用将解析出的对象封装为一个map,然后把这个数据给FreeMarker,FreeMarker会根据上述标签自动解析。
if指令
<#if 条件>
....
<#elseif 条件2>
...
<#elseif 条件3>
...
<#else>
...
</#if>
list指令
别名_index:当前变量的索引值。
别名_has_next:是否存在下一个对象
特殊变量的使用:在标签内不需要使用占位符${ },否则需要。
${别名_index}.${x} <#if 别名_has_next>,</#if>
<#list 遍历的列表 as 别名>
...
</#list>
内置函数
FreeMarker提供了一些内建函数来转换输出,可以在任何变量后紧跟?,?后紧跟内建函数,就可通过内建函 数来转换输出变量。下面是常用的内建的字符串函数:
?html:html字符转义
?cap_first: 字符串的第一个字母变为大写形式
?lower_case :字符串的小写形式
?upper_case :字符串的大写形式
?trim:去掉字符串首尾的空格
?substring:截字符串
?lenth: 取长度
?size: 序列中元素的个数
?int : 数字的整数部分(比如- 1.9?int 就是- 1)
问题4 :代码生成的步骤是什么?(具体代码可以访问github查看源码)
- 获取数据库的连接
- 通过获取到的连接,在获取所有数据库表信息.
- 循环遍历所有的表信息,找到该表的所有字段,经过处理封装到实体中.(元数据集准备完成)
- 遍历所有的元数据集,在遍历数据的时候同时加载所有的模板文件。(模板准备完成)
- 模板 + 数据 = 生成的代码文件。
问题5:数据库表抽象的实体有什么?
答:数据库基本信息实体(数据库类型 用户名 密码 驱动 连接地址(经过处理的))
用户自定义数据实体(项目名称 包路径 项目描述 作者 项目包路径1 项目包路径2 项目包路径3 代码生成路径)
表实体(表名称 实体名称 表介绍 主键列 列集合)
列实体(列名称 属性名称 列类型 数据库列类型 列备注 是否是主键)
问题6:如何获取数据库连接信息?
答:主要通过用JDBC中提供Connection接口的 DriverManager.getConnection( 连接地址, properties相关数据库配置信息)获取.
public static Connection getConnection(DataBase db) throws Exception {
Properties props = new Properties();
//获取数据库的备注信息
props.put("remarksReporting", "true");
props.put("user", db.getUserName());
props.put("password", db.getPassWord());
//注册驱动
Class.forName(db.getDriver());
return DriverManager.getConnection(db.getUrl(), props);
}
问题7:如何获取表、列字段,以及如何实现转换的?
答:获取所有数据库列表是通过DriverManager对象的getCatalogs()获取的。获取列是通过metaData.getColumns(null, null, tableName, null);方法获取的。其中关系是一对多的关系,因此需要遍历数据库表去查询。
一般我们采取的都是驼峰命名法(ts_name)来根据数据库表名创建实体名称,因此在这里采取的是去掉表前缀 + 首字母大写的方式来对实体进行命名。而对于列来说名称也是这样处理,但是类型是用过配置文件的形式又 数据库类型(varchar)- -> java类型(String).
部分参考代码如下:具体的请访问github仓库.
/**
* 获取数据库列表
*
* @param db 数据库信息
* @return
* @throws Exception
*/
public static List<String> getSchemas(DataBase db) throws Exception {
//获取元数据
Connection connection = getConnection(db);
DatabaseMetaData metaData = connection.getMetaData();
//获取所有数据库列表
ResultSet rs = metaData.getCatalogs();
List<String> list = new ArrayList<String>();
while (rs.next()) {
list.add(rs.getString(1));
}
rs.close();
connection.close();
return list;
}
/**
* 将表名转换为实体名称(db_user_name --> userName)
*
* @param tableName
* @return
*/
public static String removePrefix(String tableName) {
//获取前缀
String prefix = PropertiesUtils.customMap.get("tableRemovePrefixes");
String replaceTableName = tableName;
for (String pf : prefix.split(",")) {
replaceTableName = StringUtils.removePrefix(replaceTableName, pf, true);
}
return StringUtils.makeAllWordFirstLetterUpperCase(replaceTableName);
}
问题8:如何获取模板文件信息以及输出代码生成文件的?
答:首先创建freeMarker配置实例,然后设置模板加载器,开始加载模板,并且把模板加载在缓存中.最后通过freeMarker实例的getTemplate(模板所在路径)来获取模板文件信息。
//1.创建freeMarker配置实例
Configuration cfg = new Configuration();
//2.设置模板加载器:开始加载模板,并且把模板加载在缓存中
cfg.setTemplateLoader(new FileTemplateLoader(new File("templates")));
//3.创建数据模型
Map<String,Object> dataModel = new HashMap<>();
dataModel.put("username","张三");
//4.获取模板
Template template = cfg.getTemplate("temp01.ftl");
//i.输出到文件
//template.process(dataModel, new FileWriter(new
File("C:\\Users\\ThinkPad\\Desktop\\ihrm\\day12\\测试\\aa.text")));
//i.打印到控制台
template.process(dataModel, new PrintWriter(System.out));