一,前提知识储备

1,什么是FreeMarker?

​FreeMarker​​​ 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(​​HTML​​​网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个​​Java​​类库,是一款程序员可以嵌入他们所开发产品的组件。

2,FreeMarker的基本用法

下面使用测试类来说明​​FreeMarker​​​的基本用法,其实​​FreeMarker​​​就是一个模板引擎,和​​thymeleaf​​​和​​beetle​​​等都是一个品种的,​​FreeMarker​​​最初的目的只是为了渲染​​html​​页面的,不过它可以渲染任何文件,只要你遵循它指定好的语法即可!

(1)FreeMarker依赖

首先创建好一个基本的​​springboot​​​工程,然后导入​​FreeMarker​​依赖,然后就可以开始测试了。

<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>

(2)指定模板并生成

说明:文件路径加载器加载的是存放模板的文件夹的绝对路径,数据模型是​​Map<String,Object>​​​,给里面存放数据即可,​​process​​​方法的参数1是数据模型,参数2是​​Writer​​​对象,执行此方法​​FreeMarker​​会自动渲染页面。

@Test
public void test0() throws Exception {
//1,创建FreeMarker的配置类
Configuration cfg=new Configuration();
//2,指定模板加载器,将模板加入缓存中
//文件路径加载器,获取到templates文件的路径
String templates = this.getClass().getClassLoader().getResource("templates").getPath();
FileTemplateLoader fileTemplateLoader=new FileTemplateLoader(new File(templates));
cfg.setTemplateLoader(fileTemplateLoader);
//3,获取模板
Template template = cfg.getTemplate("test.ftl");
//4,构造数据模型
Map<String,Object> map=new HashMap<String, Object>();
map.put("username","测试人员");
map.put("password",1234);
List<String> list=new ArrayList<>();
list.add("第一个");
list.add("第二个");
map.put("list",list);
//5,文件输出
/**
* 处理模型
* 参数一 数据模型
* 参数二 writer对象(FileWriter(文件输出),printWriter(控制台输出))
*/
// template.process(map,new FileWriter(new File("D:\\a.txt")));
template.process(map,new PrintWriter(System.out));
}

​test.ftl​​模板

<#-- assign指令 在ftl模板中定义数据存入到root节点下 -->
<#assign name="傻子">
<#--然后就可以取出name的值-->
${name}
你好,${username}
<#--- if指令 -->
<#if password=1234>
简单密码
<#elseif password=123456>
一般密码
<#else>
复杂密码
</#if>
<#-- list指令 迭代循环 -->
<#list list as abc>
${abc}
</#list>
<#--模板包含,这样生成模板test.ftl的时候同时会包含模板test2.ftl-->
<#include "test2.ftl">

(3)指定字符串模板并生成

//字符串模板
public static void main(String[] args) throws Exception {
//1,创建配置对象
Configuration cfg=new Configuration();
//2,指定加载器
cfg.setTemplateLoader(new StringTemplateLoader());
//3,创建字符串模板
//字符串
String templateString="欢迎您,${username}";
//通过字符串创建模板
Template template=new Template("templateNames",new StringReader(templateString),cfg);
//4,构造数据
Map<String,Object> map=new HashMap<String, Object>();
map.put("username","测试人员");
map.put("password",1234);
List<String> list=new ArrayList<>();
list.add("第一个");
list.add("第二个");
map.put("list",list);
//5,处理模板
template.process(map,new PrintWriter(System.out));
}

3,测试数据库元数据的操作

(1)获取数据库基本信息

@Test
public void test01() throws Exception{
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC";//没有指定具体哪个数据库,现在获取的是整个连接
String username="root";
String password="1234";
//获取连接
Class.forName(driver);//注册驱动
Connection connection = DriverManager.getConnection(url, username,password);
//获取元数据
DatabaseMetaData metaData = connection.getMetaData();
//获取数据库基本信息
System.out.println(metaData.getUserName());
System.out.println(metaData.supportsTransactions());//是否支持事务
System.out.println(metaData.getDatabaseProductName());//数据库类型(MYSQL)
connection.close();
/**
* 打印结果如下
* root@localhost
* true
* MySQL
*/
}

(2)获取数据库列表

@Test
public void test2() throws Exception{
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC";//没有指定具体哪个数据库,现在获取的是整个连接
String username="root";
String password="1234";
//获取连接
Class.forName(driver);//注册驱动
Connection connection = DriverManager.getConnection(url, username,password);
//获取元数据
DatabaseMetaData metaData = connection.getMetaData();
//获取数据库列表名称
ResultSet resultSet = metaData.getCatalogs();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
resultSet.close();
connection.close();
/**
* 打印连接中的所有数据库名称
* activiti
* dage
* dk
* dk1
* dk_front
* dk_front1
* information_schema
* laji
* light_master
* mysql
* performance_schema
* sakila
* solr
* sys
* test
* workflow
* world
*/
}

(3)获取指定数据库中的表信息

打印出该数据库下的所有表名。

@Test
public void test3() throws Exception{
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://127.0.0.1:3306/laji?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true";//指定了数据库
String username="root";
String password="1234";
//获取连接
Class.forName(driver);//注册驱动
Connection connection = DriverManager.getConnection(url, username,password);
//获取元数据
DatabaseMetaData metaData = connection.getMetaData();
//获取数据库中表信息(mysql可以这样写,oracle会有一点区别)
//参数1:当前操作的数据库 参数2:mysql可为空,oracle填写用户名(要大写) 参数3:null是查询所有表 非空是查询目标表 参数4:类型 table是表,view是视图
ResultSet resultSet = metaData.getTables("laji", null, null, new String[]{"TABLE"});
while (resultSet.next()){
//会打印出该数据库下的所有表名
System.out.println(resultSet.getString("TABLE_NAME"));
}
resultSet.close();
connection.close();
}

(4)获取指定表中的字段信息

打印出指定表的所有字段名。

public static void main(String[] args) throws Exception{
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://127.0.0.1:3306/laji?characterEncoding=utf8&serverTimezone=UTC";//指定了数据库
String username="root";
String password="1234";
//获取连接
Class.forName(driver);//注册驱动
Connection connection = DriverManager.getConnection(url, username,password);
//获取元数据
DatabaseMetaData metaData = connection.getMetaData();
ResultSet city = metaData.getColumns("laji", null, "user", null);
while (city.next()){
//会打印出指定表的所有字段名
System.out.println(city.getString("COLUMN_NAME"));
}
}

(5)测试参数元数据

/**
* 测试参数元数据
* 通过preparedStatement获取
* 目的:获取sql参数中的属性信息
*/
@Test
public void test4() throws Exception{
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://127.0.0.1:3306/laji?characterEncoding=utf8&serverTimezone=UTC";//指定了数据库
String username="root";
String password="1234";
//获取连接
Class.forName(driver);//注册驱动
Connection connection = DriverManager.getConnection(url, username,password);
String sql="select * from user where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,1);//设第一个参数为int 1
//获取参数元数据
ParameterMetaData metaData = preparedStatement.getParameterMetaData();
//得到参数的个数
int count = metaData.getParameterCount();//打印 1 (只有一个id参数)
System.out.println(count);
preparedStatement.close();
connection.close();
}

(6)测试结果集元数据

@Test
public void test5() throws Exception{
String driver="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://127.0.0.1:3306/laji?characterEncoding=utf8&serverTimezone=UTC";//指定了数据库
String username="root";
String password="1234";
//获取连接
Class.forName(driver);//注册驱动
Connection connection = DriverManager.getConnection(url, username,password);
String sql="select * from user where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,1);//设第一个参数为int 1
//查询
ResultSet resultSet = preparedStatement.executeQuery();
//获取结果集元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//获取查询字段个数
int count = metaData.getColumnCount();
for(int i=1;i<=count;i++){
//获取列名
String columnName = metaData.getColumnName(i);//第i个列
//获取字段类型 sql类型 varchar
int columnType = metaData.getColumnType(i);
//获取java类型 String
String columnClassName = metaData.getColumnClassName(i);
System.out.println(columnName+"---"+columnType+"---"+columnClassName);
}
preparedStatement.close();
connection.close();
}
/**
* 打印
* id---4---java.lang.Integer
* account---12---java.lang.String
* password---12---java.lang.String
* islogin---4---java.lang.Integer
*/

二,生成代码实战

1,首先准备四个实体类

​DataBase​​实体

package com.ftx.demo.model;
/**
* @author FanJiangFeng
* @version 1.0.0
* @ClassName DataBase.java
* @Description TODO
* @createTime 2020年04月23日 14:15:00
*/
public class DataBase {
private static String mysqlUrl="jdbc:mysql://[ip]:[port]/[db]?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC";
private static String oracleUrl="jdbc:oracle:thin:@[ip]:[port]:[db]";
private String dbType;//数据库类型
private String userName;
private String passWord;
private String driver;
private String url;
public DataBase(){}
public DataBase(String dbType){
this(dbType,"127.0.0.1","3306","");
}
public DataBase(String dbType,String db){
this(dbType,"127.0.0.1","3306",db);
}
/**
*
* @param dbType 数据库类型 mysql/oracle
* @param ip ip
* @param port 3306
* @param db 数据库名称 test
*/
public DataBase(String dbType,String ip,String port,String db){
this.dbType=dbType;
if("MYSQL".equals(dbType.toUpperCase())){
this.driver="com.mysql.cj.jdbc.Driver";
this.url=mysqlUrl.replace("[ip]",ip).replace("[port]",port).replace("[db]",db);
}else{
this.driver="oracle.jdbc.driver.OracleDriver";
this.url=oracleUrl.replace("[ip]",ip).replace("[port]",port).replace("[db]",db);
}
}
}

​Settings​​实体

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;
}

​Table​​实体

//表实体
public class Table {
private String name;//表名称
private String name2;//处理后的表名称
private String comment;//介绍
private String key;//主键列
private List<Column> columnList;

​Column​​实体

public class Column {
private String columnName;//列名称
private String columnName2;//处理后的列名称
private String columnType;//列类型
private String columnDbType;//列在数据库中的类型
//本工程暂不处理备注和主键
private String columnComment;//列备注id
private String columnKey;//是否是主键

2,自定义配置文件

自定义配置文件中配置数据库和​​Java​​对应的数据类型

springboot集成FreeMarker一键式自动生成增删改查代码_FreeMarker

3,准备三个工具类

​FileUtils​​工具类

该工具类用到的主要是 查询整个目录的文件夹 和 递归获取某个目录下的所有文件夹 的方法

package com.ftx.demo.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//文件处理工具类
public class FileUtils {
//得到相对路径
public static String getRelativePath(File baseDir,File file){
if(baseDir.equals(file)){
return "";
}
if(baseDir.getParentFile()==null){
return file.getAbsolutePath().substring(baseDir.getAbsolutePath().length());
}else{
return file.getAbsolutePath().substring(baseDir.getAbsolutePath().length()+1);
}
}
//查询整个目录下的所有文件
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){
if(dir.isDirectory()){
File[] files = dir.listFiles();
for(int i=0;i<files.length;i++){
searchFiles(files[i],collector);
}
}else{
collector.add(dir);
}
}
//创建文件
public static File mkdir(String dir,String file){
if(dir==null){
throw new IllegalArgumentException("文件夹不许为空");
}
File result=new File(dir,file);
if(result.getParentFile()!=null){
result.getParentFile().mkdirs();
}
return result;
}
}

​PropertiesUtils​​工具类

此工具类说明:静态代码块预加载,将自定义的配置文件​​properties​​的内容全部加载到​​customMap​​中,然后在其他类中调用此类获取​​customMap​​中的键值对(键值对就是字都应以配置文件中所配置的内容)

package com.ftx.demo.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
//需要将自定义的配置信息写入到properties文件中,配置到相对于工程的properties文件下
public class PropertiesUtils {
public static Map<String,String> customMap=new HashMap<>();
//静态块,预加载,将自定义的配置文件properties的内容全部加载到customMap中
static {
File dir=new File("properties");
try {
List<File> files = FileUtils.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();
}
}
//测试预加载是否成功(看是否打印出了properties配置文件的key和value)
public static void main(String[] args) {
for(String key:customMap.keySet()){
System.out.println(key+"---"+customMap.get(key));
}
}
}

​DataBaseUtils​​工具类


方法介绍:
1,获取数据库连接
2,获取数据库列表
3,获取数据库中的所有表和字段并构造实体类
4,根据表名的截取操作生成类名


package com.ftx.demo.util;
import com.ftx.demo.model.Column;
import com.ftx.demo.model.DataBase;
import com.ftx.demo.model.Table;
import org.junit.Test;
import javax.servlet.http.HttpSession;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class DataBaseUtils {
//获取数据库连接
public static Connection getConnection(DataBase db) throws Exception{
//获取连接
Class.forName(db.getDriver());//注册驱动
Connection connection = DriverManager.getConnection(db.getUrl(), db.getUserName(),db.getPassWord());
return connection;
}
//获取数据库列表
public static List<String> getShemas(DataBase db) throws Exception{
Connection connection = getConnection(db);
//获取元数据
DatabaseMetaData metaData = connection.getMetaData();
//获取所有数据库列表
ResultSet resultSet = metaData.getCatalogs();
List<String> list=new ArrayList<>();
while(resultSet.next()){
list.add(resultSet.getString(1));
}
resultSet.close();
connection.close();
return list;
}
//获取数据库中的所有表和字段并构造实体类(相当于一键生成数据库中所有表的增删改查代码)
public static List<Table> getDbInfo(DataBase db, HttpSession session) throws Exception{
//获取连接
Connection connection = getConnection(db);
//获取元数据
DatabaseMetaData metaData = connection.getMetaData();
List<Table> list=new ArrayList<>();
//获取当前数据库的所有表
String dbss=(String) session.getAttribute("db");
ResultSet tables = metaData.getTables(dbss, null, null, new String[]{"TABLE"});
while (tables.next()){
//表名
String table_name = tables.getString("TABLE_NAME");
//构造生成对应实体类的类名
String className = removePrefix(table_name);
//主键
ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, table_name);
//对主键遍历的原因(或许一张表有多个主键)
String keys="";
while (primaryKeys.next()){
String keyName = primaryKeys.getString("COLUMN_NAME");
keys+=keyName+",";
}
Table tab=new Table();
tab.setName(table_name);
tab.setName2(className);
tab.setKey(keys);
//处理表中的所有字段
ResultSet columns = metaData.getColumns(dbss, null, table_name, null);
List<Column> cols=new ArrayList<>();
while (columns.next()){
Column column=new Column();
//列名称
String column_name = columns.getString("COLUMN_NAME");
//java实体的属性名
String attName = column_name;
//java类型和数据库类型
String type_name = columns.getString("TYPE_NAME");
String javaType = PropertiesUtils.customMap.get(type_name);
column.setColumnName(column_name);
column.setColumnName2(attName);
column.setColumnDbType(type_name);
column.setColumnType(javaType);
cols.add(column);
}
tab.setColumnList(cols);
list.add(tab);
//关闭连接,释放资源
columns.close();
primaryKeys.close();
}
tables.close();
connection.close();
return list;
}
//根据表名的截取操作生成类名
public static String removePrefix(String tableName){
//从自定义的配置文件中拿到前缀的配置
String prefixes = PropertiesUtils.customMap.get("tableRemovePrefixes");
//这里就不字符串处理了,直接把表名当类名用了
String replace = tableName;
return replace;
}
// 测试 获取数据库中的所有表和字段并构造实体类 的方法是否可用
// public static void main(String[] args) throws Exception {
// DataBase db=new DataBase("MYSQL","laji");
// db.setUserName("root");
// db.setPassWord("1234");
// List<Table> dbInfo = DataBaseUtils.getDbInfo(db);
// for(Table table:dbInfo){
// System.out.println(dbInfo);
// }
// }
}

4,准备生成代码的模板

在​​resources​​静态资源中添加生成代码的​​FreeMarker​​模板,如下图

springboot集成FreeMarker一键式自动生成增删改查代码_代码自动生成_02

package ${pPackage}.dao;
import java.util.List;
import java.util.Map;
public interface ${table.name?cap_first}Dao {
/**
* 查询
*/
List<Map> get${table.name?cap_first}List();
/**
* 添加
*/
int add${table.name?cap_first}(Map map);
/**
* 待修改
*/
Map toEdit${table.name?cap_first}(${table.columnList[0].columnType} ${table.columnList[0].columnName});
/**
* 修改
*/
int update${table.name?cap_first}(Map map);
/**
* 删除
*/
int delete${table.name?cap_first}(${table.columnList[0].columnType} ${table.columnList[0].columnName});
}
package ${pPackage}.model;
public class ${table.name?cap_first} { //?cap_first freemarker内置函数 首字母大写
<#list table.columnList as abc>
private ${abc.columnType} ${abc.columnName};
</#list>
<#list table.columnList as abc>
public ${abc.columnType} get${abc.columnName?cap_first}() {
return this.${abc.columnName};
}
public void set${abc.columnName?cap_first}(String ${abc.columnName}) {
this.${abc.columnName} = ${abc.columnName};
}
</#list>
}

5,html页面(web页面进行生成)

​index.html​​首页

<table style="height: 100%;width: 100%;" border="3">
<form action="">
<tr>
<td>数据库类型</td>
<td><select name="dbKind" id="dbKind">
<option value="MYSQL">MYSQL</option>
<option value="ORACLE">ORACLE</option>
</select></td>
</tr>
<tr>
<td>数据库IP</td>
<td><input id="ip" type="text" name="ip" required><span style="color: red;">*必填项</span></td>
</tr>
<tr>
<td>端口号</td>
<td><input id="port" type="text" name="port" required><span style="color: red;">*必填项</span></td>
</tr>
<tr>
<td>用户名</td>
<td><input id="username" type="text" name="username" required><span style="color: red;">*必填项</span></td>
</tr>
<tr>
<td>密码</td>
<td><input id="password" type="password" name="password" required><span style="color: red;">*必填项</span></td>
</tr>
<tr>
<td><input id="test" type="button" value="测试连接"><span style="color: red;">*必选项</span></td>
<td>选择数据库 <select name="databases" id="databases">
</select></td>
<td><input id="next" type="button" value="下一步"></td>
</tr>
</form>
</table>
<script th:src="@{../jquery.min.js}"></script>
<script>
$("#test").click(function () {
$.post(
"/testConnection",
{dbKind:$("#dbKind").val(),ip:$("#ip").val(),port:$("#port").val(),username:$("#username").val(),password:$("#password").val()},
function (data) {
if(data.length>0){
alert("连接成功");
var temp="";
for(var i=0;i<data.length;i++){
temp+='<option value="'+data[i]+'">'+data[i]+'</option>';
}
$("#databases").html(temp);
}else{
alert("连接失败");
}
},
"json"
);
});
$("#next").click(function () {
location.href="/enter1?databases="+$("#databases").val()+"&username="+$("#username").val()+"&password="+$("#password").val();
});
</script>

​index1.html​​下一步的页面

<table style="height: 100%;width: 100%;" border="3">
<form action="/create" method="post">
<tr>
<td>模板</td>
<td><select name="dbKind" id="dbKind">
<option value="springboot程序的模板">springboot程序的模板</option>
</select></td>
</tr>
<tr>
<td>代码生成路径</td>
<td><input id="fileUrl" type="text" name="fileUrl" /><span style="color: red;">*必填项</span></td>
</tr>
<tr>
<td>项目名</td>
<td><input id="projectEngName" type="text" name="projectEngName" required></td>
</tr>
<tr>
<td>包名</td>
<td><input id="packagename" type="text" name="packagename" required><span style="color: red;">*必填项(格式必须为com.ftx.demo这种)</span></td>
</tr>
<tr>
<td>项目中文名称</td>
<td><input id="projectChinaName" type="text" name="projectChinaName" required></td>
</tr>
<tr>
<td>数据库</td>
<td><input id="db" type="text" name="db" th:value="*{databases}" readonly></td>
</tr>
<tr>
<td>用户名</td>
<td><input id="root" type="text" name="root" th:value="*{username}" readonly></td>
</tr>
<tr>
<td>密码</td>
<td><input id="psd" type="text" name="psd" th:value="*{password}" readonly></td>
</tr>
<tr>
<td><input id="test" type="submit" value="生成代码"></td>
<td><input type="button" value="关闭"></td>
</tr>
</form>
</table>

​result.html​​反馈结果页面

<h3 style="text-align:center;color: blue;" th:text="${success}"></h3>

6,controller类

package com.ftx.demo.controller;
import com.ftx.demo.core.GeneratorFacade;
import com.ftx.demo.model.DataBase;
import com.ftx.demo.model.Settings;
import com.ftx.demo.util.DataBaseUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
@Controller
public class GeneratorController {
@RequestMapping("/enter")
public String index(){
return "index.html";
}
@RequestMapping("/enter1")
public String index1(String databases, String username, String password, Model model){
model.addAttribute("databases",databases);
model.addAttribute("username",username);
model.addAttribute("password",password);
return "index1.html";
}
//点击测试连接按钮,填充选择数据库下拉框
@RequestMapping("/testConnection")
@ResponseBody
public List<String> testConnection(String dbKind,String ip,String port,String username,String password) throws Exception{
DataBase dataBase=new DataBase(dbKind,ip,port,"");
dataBase.setUserName(username);
dataBase.setPassWord(password);
List<String> dbs = DataBaseUtils.getShemas(dataBase);
if(dbs.size()>0){
return dbs;
}else{
return null;
}
}
//生成代码
/**
* 确认生成按钮传来的参数
* @param dbKind 选择模板,这里没啥用,因为已经写死模板位置了 resources/springboot程序的模板 文件夹
* @param fileUrl 代码生成路径
* @param projectEngName 项目英文名称 没啥用
* @param packagename 包名 com.ftx.demo
* @param projectChinaName 项目中文名称,也没啥用
* @param db 选择的数据库名
* @param root 数据库的用户名root
* @param psd 数据库的密码
* @throws Exception
*
* 下面用sesson的目的很简单,不要想多了,是存了数据库名,到后来的方法要取出来,我这是把它当成全局变量使用了
*/
@RequestMapping("/create")
public String create(String dbKind, String fileUrl, String projectEngName, String packagename, String projectChinaName,
String db, String root, String psd, HttpSession session,Model model) throws Exception{
Settings settings=new Settings();
//包名
settings.setpPackage(packagename);//com.ftx.demo
String[] split = packagename.split("\\.");//split(".")无法分割字符串,必须加上\\
settings.setPath1(split[0]);//com
settings.setPath2(split[1]);//ftx
settings.setPath3(split[2]);//demo
//项目名(没啥用)
settings.setProject(projectEngName);
//默认只支持mysql数据库吧,oracle暂时先不处理,先写死为mysql
session.setAttribute("db",db);
DataBase dbs=new DataBase("MYSQL",db);
dbs.setUserName(root);
dbs.setPassWord(psd);
GeneratorFacade generatorFacade=new GeneratorFacade(dbKind,fileUrl,settings,dbs);
boolean b = generatorFacade.generatorByDataBase(session);
if(b){
model.addAttribute("success","代码已生成,回文件查看!");
return "result";
}else{
model.addAttribute("success","代码生成失败!");
return "result";
}
}
}

7,核心类Generator和GeneratorFacade

​Generator​​类

代码生成器的核心处理类,使用​​FreeMarker​​完成文件生成,数据模型+模板

package com.ftx.demo.core;
import com.ftx.demo.util.FileUtils;
import freemarker.cache.FileTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.File;
import java.io.FileWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
//代码生成器的核心处理类,使用FreeMarker完成文件生成,数据模型+模板
//数据:数据模型 模板的位置 生成文件的路径
public class Generator {
private String templatePath;//模板路径
private String outPath;//代码生成路径
private Configuration cfg;
public Generator(String templatePath, String outPath)throws Exception {
this.outPath = outPath;
//实例化Configuration对象
cfg=new Configuration();
//指定模板加载器
//在代码中动态加载jar、资源文件的时候,首先应该是使用Thread.currentThread().getContextClassLoader()。
// 如果你使用Test.class.getClassLoader(),可能会导致和当前线程所运行的类加载器不一致(因为Java天生的多线程)
String templates = Thread.currentThread().getContextClassLoader().getResource("").getPath()+"\\springboot程序的模板\\";
this.templatePath = templates;
FileTemplateLoader fileTemplateLoader=new FileTemplateLoader(new File(templates));
cfg.setTemplateLoader(fileTemplateLoader);
}
/**
* 代码生成
* 1,扫描模板路径下的所有模板
* 2,对每个模板进行文件生成(数据模板)
* 3,参数:数据模板
*/
public void scanAndGenerator(Map<String,Object> dataModel)throws Exception{
//根据模板路径找到此路径下的所有模板文件
List<File> fileList = FileUtils.searchAllFile(new File(templatePath));
//对每个模板进行文件生成
for(File file:fileList){
//参数1:数据模型 参数2:文件模板
excuteGenerator(dataModel,file);
}
}
//对模板进行文件生成
//参数1:数据模型 参数2:文件模板
private void excuteGenerator(Map<String, Object> dataModel, File file) throws Exception {
//1,文件路径处理
/**
* file:D:/模板存在的文件夹/${path1}/${path2}/${path3}/${classname}.java 绝对路径
* replace的目的:得到文件名,文件名之前的路径都不要了,只留包名之后的内容
*/
// 得到模板文件的这样的路径 ${path1}/${path2}/${path3}/${className}.java
String one=file.getAbsolutePath();
int i = one.indexOf("$");
String templateFileName = one.substring(i);
//把${path1}/${path2}/${path3}/${className}.java 替换成 com/ftx/demoUser.java (数据模型中的内容)
String outFileName = processString(templateFileName, dataModel);
//2,读取文件模板
//上面把模板整个文件夹都加载到了模板加载器,所以这里拿模板只需要传入该文件夹下的文件名即可
Template template = cfg.getTemplate(templateFileName);//相对路径 ${path1}/${path2}/${path3}/${classname}.java
template.setOutputEncoding("utf-8");//指定生成文件的字符集编码
//3,创建文件
File file1 = FileUtils.mkdir(this.outPath, outFileName);
//4,模板处理(文件生成)
FileWriter fileWriter=new FileWriter(file1);
System.err.println(dataModel);
template.process(dataModel,fileWriter);
fileWriter.close();
}
/**
* 把${path1}/${path2}/${path3}/${className}.java 替换成 com/ftx/demoUser.java (数据模型中的内容)
* FreeMarker的字符串模板 替换
*/
public String processString(String templateString,Map dataModel) throws Exception{
StringWriter stringWriter=new StringWriter();
Template template=new Template("ts",new StringReader(templateString),cfg);
template.process(dataModel,stringWriter);
return stringWriter.toString();
}
/**
* 测试代码生成主类scanAndGenerator是否管用
*/
public static void main(String[] args) throws Exception {
String templatePath="D:\\工作\\学习资料\\FreeMarker\\模板";
String outPath="D:\\工作\\学习资料\\FreeMarker\\生成路径";
Generator generator=new Generator(templatePath,outPath);
Map<String,Object> dataModel=new HashMap<>();
dataModel.put("username","张三");
generator.scanAndGenerator(dataModel);
}
}

​GeneratorFacade​​类

准备数据模型,调用核心处理类​​Generator​​类完成代码生成工作

package com.ftx.demo.core;
import com.ftx.demo.model.DataBase;
import com.ftx.demo.model.Settings;
import com.ftx.demo.model.Table;
import com.ftx.demo.util.DataBaseUtils;
import com.ftx.demo.util.PropertiesUtils;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GeneratorFacade {
//模板位置
private String templatePath;
//代码生成路径
private String outPath;
//工程配置对象
private Settings settings;
//数据库对象
private DataBase db;
private Generator generator;
public GeneratorFacade(String templatePath, String outPath, Settings settings, DataBase db) throws Exception {
this.templatePath = templatePath;
this.outPath = outPath;
this.settings = settings;
this.db = db;
generator=new Generator(templatePath,outPath);
}
/**
* 准备数据模型
* 调用核心处理类完成代码生成工作
*/
public boolean generatorByDataBase(HttpSession session) throws Exception{
List<Table> tables = DataBaseUtils.getDbInfo(db,session);
for(Table table:tables){
//对每个table进行代码生成
Map<String, Object> dataModel = getDataModel(table);
/**
* 得到的数据模型如下
* {NUMBER=Long, CHAR=String, project=test, BIGINT=Long, TEXT=String, className=r, VARCHAR2=String,
* INT=Integer, NVARCHAR2=String, DATE=java.util.Date, DATETIME=java.util.Date, path1=com, path2=ftx,
* path3=demo, pPackage=com.ftx.demo, VARCHAR=String, testKey=testValue, DOUBLE=Double, tableRemovePrefixes="tb_,co_",
* table=Table{name='user', name2='r', comment='null', key='userid,userno,id,Host,User,id,',
* columnList=[Column{columnName='id', columnName2='id', columnType='Integer', columnDbType='INT', columnComment='null',
* columnKey='null'}, Column{columnName='account', columnName2='account', columnType='String', columnDbType='VARCHAR',
* columnComment='null', columnKey='null'}, Column{columnName='password', columnName2='password', columnType='String',
* columnDbType='VARCHAR', columnComment='null', columnKey='null'}, Column{columnName='islogin', columnName2='islogin',
* columnType='Integer', columnDbType='INT', columnComment='null', columnKey='null'}]}}
*/
//调用代码生成方法,把数据模型传过去,进行生成
generator.scanAndGenerator(dataModel);
}
return true;
}
/**
* 根据table对象获取数据模型
* @param table
* @return
*/
private Map<String,Object> getDataModel(Table table) {
Map<String,Object> map=new HashMap<>();
//自定义配置
map.putAll(PropertiesUtils.customMap);
//元数据
map.put("table",table);
//settings
map.put("project",this.settings.getProject());
map.put("pPackage",this.settings.getpPackage());
map.put("path1",this.settings.getPath1());
map.put("path2",this.settings.getPath2());
map.put("path3",this.settings.getPath3());
//类名
map.put("className",table.getName2());
return map;
}
}

8,运行项目

springboot集成FreeMarker一键式自动生成增删改查代码_java_03

springboot集成FreeMarker一键式自动生成增删改查代码_mysql_04

springboot集成FreeMarker一键式自动生成增删改查代码_mysql_05

springboot集成FreeMarker一键式自动生成增删改查代码_代码自动生成_06

springboot集成FreeMarker一键式自动生成增删改查代码_FreeMarker_07

springboot集成FreeMarker一键式自动生成增删改查代码_mysql_08

springboot集成FreeMarker一键式自动生成增删改查代码_mysql_09

springboot集成FreeMarker一键式自动生成增删改查代码_FreeMarker_10

工程说明

该​​demo​​​工程启动之后,测试连接成功之后选择生成的数据库,然后会生成该数据库中所有表的增删改查,暂时本工程只支持自动生成​​model​​​实体类和​​dao​​​接口,要其他的话你只需要增加​​FreeMarker​​模板即可!

作者:​​樊同学​