代码生成器

代码生成器概述

背景

基于MVC架构的传统进行模块开发的步骤:

  • 创建数据库表
  • 根据表字段设计对应的实体类(PO)
  • 编写增删改查持久层(dao)代码
  • 根据业务逻辑编写service层代码
  • controller层代码和前端页面

通常只要知道了一个表的结构,增删改查的前后台代码格式基本上就是固定的,比如controllerservicedao实体类Po、前端jsp等模块的代码在不同数据库表基础上都具有类似的结构,所以正对这部分代码,可以让计算机自动帮我们生成,让精力聚焦于更复杂业务代码的编写上

需求分析

代码生成器依据公共的模板和数据库表中的信息自动生成代码

  • 传统开发中,由于不同数据库基础增删改查代码结构相似,程序员通常都是将一份已经写好的代码进行复制和修改,再根据不同业务需求在此基础上进行代码的完善,可以将这份代码称之为代码模板
  • 生成的基础代码与对应数据库密切相关,所以除代码模板之外还需要数据库表的信息来对模板相关内容进行填充

image-20231214225027020

基本思路

image-20231214225902481

代码生成自动生成代码的几个重点需要解决的问题

  1. 数据库以及表的解析,用于生成实体类以及其他代码

    从数据库中解析出数据库中表的名称以及表字段等属性:可以根据表名确定实体类名称,根据字段确定实体类中的属性

  2. 模板开发生成代码文件

    模板中定义公共的基础代码和需要替换的占位符内容,然后根据解析好的数据库信息进行数据替换并生成对应代码文件

  3. 基础框架模板代码的抽取

FreeMaker的使用

FreeMaker概述

FreeMaker是一款模板引擎:一种基于模板的,用来输出文本的通用工具。

它是为Java程序员提供的一个开发包或者类库,它并不面向最终用户,而是面向程序员提供的嵌入他们开发产品过程中的一款应用程序。

FreeMaker最早是用来设计生成HTML网页的。使用 MVC 模式的动态网页的构思使得你可以将前端设计者(编写 HTML)从 程序员中分离出来。所有人各司其职,发挥其擅长的一面。网页设计师可以改写页面的显示效果而不受程序员编译 代码的影响,因为应用程序的逻辑(Java 程序)和页面设计(FreeMarker 模板)已经分开了。页面模板代码不会受到复 杂的程序代码影响。这种分离的思想即便对一个程序员和页面设计师是同一个人的项目来说都是非常有用的,因为 分离使得代码保持简洁而且便于维护。

image-20231214231403258

FreeMaker的应用场景

动态页面

基于模板配置和表达式生成页面文件,可以像jsp一样被客户端访问

页面静态化

对于系统中频繁使用数据库进行查询但是内容更新很小的应用,都可以用FreeMarker将网页静态化,这样就避免 了大量的数据库访问请求,从而提高网站的性能

代码生成器

可以自动根据后台配置生成页面或者代码

FreeMaker的优势

  • 强大的模板语言:有条件的块,迭代,赋值,字符串和算术运算和格式化,宏和函数,编码等更多的功能;
  • 多用途且轻量:零依赖,输出任何格式,可以从任何地方加载模板(可插拔),配置选项丰富;
  • 智能的国际化和本地化:对区域设置和日期/时间格式敏感;
  • XML处理功能:将dom-s放入到XML数据模型并遍历它们,甚至处理他们的声明;
  • 通用的数据模型:通过可插拔适配器将java对象暴露于模板作为变量树;

FreeMaker的基本使用

使用FreeMaker的重点

  • 编写模板文件
  • 使用FreeMaker解析模板,并将模板中的对应内容替换掉

导入pom依赖

1
2
3
4
5
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>

FreeMaker使用步骤

编写对应的模板文件

FreeMaker的模板文件一般以.ftl结尾,以什么后缀结尾并不影响最终的处理

模板数据填充替换

解析模板文件,将数据替换到模板中对应位置,并生成需要的文件,此过程一般需要经历以下5个步骤

  1. 创建FreeMaker的配置类
  2. 指定模板加载器,将模板存入缓存
  3. 获取模板
  4. 构造数据模型
  5. 输出生成的文件

入门案例:文件模板

创建模板文件template01.ftl

在工程src同级目录下新建文件templates/template01.ftl

1
中国欢迎您:${username}
使用FreeMaker进行数据填充,生成指定文件
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
public class FreeMakerParse {
public static void main(String[] args) throws IOException, TemplateException {
//1.创建FreeMaker配置类
Configuration cfg = new Configuration();
//2.选择模板加载器
//2.1指定模板路径
String templateFolder = "templates";
//2.2选择文件模板加载器
FileTemplateLoader loader = new FileTemplateLoader(new File(templateFolder));
//2.3设置模板加载器
cfg.setTemplateLoader(loader);
//3.获取模板
String templatePath = "template01.ftl";
Template template = cfg.getTemplate(templatePath);
//4.构造数据模型,FreeMaker中数据模型以HashMap的形式存在,key为模板中占位符,value为替换数据
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("username","李白");
//5.处理模板,输出文件
/**
* process参数:
* 参数1:数据模型,HashMap
* 参数2:writer对象,可以输出到文件或者控制台
* 文件用: new FileWriter(文件名)
* 控制台用: new PrintWriter()
*/
//输出到控制台
//template.process(dataMap,new PrintWriter(System.out));
//写入文件
//文件路径
String outPath = "Files/out1.txt";
template.process(dataMap,new FileWriter(new File(outPath)));
}
}

项目目录结构

image-20231214235525320

入门案例:字符模板

模板通过字符串的形式在代码中直接定义

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
public class FreeMakerStringParse {
public static void main(String[] args) throws IOException, TemplateException {
//1.创建FreeMaker配置类
Configuration cfg = new Configuration();
//2.选择模板加载器
cfg.setTemplateLoader(new StringTemplateLoader());
//3.创建模板
//3.1定义字模板字符串
String templateStr = "你来自于哪个城市:${cityName}";
//3.2创建模板
//第一参数:模板名 第二个参数:读取字符模板 第三个参数:配置类
Template template = new Template("strYemplate", new StringReader(templateStr), cfg);
//4.构造数据模型,FreeMaker中数据模型以HashMap的形式存在,key为模板中占位符,value为替换数据
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("cityName","安徽");
//5.输出文件
/**
* process参数:
* 参数1:数据模型,HashMap
* 参数2:writer对象,可以输出到文件或者控制台
* 文件用: new FileWriter(文件名)
* 控制台用: new PrintWriter()
*/
//输出到控制台
template.process(dataMap,new PrintWriter(System.out));
//写入文件
//文件路径
// String outPath = "Files/out1.txt";
// template.process(dataMap,new FileWriter(new File(outPath)));
}
}

image-20231215000952116

FreeMaker模板

经过前面案例,明显感觉到FreeMakerJava代码部分编写的步骤比较固定,FreeMaker的重点部分在于模板的创建编写

概述

FreeMaker模板文件主要由以下5个部分组成:

  • 数据模型:模板中用到的数据
  • 文本:直接输出的部分
  • 注释:模板中的注释格式<#-- 注释内容 -->
  • 插值:即${}或者#{}部分,将会使用数据模型中的部分来对这部分进行替换
  • FTL指令:FreeMaker指令,和HTML标记类似,名字前面加#区分,不会输出
数据模型

FreeMarker(还有模板开发者)并不关心数据是如何计算的,FreeMarker 只是知道真实的数据是什么。模板能用 的所有数据被包装成 data-model 数据模型

数据模型结构

image-20231215002110262

比如要获取user数据,模板中应该写为:${user},要获取url数据,模板中应该写为:${latestProduct.url}

模板常用标签
  • ${},插值(占位符),FreeMaker在输出时,会用数据模型中的实际值进行替代
  • <#..>: FTL标记,类似于HTML标记,#主要是为了与HTML进行区分
  • <@>:宏,自定义标签
  • <#-- -->注释符
模板中常用的指令(FTL标记)
if指令

条件分支控制语句

<#内写的占位符,无需用#{}包裹

1
2
3
4
5
6
7
<#if flag=1>
传入的数据是1
<#elseif flag=2>
传入的数据据是2
<#else>
传入的数据是其他
</#if>

对应的FreeMaker解析的代码

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
public class FreeMakerParse {
public static void main(String[] args) throws IOException, TemplateException {
//1.创建FreeMaker配置类
Configuration cfg = new Configuration();
//2.选择模板加载器
//2.1指定模板路径
String templateFolder = "templates";
//2.2选择文件模板加载器
FileTemplateLoader loader = new FileTemplateLoader(new File(templateFolder));
//2.3设置模板加载器
cfg.setTemplateLoader(loader);
//3.获取模板
String templatePath = "template01.ftl";
Template template = cfg.getTemplate(templatePath);
//4.构造数据模型,FreeMaker中数据模型以HashMap的形式存在,key为模板中占位符,value为替换数据
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("username","李白");
dataMap.put("flag",3);
//5.输出文件
/**
* process参数:
* 参数1:数据模型,HashMap
* 参数2:writer对象,可以输出到文件或者控制台
* 文件用: new FileWriter(文件名)
* 控制台用: new PrintWriter()
*/
//输出到控制台
template.process(dataMap,new PrintWriter(System.out));
//写入文件
//文件路径
// String outPath = "Files/out1.txt";
// template.process(dataMap,new FileWriter(new File(outPath)));

}
}
list指令

list指令是迭代输出指令,用于迭代输出数据模型中的集合数据

1
2
3
<#list userList as user>
当前登录用户:${user}
</#list>

对应的FreeMaker解析的代码

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
public class FreeMakerParse {
public static void main(String[] args) throws IOException, TemplateException {
//1.创建FreeMaker配置类
Configuration cfg = new Configuration();
//2.选择模板加载器
//2.1指定模板路径
String templateFolder = "templates";
//2.2选择文件模板加载器
FileTemplateLoader loader = new FileTemplateLoader(new File(templateFolder));
//2.3设置模板加载器
cfg.setTemplateLoader(loader);
//3.获取模板
String templatePath = "template01.ftl";
Template template = cfg.getTemplate(templatePath);
//4.构造数据模型,FreeMaker中数据模型以HashMap的形式存在,key为模板中占位符,value为替换数据
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("username","李白");
dataMap.put("flag",3);
ArrayList<String> list = new ArrayList<>();
list.add("曹阿满");
list.add("刘大耳");
list.add("孙十万");
list.add("诸葛村夫");
dataMap.put("userList",list);
//5.输出文件
/**
* process参数:
* 参数1:数据模型,HashMap
* 参数2:writer对象,可以输出到文件或者控制台
* 文件用: new FileWriter(文件名)
* 控制台用: new PrintWriter()
*/
//输出到控制台
template.process(dataMap,new PrintWriter(System.out));
//写入文件
//文件路径
// String outPath = "Files/out1.txt";
// template.process(dataMap,new FileWriter(new File(outPath)));

}
}

执行结果

image-20231215004420106

list结合break指令使用
1
2
3
4
5
6
<#list userList as user>
当前登录用户:${user},当前用户编号:${user_index+1}
<#if user_has_next>=========</#if>
<#if user="孙十万"><#break></#if>

</#list>
  • xxx_index:当前变量的索引值
  • xxx_has_next:是否存在下一个对象
  • <#break>:跳出循环迭代

运行结果

image-20231215005053032

include指令

模板包含,一个模板中包含另一个模板内容

1
<#include "需要导入包含模板路径,支持相对路径的写法,只是要用引号括起来">
assign指令

assign指令用于在模板中自定义个变量,可以存储到数据模型中,在模板其他部分引用

1
<#assign age=27>

模板其他位置可以通过${age}来引用该变量

内置函数

FreeMarker还提供了一些内建函数来转换输出,可以在任何变量后紧跟?,?后紧跟内建函数,就可通过内建函 数来转换输出变量。

使用语法:${username?upper_case}

下面是常用的内建的字符串函数:

  • ?html:html字符转义
  • ?cap_first: 字符串的第一个字母变为大写形式
  • ?lower_case :字符串的小写形式
  • ?upper_case :字符串的大写形式
  • ?trim:去掉字符串首尾的空格
  • ?substring:截字符串
  • ?lenth: 取长度
  • ?size: 序列中元素的个数
  • ?int : 数字的整数部分(比如- 1.9?int 就是- 1)
  • ?replace:字符串替换

数据库元数据

背景

什么是数据库元数据

元数据(meta data)是指定义数据结构的数据,数据库元数据是指定义数据库各类对象结构的数据,例如数据库中的数据库名、表名、列名、用户名、版本名以及从SQL语句得到的结果中大部分字符串都是元数据

数据库元数据作用

  • 在应用设计时,能够充分利用数据库元数据
  • 深入理解了数据库组织结构,再去理解数据访问相关框架的实现原理会更加容易

如何获取数据库元数据

JDBC来处理数据库的接口主要有三个,即ConnectionPreparedStatementResultSet这三个, 而对于这三个接口,还可以获取不同类型的元数据,通过这些元数据类获得一些数据库的信息。

数据库元数据

概述

数据库元数据(DatabaseMetaData):是由Connection对象通过getMetaData方法获取而来,主要封装了是对 数据库本身的一些整体综合信息,例如数据库的产品名称,数据库的版本号,数据库的URL,是否支持事务等等

下面是getMetaData的一些常用方法:

  • getDatabaseProductName:获取数据库的产品名称
  • getDatabaseProductVersion:获取数据库的版本号
  • getUserName:获取数据库的用户名
  • getURL:获取数据库连接的URL
  • getDriverName:获取数据库的驱动名称
  • driverVersion:获取数据库的驱动版本号
  • isReadOnly:查看数据库是否只允许读操作
  • supportsTransactions:查看数据库是否支持事务

使用案例

引入pom依赖
1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
获取数据库连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Connection conn;
//建立数据库连接
@Before
public void init() throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.配置连接基本属性
//url不指定数据据库名,便于获取当前服务器下所有数据库
String url = "jdbc:mysql://127.0.0.1:3306?useUnicode=true&characterEncoding=utf8";
String userName = "train_member";
String passWd = "wu123456";
Properties props = new Properties();
props.put("user",userName);
props.put("password",passWd);
props.put("remarksReporting","true"); //获取表的REMARK(备注信息)
//3.获取连接
conn = (Connection) DriverManager.getConnection(url, props);
}
获取数据库综合信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取数据库综合信息
@Test
public void test01() throws SQLException {
//获取数据库元数据
DatabaseMetaData metaData = conn.getMetaData();
//获取数据库产品名称
System.out.println(metaData.getDatabaseProductName());
//获取数据库版本号
System.out.println(metaData.getDatabaseProductVersion());
//获取数据库的用户名
System.out.println(metaData.getUserName());
//获取数据库连接的URL
System.out.println(metaData.getURL());
//获取数据库的驱动名称
System.out.println(metaData.getDriverName());
//获取数据库的驱动版本号
System.out.println(metaData.getDriverVersion());
//查看数据库是否只允许读操作
System.out.println(metaData.isReadOnly());
//查看数据库是否支持事务
System.out.println(metaData.supportsTransactions());
}
1
2
3
4
5
6
7
8
9
#运行结果
MySQL
5.7.35-log
train_member@localhost
jdbc:mysql://127.0.0.1:3306?useUnicode=true&characterEncoding=utf8
MySQL-AB JDBC Driver
mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
false
true
获取数据库列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//获取数据列表
@Test
public void test2() throws SQLException {
//获取元数据
DatabaseMetaData metaData = conn.getMetaData();
//获取数据库列表
ResultSet catalogs = metaData.getCatalogs();
//年里数据库列表
while (catalogs.next()){
System.out.println(catalogs.getString(1));
}
//释放资源
catalogs.close();
conn.close();
}
1
2
3
#运行结果
information_schema
train_member

image-20231216102339977

获取数据库中表信息
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
//获取数据库表的信息
@Test
public void test03() throws SQLException {
//1.获取元数据
DatabaseMetaData metaData = conn.getMetaData();
//2.数据库表信息
/**
* getTables有四个参数:
* String catalog: 数据库名称
* String schemaPattern: mysql此项参数为空
* String tableNamePattern:表名,为空则为所有表
* String types[]:表类型,可以为 TABLE:数据库表 VIEW:视图
*/
ResultSet rs = metaData.getTables("train_member", null, null, new String[]{"TABLE"});
while (rs.next()){
//所属数据库
System.out.println(rs.getString(1));
//所属schema
System.out.println(rs.getString(2));
//表名
System.out.println(rs.getString(3));
//数据库表类型
System.out.println(rs.getString(4));
//数据库备注
System.out.println(rs.getString(5));
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#运行结果
train_member
null
member
TABLE

train_member
null
passenger
TABLE

train_member
null
ticket
TABLE
获取指定表中的字段信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取数据库表字段信息
@Test
public void test04() throws SQLException {
//获取元数据
DatabaseMetaData metaData = conn.getMetaData();
/*
getColumns有四个参数
String catalog: 数据库名
String schemaPattern: mysql可以为空
String tableNamePattern: 表名
String columnNamePattern: 列名
*/
ResultSet rs = metaData.getColumns("train_member", null, "passenger", null);
while (rs.next()){
//rs结果集中对应属性获取建议参考JDK官方文档
System.out.println(rs.getString("COLUMN_NAME")); //字段名获取
}
//关闭资源
rs.close();
conn.close();

}

具体详细用法,可以参考JDK官方文档

image-20231216104929224

参数元数据

参数元数据(ParameterMetaData):是由PreparedStatement对象通过getParameterMetaData方法获取而 来,主要是针对PreparedStatement对象和其预编译的SQL命令语句提供一些信息,ParameterMetaData能提供 占位符参数的个数,获取指定位置占位符的SQL类型等等

参数元数据在实际应用场景中一般很少用到,只需了解即可,主要常用方法:

  • getParameterCount:获取预编译SQL语句中占位符参数的个数
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
//参数元数据简单使用
@Test
public void test05() throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.配置连接基本属性
//url不指定数据据库名,便于获取当前服务器下所有数据库
String url = "jdbc:mysql://127.0.0.1:3306/train_member?useUnicode=true&characterEncoding=utf8";
String userName = "train_member";
String passWd = "wu123456";
Properties props = new Properties();
props.put("user",userName);
props.put("password",passWd);
props.put("remarksReporting","true"); //获取表的REMARK(备注信息)
//3.获取连接
conn = (Connection) DriverManager.getConnection(url, props);
//4.编写SQL语句
String sql = "select * from passenger where id=?";
//5.编译SQL
PreparedStatement pstm = conn.prepareStatement(sql);
//6.填充参数
pstm.setString(1,"1733754970974588928");
//7.获取参数元数据
ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
//8.获取参数个数
int count = parameterMetaData.getParameterCount();
System.out.println(count);//1

}

结果集元数据

结果集元数据(ResultSetMetaData):是由ResultSet对象通过getMetaData方法获取而来,主要是针对由数据 库执行的SQL脚本命令获取的结果集对象ResultSet中提供的一些信息,比如结果集中的列数、指定列的名称、指定 列的SQL类型等等,可以说这个是对于框架来说非常重要的一个对象

关于ResultSetMetaData的常用方法:

  • getColumnCount:获取结果集中列项的个数
  • .getColumnName: 获取列名
  • getColumnTypeName:getColumnTypeName:获取指定列的SQL数据类型
  • getColumnClassName:获取指定列SQL类型对应于Java中的数据类型(包名加类名)
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
//结果集元数据
@Test
public void test06() throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.配置连接基本属性
//url不指定数据据库名,便于获取当前服务器下所有数据库
String url = "jdbc:mysql://127.0.0.1:3306/train_member?useUnicode=true&characterEncoding=utf8";
String userName = "train_member";
String passWd = "wu123456";
Properties props = new Properties();
props.put("user",userName);
props.put("password",passWd);
props.put("remarksReporting","true"); //获取表的REMARK(备注信息)
//3.获取连接
conn = (Connection) DriverManager.getConnection(url, props);
//4.编写SQL语句
String sql = "select * from passenger where id=?";
//5.编译SQL
PreparedStatement pstm = conn.prepareStatement(sql);
//6.填充参数
pstm.setString(1,"1733754970974588928");
//7.执行获取结果集
ResultSet rs = pstm.executeQuery();
//8.获取结果集元数据
ResultSetMetaData metaData = rs.getMetaData();
//获取列个数
int columnCount = metaData.getColumnCount();
//获取列信息
for(int i=1;i<=columnCount;i++){
//1.获取列名
System.out.println(metaData.getColumnName(i));
//2.获取列对应的SQL数据类型
System.out.println(metaData.getColumnTypeName(i));
//3.获取列对应Java数据类型
System.out.println(metaData.getColumnClassName(i));
System.out.println("================================");
}
}
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
#运行结果
id
BIGINT
java.lang.Long
================================
member_id
BIGINT
java.lang.Long
================================
name
VARCHAR
java.lang.String
================================
id_card
VARCHAR
java.lang.String
================================
type
CHAR
java.lang.String
================================
create_time
DATETIME
java.sql.Timestamp
================================
update_time
DATETIME
java.sql.Timestamp
================================

代码生成器制作

思路分析

image-20231216144427656

完成代码生成器需要以下几个步骤:

  • 用户填写数据库相关信息,相关信息封装到实体类,便于后续操作
  • 借助元数据,将数据库表信息、数据库字段信息封装到对应实体类
  • 构建FreeMaker数据模型,将数据库表对象和相关配置存入Map集合
  • 利用FreeMaker完成代码生成
  • 自定义公共代码模板

项目环境搭建

引入pom依赖

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
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.15</version>
</dependency>

创建对应实体类

在整个项目中设计如下实体类的封装

数据库信息实体类

在代码生成过程中,用户需要提供数据库类型、数据名、用户名、密码、数据库地址等数据信息,为便于后续操作,可以将这些信息统一封装成一个对应的实体类

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
/**
* 数据库信息对应实体类
* 用户需要提供以下信息:
* ①数据库类型 ②数据库名称 ③数据库ip地址以及端口号 ④用户名 ⑤用户密码
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataBase {
//数据库连接,支持Mysql和Oracale两种数据库类型
private static String mysqlUrl = "jdbc:mysql://[ip]:[port]/[db]?useUnicode=true&amp;characterEncoding=UTF8";
private static String oracleUrl = "jdbc:oracle:thin:@[ip]:[port]:[db]";

private String dbType="MYSQL";//数据库类型,需要用户输入,默认为
private String dbName; //数据库名称
private String driver="com.mysql.jdbc.Driver";//驱动名称
private String userName;//用户名
private String passWord;//密码
private String ip="127.0.0.1"; //数据库ip地址
private String port="3306";
private String url;//数据库地址,包括协议类型

public DataBase(String dbType,String dbName,String userName,String passWord,String ip,String port){
this.dbType = dbType;
this.dbName = dbName;
this.ip=ip;
this.port=port;
this.userName = userName;
this.passWord = passWord;
if("MYSQL".equals(dbType.toUpperCase())){
this.driver = "com.mysql.jdbc.Driver";
this.url = mysqlUrl.replace("[ip]",this.ip).replace("[port]",port).replace("[db]",this.dbName);
}else{
this.driver = "oracle.jdbc.driver.OracleDriver";
this.url=oracleUrl.replace("[ip]",ip).replace("[port]",port).replace("[db]",dbName);
}
}
}
配置信息实体类

用户需要输入项目的项目名称、全包名、项目的中文名等,为便于使用,也将这些数据封装成一个实体类

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
/*
项目配置信息实体类,包括项目名称、包名、项目中文名等
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Settings {
private String project="example"; //项目名
private String pPackage="com.example.demo"; //全包名
private String projectComment; //项目中文名
private String author; //作者
private String path1="com"; //项目包名的分解,这里假定一般为三级目录
private String path2="example";
private String path3="demo";
private String pathAll; //项目存储全路径

public Settings(String project, String pPackage, String projectComment, String author) {
if(StrUtil.isNotBlank(project)) {
this.project = project;
}
if(StrUtil.isNotBlank(pPackage)) {
this.pPackage = pPackage;
String[] paths = pPackage.split("\\.");
this.path1 = paths[0];
this.path2 = paths.length>1?paths[1]:path2;
this.path3 = paths.length>2?paths[2]:path3;
this.pathAll = pPackage.replaceAll(".","/");
}
this.projectComment = projectComment;
this.author = author;
}

//属性和值转换为map形式,便于后续生成freeMaker的数据模型
public Map<String, Object> getSettingMap(){
Map<String, Object> map = new HashMap<>();
Field[] declaredFields = Settings.class.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
try{
map.put(field.getName(), field.get(this));
}catch (Exception e){}
}
return map;
}

}
数据库表信息实体类

数据某个表的SQL表名、处理后的Java表名,以及数据库表的注释信息、字段列表等数据

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 数据库表属性信息
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Table {
private String tableName; //SQL中数据库表名
private String poName; //SQL表对应Java实体类名
private String comment; //数据库表附属信息
private String key; //主键列
private List<Column> columnsList; //字段列表
}
数据库字段实体类

数据库字段SQL名称、SQL数据类型、列备注信息,是否主键,以及对应Java实体类属性名、Java数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
数据库字段信息实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Column {
private String columnName; //SQL字段名
private String fieldName; //Java实体类属性名
private String columnType;//SQL数据类型
private String fieldType;//SQL字段类型
private String columnComment;//字段附加信息
private String columnKey;//主键名称
}

数据库相关工具类的创建

将获取数据库连接,数据库资源关闭以及获取数据库表信息等功能封装成对应的工具类方法

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
public class DataBaseUtils {
//获取数据库连接
public static Connection getConnection(DataBase dataBase) throws Exception {
Connection conn = null;
//1.注册驱动
Class.forName(dataBase.getDriver());
//2.配置连接基本属性
String url = dataBase.getUrl();
Properties props = new Properties();
props.put("user",dataBase.getUserName());
props.put("password",dataBase.getPassWord());
props.put("remarksReporting","true"); //获取表的REMARK(备注信息)
//3.获取连接
conn = (Connection) DriverManager.getConnection(url, props);
return conn;
}


//获取数据库中所有数据库名列表
public static List<String> getSchema(DataBase db) throws Exception {
//1.获取连接
Connection conn = getConnection(db);
//2.获取元数据
DatabaseMetaData metaData = conn.getMetaData();
//获取数据库列表
ResultSet catalogs = metaData.getCatalogs();
//数据库列表
List<String> schemaList = new ArrayList<>();
while (catalogs.next()){
schemaList.add(catalogs.getString(1));
}
//释放资源
catalogs.close();
conn.close();
return schemaList;
}
}

字符串相关工具类创建

在整个代码编写中,涉及到大量字符串处理操作,比如数据库表名、字段名与Java实体类名、属性名的映射

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
package com.bang.generator.utils;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringUtils {

public static String removeCrlf(String str) {
if(str == null) return null;
return StringUtils.join(StringUtils.tokenizeToStringArray(str,"\t\n\r\f")," ");
}

private static final Map<String,String> XML = new HashMap<String,String>();

static{
XML.put("apos", "'");
XML.put("quot", "\"");
XML.put("amp", "&");
XML.put("lt", "<");
XML.put("gt", ">");
}

public static String unescapeXml(String str) {
if(str == null) return null;
for(String key : XML.keySet()) {
String value = XML.get(key);
str = StringUtils.replace(str, "&"+key+";", value);
}
return str;
}


public static String removePrefix(String str,String prefix) {
return removePrefix(str,prefix,false);
}

public static String removePrefix(String str,String prefix,boolean ignoreCase) {
if(str == null) return null;
if(prefix == null) return str;
if(ignoreCase) {
if(str.toLowerCase().startsWith(prefix.toLowerCase())) {
return str.substring(prefix.length());
}
}else {
if(str.startsWith(prefix)) {
return str.substring(prefix.length());
}
}
return str;
}

public static boolean isBlank(String str) {
return str == null || str.trim().length() == 0;
}

public static boolean isNotBlank(String str) {
return !isBlank(str);
}

public static String getExtension(String str) {
if(str == null) return null;
int i = str.lastIndexOf('.');
if(i >= 0) {
return str.substring(i+1);
}
return null;
}

/**
* Count the occurrences of the substring in string s.
* @param str string to search in. Return 0 if this is null.
* @param sub string to search for. Return 0 if this is null.
*/
public static int countOccurrencesOf(String str, String sub) {
if (str == null || sub == null || str.length() == 0 || sub.length() == 0) {
return 0;
}
int count = 0;
int pos = 0;
int idx;
while ((idx = str.indexOf(sub, pos)) != -1) {
++count;
pos = idx + sub.length();
}
return count;
}

public static boolean contains(String str,String... keywords) {
if(str == null) return false;
if(keywords == null) throw new IllegalArgumentException("'keywords' must be not null");

for(String keyword : keywords) {
if(str.equals(keyword)) {
return true;
}
}
return false;
}

public static String defaultString(Object value) {
if(value == null) {
return "";
}
return value.toString();
}

public static String defaultIfEmpty(Object value,String defaultValue) {
if(value == null || "".equals(value)) {
return defaultValue;
}
return value.toString();
}

public static String makeAllWordFirstLetterUpperCase(String sqlName) {
String[] strs = sqlName.toLowerCase().split("_");
String result = "";
String preStr = "";
for(int i = 0; i < strs.length; i++) {
if(preStr.length() == 1) {
result += strs[i];
}else {
result += capitalize(strs[i]);
}
preStr = strs[i];
}
return result;
}

public static int indexOfByRegex(String input,String regex) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
if(m.find()) {
return m.start();
}
return -1;
}

public static String toJavaVariableName(String str) {
return uncapitalize(toJavaClassName(str));
}

public static String toJavaClassName(String str) {
return makeAllWordFirstLetterUpperCase(StringUtils.toUnderscoreName(str));
}

public static String removeMany(String inString, String... keywords) {
if (inString == null) {
return null;
}
for(String k : keywords) {
inString = replace(inString, k, "");
}
return inString;
}

public static String replace(String inString, String oldPattern, String newPattern) {
if (inString == null) {
return null;
}
if (oldPattern == null || newPattern == null) {
return inString;
}

StringBuffer sbuf = new StringBuffer();
// output StringBuffer we'll build up
int pos = 0; // our position in the old string
int index = inString.indexOf(oldPattern);
// the index of an occurrence we've found, or -1
int patLen = oldPattern.length();
while (index >= 0) {
sbuf.append(inString.substring(pos, index));
sbuf.append(newPattern);
pos = index + patLen;
index = inString.indexOf(oldPattern, pos);
}
sbuf.append(inString.substring(pos));

// remember to append any characters to the right of a match
return sbuf.toString();
}
/**����ĸ��copy from spring*/
public static String capitalize(String str) {
return changeFirstCharacterCase(str, true);
}

/**����ĸСдcopy from spring*/
public static String uncapitalize(String str) {
return changeFirstCharacterCase(str, false);
}
/**copy from spring*/
private static String changeFirstCharacterCase(String str, boolean capitalize) {
if (str == null || str.length() == 0) {
return str;
}
StringBuffer buf = new StringBuffer(str.length());
if (capitalize) {
buf.append(Character.toUpperCase(str.charAt(0)));
}
else {
buf.append(Character.toLowerCase(str.charAt(0)));
}
buf.append(str.substring(1));
return buf.toString();
}

private static final Random RANDOM = new Random();
public static String randomNumeric(int count) {
return random(count, false, true);
}

public static String random(int count, boolean letters, boolean numbers) {
return random(count, 0, 0, letters, numbers);
}

public static String random(int count, int start, int end, boolean letters, boolean numbers) {
return random(count, start, end, letters, numbers, null, RANDOM);
}

public static String random(int count, int start, int end, boolean letters,
boolean numbers, char[] chars, Random random) {
if (count == 0) {
return "";
} else if (count < 0) {
throw new IllegalArgumentException(
"Requested random string length " + count
+ " is less than 0.");
}
if ((start == 0) && (end == 0)) {
end = 'z' + 1;
start = ' ';
if (!letters && !numbers) {
start = 0;
end = Integer.MAX_VALUE;
}
}

char[] buffer = new char[count];
int gap = end - start;

while (count-- != 0) {
char ch;
if (chars == null) {
ch = (char) (random.nextInt(gap) + start);
} else {
ch = chars[random.nextInt(gap) + start];
}
if ((letters && Character.isLetter(ch))
|| (numbers && Character.isDigit(ch))
|| (!letters && !numbers)) {
if (ch >= 56320 && ch <= 57343) {
if (count == 0) {
count++;
} else {
// low surrogate, insert high surrogate after putting it
// in
buffer[count] = ch;
count--;
buffer[count] = (char) (55296 + random.nextInt(128));
}
} else if (ch >= 55296 && ch <= 56191) {
if (count == 0) {
count++;
} else {
// high surrogate, insert low surrogate before putting
// it in
buffer[count] = (char) (56320 + random.nextInt(128));
count--;
buffer[count] = ch;
}
} else if (ch >= 56192 && ch <= 56319) {
// private high surrogate, no effing clue, so skip it
count++;
} else {
buffer[count] = ch;
}
} else {
count++;
}
}
return new String(buffer);
}

/**
*/
public static String toUnderscoreName(String name) {
if(name == null) return null;

String filteredName = name;
if(filteredName.indexOf("_") >= 0 && filteredName.equals(filteredName.toUpperCase())) {
filteredName = filteredName.toLowerCase();
}
if(filteredName.indexOf("_") == -1 && filteredName.equals(filteredName.toUpperCase())) {
filteredName = filteredName.toLowerCase();
}

StringBuffer result = new StringBuffer();
if (filteredName != null && filteredName.length() > 0) {
result.append(filteredName.substring(0, 1).toLowerCase());
for (int i = 1; i < filteredName.length(); i++) {
String preChart = filteredName.substring(i - 1, i);
String c = filteredName.substring(i, i + 1);
if(c.equals("_")) {
result.append("_");
continue;
}
if(preChart.equals("_")){
result.append(c.toLowerCase());
continue;
}
if(c.matches("\\d")) {
result.append(c);
}else if (c.equals(c.toUpperCase())) {
result.append("_");
result.append(c.toLowerCase());
}
else {
result.append(c);
}
}
}
return result.toString();
}

public static String removeEndWiths(String inputString,String... endWiths) {
for(String endWith : endWiths) {
if(inputString.endsWith(endWith)) {
return inputString.substring(0,inputString.length() - endWith.length());
}
}
return inputString;
}

/**
* 将string转换为List<ColumnEnum> 格式为: "enumAlias(enumKey,enumDesc)"
*/
static Pattern three = Pattern.compile("(.*)\\((.*),(.*)\\)");
static Pattern two = Pattern.compile("(.*)\\((.*)\\)");

/**
* Test whether the given string matches the given substring
* at the given index.
* @param str the original string (or StringBuilder)
* @param index the index in the original string to start matching against
* @param substring the substring to match at the given index
*/
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
for (int j = 0; j < substring.length(); j++) {
int i = index + j;
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
return false;
}
}
return true;
}

public static String[] tokenizeToStringArray(String str,String seperators) {
if(str == null) return new String[0];
StringTokenizer tokenlizer = new StringTokenizer(str,seperators);
List result = new ArrayList();

while(tokenlizer.hasMoreElements()) {
Object s = tokenlizer.nextElement();
result.add(s);
}
return (String[])result.toArray(new String[result.size()]);
}
public static String join(List list, String seperator) {
return join(list.toArray(new Object[0]),seperator);
}

public static String replace(int start, int end, String str,String replacement) {
String before = str.substring(0,start);
String after = str.substring(end);
return before + replacement + after;
}

public static String join(Object[] array, String seperator) {
if(array == null) return null;
StringBuffer result = new StringBuffer();
for(int i = 0; i < array.length; i++) {
result.append(array[i]);
if(i != array.length - 1) {
result.append(seperator);
}
}
return result.toString();
}
public static int containsCount(String string, String keyword) {
if(string == null) return 0;
int count = 0;
for(int i = 0; i < string.length(); i++ ) {
int indexOf = string.indexOf(keyword,i);
if(indexOf < 0) {
break;
}
count ++;
i = indexOf;
}
return count;
}
}

构造数据模型

需求分析

借助FreeMaker可以方便的根据模板生成文件,对于FreeMaker而言,其强调数据模型+模板=文件的思想,所以代码生成器最重要的一个部分就是数据模型

数据模型一共以两种形式组成:

  • 数据库中表、字段等信息
    • 使用元数据读取,封装成对应实体类,构建数据模型
  • 用户自定义数据
    • 为使得代码生成器匹配多样的使用环境,可以让用户自定义数据,并且以key-value的形式配置到propertities文件中

自定义数据

规定用户自定义数据存放在properties目录下,构建PropertiesUtils工具类,统一对properties文件夹下的所有.properties文件进行加载

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
/*
读取Properties数据工具类
*/
public class PropertiesUtils {
//自定义数据对应的FreeMaker数据模型map集合
public static Map<String,String> customMap = new HashMap<>();

static {
//默认用户将自定义数据存放在properties目录下
File dir = new File("properties");
try {
List<File> files = searchAllFile(new File(dir.getAbsolutePath()));
for (File file : files) {
if(file.getName().endsWith(".properties")) {
Properties prop = new Properties();
prop.load(new FileInputStream(file));
customMap.putAll((Map) prop);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

//查询某个目录下的所有文件
public static List<File> searchAllFile(File dir) throws IOException {
ArrayList arrayList = new ArrayList();
searchFiles(dir,arrayList);
return arrayList;
}

//递归获取某个目录下的所有文件
public static void searchFiles(File dir,List<File> collector) throws IOException {
if(dir.isDirectory()) {
File[] subFiles = dir.listFiles();
for(int i = 0; i < subFiles.length; i++) {
searchFiles(subFiles[i],collector);
}
}else{
collector.add(dir);
}
}
}

测试

1
2
3
4
5
6
@Test
public void test03(){
PropertiesUtils.customMap.forEach((k,v)->{
System.out.println(k+"->"+v);
});
}

元数据处理

将元数据读取到对应数据库表和字段的信息封装到前面定义的TableColumn实体类

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
81
82
83
84
85
/**
*将数据库中表和字段信息封装成实体类
* 操作步骤:
* ①获取连接
* ②获取dbdatabasemetaData
* ③获取数据库中所有的表
* ④获取表中的所有字段
* ⑤封装成Java对象
*/
public static List<Table> getDbInfo(DataBase db) throws Exception{
//1.获取连接
Connection conn = getConnection(db);
//2.获取元数据
DatabaseMetaData metaData = conn.getMetaData();
//3.获取数据库下所有表名
List<Table> tableList = new ArrayList<>();
ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"});
while (tables.next()){
Table tab = new Table();
//①表名
String tableName = tables.getString("TABLE_NAME");
tab.setTableName(tableName);
//②对应Java实体类名,去掉前缀,首字母大写
String className = removePrefix(tableName);
tab.setPoName(className);
//③获取描述信息
String remarks = tables.getString("REMARKS");
tab.setComment(remarks);
//④主键
ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName);
String primaryKesName="";
while (primaryKeys.next()){
//获取主键名
String keyName = primaryKeys.getString("COLUMN_NAME");
primaryKesName+=keyName+","; //多个主键以逗号分隔
}
tab.setKey(primaryKesName);
//5表中所有字段
List<Column> columnList = new ArrayList<>();
ResultSet columns = metaData.getColumns(null, null, tableName, null);
while (columns.next()){
Column column = new Column();
//列名
String columnName = columns.getString("COLUMN_NAME"); //字段名获取
column.setColumnName(columnName);
//Java属性名,转换规则: user_id--> userId
String fieldName = StringUtils.toJavaVariableName(columnName);
column.setFieldName(fieldName);
//字段SQL数据类型
String columnType = columns.getString("TYPE_NAME");
column.setColumnType(columnType);
//Java属性类型
String filedType = PropertiesUtils.customMap.get(columnType);
column.setFieldType(filedType);
//备注信息
String comment = columns.getString("REMARKS");
column.setColumnComment(comment);
//是否是主键
String pri = "";
if(StringUtils.contains(columnName,primaryKesName.split(","))){
pri = "PRI";
}
column.setColumnKey(pri);
columnList.add(column);
}
tab.setColumnsList(columnList);
tableList.add(tab);
columns.close();
}
tables.close();
conn.close();
return tableList;
}

//删除字符串中指定前缀
public static String removePrefix(String tableName){
String prefix = PropertiesUtils.customMap.get("tableRemovePrefixes");
String newTableName = tableName;
//替换指定前缀
for(String pf:prefix.split(",")){
newTableName = newTableName.replace(pf,"");
}
//首字母大写
return StrUtil.upperFirst(newTableName);
}

测试代码

1
2
3
4
5
6
7
8
@Test
public void test01() throws Exception {
DataBase dataBase = new DataBase("MYSQL", "train_member", "train_member", "wu123456", "127.0.0.1","3306");
List<Table> dbInfo = DataBaseUtils.getDbInfo(dataBase);
for (Table table : dbInfo) {
System.out.println(table);
}
}

代码生成器实现

需求分析

用户需要提供的信息

  • 数据库相关信息
    • 包括数据库类型、ip、端口、用户名、密码等
  • 工程配置信息
    • 项目目录、包名等
  • 模板路径
    • 模板文件根目录,程序会获取目录下的所有模板文件,集合数据模型生成对应代码文件
  • 输出路径
    • 最终生成的代码文件存储位置

中间需要生成的数据模型

  • 用户自定义数据对应的数据模型
  • 数据库元数据对应的数据模型
  • 工程配置信息对应的数据模型

数据模型构建类创建

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
/**
* 用户提供的数据
* 模板位置
* 代码生成路径
* 工程配置对象 setting
* 数据库对象 DataBase
* 准备对应的数据模型
* 1.自定义配置
* 2.元数据
* 3.setting
* 调用核心处理类完成代码生成工作
* 方法:Generator
*/
@SuppressWarnings("all")
public class GeneratorFacade {
private HashMap<String, Object> map;
private Settings settings;
private DataBase db;
private Generator generator;

/**
*
* @param map 附加信息,templates:所用模板路径列表 tables: 需要生成代码的数据库表名列表
* @param settings 工程配置信息类
* @param db 数据库信息类
* @throws Exception
*/
public GeneratorFacade(HashMap<String, Object> map, Settings settings, DataBase db) throws Exception {
this.map = map;
this.settings = settings;
this.db = db;
this.generator = new Generator(map);
}

/**
* 1.准备数据模型
* 2.调用核心处理类完成代码生成工作
*/
public void generatorByDataBase() throws Exception {
//map为配置信息,里面包含①需要生成代码对应的数据库表名②对应模板的目录
List<String> tables = (List<String>) map.get("tables");
List<Table> allTable = DataBaseUtils.getDbInfo(db);
for (int i = 0; i < allTable.size(); i++) {
for (int j = 0; j < tables.size(); j++) {
if (tables.get(j).equals(allTable.get(i).getTableName())) { //只为指定表生成代码
Map<String, Object> dataModel = getDataModel(allTable.get(i));
generator.scanAndGenerator(dataModel);
}
}
}
}

/**
* 根据table对象获取数据模型
*/
private Map<String, Object> getDataModel(Table table) {
Map<String, Object> dataModel = new HashMap<>();
//1.自定义配置
dataModel.putAll(PropertiesUtils.customMap);
//2.元数据
dataModel.put("table", table); //table.name2
//3.setting
dataModel.putAll(this.settings.getSettingMap());
//4.类型
dataModel.put("ClassName", table.getPoName());
//5.添加数据库信息
dataModel.put("username", db.getUserName());
dataModel.put("password", db.getPassWord());
dataModel.put("url", db.getUrl());
dataModel.put("driver", db.getDriver());
dataModel.put("author", settings.getAuthor());
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = simpleDateFormat.format(date);
dataModel.put("createTime", str);

return dataModel;
}

}

FreeMaker解析输出代码文件

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
81
82
83
84
85
86
87
88
/**
* 代码生成器的核心处理类
* 使用Freemarker完成文件生成
* 数据模型 + 模板
* 数据:
* 数据模型
* 模板的位置
* 生成文件的路径
*
*/
@SuppressWarnings("all")
public class Generator {

private List<Configuration> cfgs;//模板加载器配置文件
private HashMap<String, Object> map;
private List<String> template;
public Generator(HashMap<String, Object> map) throws Exception {
this.map = map;
//实例化Configuration对象
cfgs = new ArrayList<>();
template = (List<String>) map.get("template");
for (int i = 0; i < template.size(); i++) {
cfgs.add(i, new Configuration());
FileTemplateLoader ftl = new FileTemplateLoader(new File(template.get(i)));
cfgs.get(i).setTemplateLoader(ftl);
}
}

/**
* 根据选中的表进行代码生成代码生成
*/
public void scanAndGenerator(Map<String, Object> dataModel) throws Exception {
//获取模板目录路径列表
List<String> template = (List<String>) map.get("template");

for (int i = 0; i < template.size(); i++) {
//1.根据模板路径找到此路径下的所有模板文件
List<File> fileList = FileUtils.searchAllFile(new File(template.get(i)));
//2.对每个模板进行文件生成
for (File file : fileList) {
executeGenerator(dataModel, file, i);
}
}

}

/**
* 对模板进行文件生成
*
* @param dataModel : 数据模型
* @param file : 模板文件
* 模板文件:c:com.ihrm.system.abc.java
*/
private void executeGenerator(Map<String, Object> dataModel, File file, int index) throws Exception {
//1.文件路径处理 (E:\模板\${path1}\${path2}\${path3}\${ClassName}.java)
//templatePath : E:\模板\

//file.getAbsolutePath(): E:\模板\xx.xx.xx.java
String templateFileName = file.getAbsolutePath().replace(template.get(index), "");
//输出路径用FreeMaker字符串模板进行处理
String outFileName = processTemplateString(templateFileName, dataModel,index);
// 2.读取文件模板
Template template = cfgs.get(index).getTemplate(templateFileName);
template.setOutputEncoding("utf-8");//指定生成文件的字符集编码
// 3.创建文件
File file1 = FileUtils.mkdir((String) map.get("templatePath"), outFileName);
// 4.模板处理(文件生成)
FileWriter fw = new FileWriter(file1);
template.process(dataModel, fw);

fw.close();
}

/**
* 将数据库的数据添加到模板里面
* @param templateString
* @param dataModel
* @param index
* @return
* @throws Exception
*/
public String processTemplateString(String templateString,Map dataModel,int index) throws Exception {
StringWriter out = new StringWriter();
Template template = new Template("ts", new StringReader(templateString), cfgs.get(index));
template.process(dataModel,out);
return out.toString();
}
}

模板文件的编写

重点在于模板文件的编写,根据需求编写不同模板