springboot整合freemarker代碼自動生成器
手擼一個代碼自動生成器!!
實現功能:MyBatis 逆向工程
技術架構
頁面是用 Vue ,element-ui開發;網絡請求是 Axios。
服務端是 Spring Boot
頁面模版是 Freemarker:
開發步驟:
一、創建工程
二、數據庫連接操作
1.所需包結構
2.在model包中創建Db類
作用:用於接受前端傳來數據庫連接相關的值(username,password,url)
package com.example.generate_code.model; /** * @author: 王澤 */ public class Db { private String username; private String password; private String url; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
3.在model中創建RespBean類
自定義響應類,返回數據更方便
package com.example.generate_code.model; /** * @author: 王澤 */ public class RespBean { private Integer status; private String msg; private Object obj; public static RespBean ok(String msg,Object obj) { return new RespBean(200, msg, obj); } public static RespBean ok(String msg) { return new RespBean(200, msg, null); } public static RespBean error(String msg,Object obj) { return new RespBean(500, msg, obj); } public static RespBean error(String msg) { return new RespBean(500, msg, null); } private RespBean() { } private RespBean(Integer status, String msg, Object obj) { this.status = status; this.msg = msg; this.obj = obj; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } }
4.在Utils中創建DBUtils
JDBC連接工具類
public class DbUtils { private static Connection connection; public static Connection getConnection() { return connection; } public static Connection initDb(Db db) { if (connection == null) { try { Class.forName("com.mysql.cj.jdbc.Driver"); connection = DriverManager.getConnection(db.getUrl(), db.getUsername(), db.getPassword()); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } } return connection; } }
5.寫一個連接接口DbController
連接數據庫
@RestController public class DbController { @PostMapping("/connect") public RespBean connect(@RequestBody Db db) { Connection con = DBUtils.initDb(db); if (con != null) { return RespBean.ok("數據庫連接成功"); } return RespBean.error("數據庫連接失敗"); } }
6.創建index頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>代碼生成工具</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 引入樣式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入組件庫 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> <table> <tr> <td> <el-tag size="mini">數據庫用戶名:</el-tag> </td> <td> <el-input size="mini" v-model="db.username"></el-input> </td> </tr> <tr> <td> <el-tag size="mini">數據庫密碼:</el-tag> </td> <td> <el-input size="mini" v-model="db.password"></el-input> </td> </tr> <tr> <td> <el-tag size="mini">數據庫連接地址:</el-tag> </td> <td> <el-input size="mini" v-model="db.url"> <template slot="prepend">jdbc:mysql://</template> <template slot="append"> ?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai </template> </el-input> </td> </tr> </table> <div style="display: flex"> <el-button type="primary" size="mini" @click="connect" :disabled="!connectBtnEnabled">連接數據庫</el-button> <div style="color: #ff018d;font-weight: bold">[{{msg}}]</div> <el-input v-model="packageName" size="mini" style="width: 300px"></el-input> <el-button type="primary" size="mini" @click="config">配置</el-button> </div> </div> <script> new Vue({ el: "#app", data: function () { return { packageName: '', msg: '數據庫未連接', connectBtnEnabled: true, db: { username: "root", password: "123456", url: "localhost:3306/" } } }, methods: { connect() { let _this = this; this.db.url = "jdbc:mysql://" + this.db.url + "?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"; axios.post('/connect', this.db) .then(function (response) { _this.msg = response.data.msg; _this.db = { username: "root", password: "123456", url: "localhost:3306/" } _this.connectBtnEnabled = false; }) .catch(function (error) { console.log(error); }); } } }) </script> </body> </html>
三、加載數據表信息
1.服務器端編寫
ColumnClass 用來描述表中的每一列
package com.example.generate_code.model; /** * @author: 王澤 */ public class ColumnClass { private String propertyName; //對應java屬性的名字 private String columnName; //數據庫中的名字 private String type; //字段類型 private String remark; //備註 private Boolean isPrimary; //字段是不是一個主鍵 @Override public String toString() { return "ColumnClass{" + "propertyName='" + propertyName + '\'' + ", columnName='" + columnName + '\'' + ", type='" + type + '\'' + ", remark='" + remark + '\'' + ", isPrimary=" + isPrimary + '}'; } public String getPropertyName() { return propertyName; } public void setPropertyName(String propertyName) { this.propertyName = propertyName; } public String getColumnName() { return columnName; } public void setColumnName(String columnName) { this.columnName = columnName; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public Boolean getPrimary() { return isPrimary; } public void setPrimary(Boolean primary) { isPrimary = primary; } }
描述一個具體的表的信息 TableClass
package com.example.generate_code.model; import java.util.List; /** * @author: 王澤 */ public class TableClass { private String tableName; //表名 ,以下是生成的名字 private String modelName; private String serviceName; private String mapperName; private String controllerName; private String packageName; private List<ColumnClass> columns; // 字段 @Override public String toString() { return "TableClass{" + "tableName='" + tableName + '\'' + ", modelName='" + modelName + '\'' + ", serviceName='" + serviceName + '\'' + ", mapperName='" + mapperName + '\'' + ", controllerName='" + controllerName + '\'' + ", packageName='" + packageName + '\'' + ", columns=" + columns + '}'; } public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getModelName() { return modelName; } public void setModelName(String modelName) { this.modelName = modelName; } public String getServiceName() { return serviceName; } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public String getMapperName() { return mapperName; } public void setMapperName(String mapperName) { this.mapperName = mapperName; } public String getControllerName() { return controllerName; } public void setControllerName(String controllerName) { this.controllerName = controllerName; } public String getPackageName() { return packageName; } public void setPackageName(String packageName) { this.packageName = packageName; } public List<ColumnClass> getColumns() { return columns; } public void setColumns(List<ColumnClass> columns) { this.columns = columns; } }
創建配置接口Controller
用map來接受前端傳來的數據
用到瞭谷歌提供的工具包guava,需要導入依賴
@PostMapping("/config") public RespBean config(@RequestBody Map<String, String> map) { String packageName = map.get("packageName"); try { Connection connection = DBUtils.getConnection(); DatabaseMetaData metaData = connection.getMetaData(); ResultSet tables = metaData.getTables(connection.getCatalog(), null, null, null); List<TableClass> tableClassList = new ArrayList<>(); while (tables.next()) { TableClass tableClass = new TableClass(); tableClass.setPackageName(packageName); String table_name = tables.getString("TABLE_NAME"); String modelName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, table_name); tableClass.setTableName(table_name); tableClass.setModelName(modelName); tableClass.setControllerName(modelName + "Controller"); tableClass.setMapperName(modelName + "Mapper"); tableClass.setServiceName(modelName+"Service"); tableClassList.add(tableClass); } return RespBean.ok("數據庫信息讀取成功", tableClassList); } catch (Exception e) { e.printStackTrace(); } return RespBean.error("數據庫信息讀取失敗"); }
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency>
2.完善index頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>代碼生成工具</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 引入樣式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- 引入組件庫 --> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> </head> <body> <div id="app"> <table> <tr> <td> <el-tag size="mini">數據庫用戶名:</el-tag> </td> <td> <el-input size="mini" v-model="db.username"></el-input> </td> </tr> <tr> <td> <el-tag size="mini">數據庫密碼:</el-tag> </td> <td> <el-input size="mini" v-model="db.password"></el-input> </td> </tr> <tr> <td> <el-tag size="mini">數據庫連接地址:</el-tag> </td> <td> <el-input size="mini" v-model="db.url"> <template slot="prepend">jdbc:mysql://</template> <template slot="append"> ?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai </template> </el-input> </td> </tr> </table> <div style="display: flex"> <el-button type="primary" size="mini" @click="connect" :disabled="!connectBtnEnabled">連接數據庫</el-button> <div style="color: #ff018d;font-weight: bold">[{{msg}}]</div> <el-input v-model="packageName" size="mini" style="width: 300px"></el-input> <el-button type="primary" size="mini" @click="config">配置</el-button> </div> <el-table :data="tableData" stripe border style="width: 100%"> <el-table-column prop="tableName" label="表名稱" width="180"> </el-table-column> <el-table-column label="實體類名稱" width="180"> <template slot-scope="scope"> <el-input v-model="scope.row.modelName"></el-input> </template> </el-table-column> <el-table-column label="Mapper名稱"> <template slot-scope="scope"> <el-input v-model="scope.row.mapperName"></el-input> </template> </el-table-column> <el-table-column label="Service名稱"> <template slot-scope="scope"> <el-input v-model="scope.row.serviceName"></el-input> </template> </el-table-column> <el-table-column label="Controller名稱"> <template slot-scope="scope"> <el-input v-model="scope.row.controllerName"></el-input> </template> </el-table-column> </el-table> <div> <el-button @click="generateCode" type="success">生成代碼</el-button> <div style="color: #ff0114;font-weight: bold">*{{result}}*</div> <div>{{codePath}}</div> </div> </div> <script> new Vue({ el: "#app", data: function () { return { tableData: [], packageName: 'com.wangze.test', msg: '數據庫未連接', connectBtnEnabled: true, db: { username: "root", password: "123456", url: "localhost:3306/" } } }, methods: { generateCode() { let _this = this; axios.post('/generateCode', this.tableData) .then(function (response) { _this.result = response.data.msg; _this.codePath = response.data.obj; }) .catch(function (error) { }); }, config() { let _this = this; axios.post('/config', {packageName: this.packageName}) .then(function (response) { _this.msg = response.data.msg; _this.tableData = response.data.obj; }) .catch(function (error) { console.log(error); }); }, connect() { let _this = this; this.db.url = "jdbc:mysql://" + this.db.url + "?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"; axios.post('/connect', this.db) .then(function (response) { _this.msg = response.data.msg; _this.db = { username: "root", password: "123456", url: "localhost:3306/" } _this.connectBtnEnabled = false; }) .catch(function (error) { console.log(error); }); } } }) </script> </body> </html>
四、代碼生成
1.創建模板 Model.java.ftl
package ${packageName}.model; import java.util.Date; public class ${modelName}{ <#if columns??> <#list columns as column> <#if column.type='VARCHAR'||column.type='TEXT'||column.type='CHAR'> /** * ${column.remark} */ private String ${column.propertyName?uncap_first}; </#if> <#if column.type='INT'> /** * ${column.remark} */ private Integer ${column.propertyName?uncap_first}; </#if> <#if column.type='DATETIME'> /** * ${column.remark} */ private Date ${column.propertyName?uncap_first}; </#if> <#if column.type='BIGINT'> /** * ${column.remark} */ private Long ${column.propertyName?uncap_first}; </#if> <#if column.type='DOUBLE'> /** * ${column.remark} */ private Double ${column.propertyName?uncap_first}; </#if> <#if column.type='BIT'> /** * ${column.remark} */ private Boolean ${column.propertyName?uncap_first}; </#if> </#list> </#if> <#if columns??> <#list columns as column> <#if column.type='VARCHAR'||column.type='TEXT'||column.type='CHAR'> public String get${column.propertyName}(){ return ${column.propertyName?uncap_first}; } public void set${column.propertyName}(String ${column.propertyName?uncap_first}){ this.${column.propertyName?uncap_first}=${column.propertyName?uncap_first}; } </#if> <#if column.type='INT'> public Integer get${column.propertyName}(){ return ${column.propertyName?uncap_first}; } public void set${column.propertyName}(Integer ${column.propertyName?uncap_first}){ this.${column.propertyName?uncap_first}=${column.propertyName?uncap_first}; } </#if> <#if column.type='DATETIME'> public Date get${column.propertyName}(){ return ${column.propertyName?uncap_first}; } public void set${column.propertyName}(Date ${column.propertyName?uncap_first}){ this.${column.propertyName?uncap_first}=${column.propertyName?uncap_first}; } </#if> <#if column.type='BIGINT'> public Long get${column.propertyName}(){ return ${column.propertyName?uncap_first}; } public void set${column.propertyName}(Long ${column.propertyName?uncap_first}){ this.${column.propertyName?uncap_first}=${column.propertyName?uncap_first}; } </#if> <#if column.type='DOUBLE'> public Double get${column.propertyName}(){ return ${column.propertyName?uncap_first}; } public void set${column.propertyName}(Double ${column.propertyName?uncap_first}){ this.${column.propertyName?uncap_first}=${column.propertyName?uncap_first}; } </#if> <#if column.type='BIT'> public Boolean get${column.propertyName}(){ return ${column.propertyName?uncap_first}; } public void set${column.propertyName}(Boolean ${column.propertyName?uncap_first}){ this.${column.propertyName?uncap_first}=${column.propertyName?uncap_first}; } </#if> </#list> </#if> }
Service.java.ftl
package ${packageName}.service; import ${packageName}.model.${modelName}; import ${packageName}.mapper.${mapperName}; import org.springframework.stereotype.Service; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; @Service public class ${serviceName}{ @Autowired ${mapperName} ${mapperName?uncap_first}; public List<${modelName}> getAll${modelName}s(){ return ${mapperName?uncap_first}.getAll${modelName}s(); } }
Controller.java.ftl
package ${packageName}.controller; import ${packageName}.model.${modelName}; import ${packageName}.service.${serviceName}; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; import java.util.List; @RestController public class ${controllerName}{ @Autowired ${serviceName} ${serviceName?uncap_first}; @GetMapping("/${modelName?lower_case}s") public List<${modelName}> getAll${modelName}s(){ return ${serviceName?uncap_first}.getAll${modelName}s(); } }
Mapper.java.ftl
package ${packageName}.mapper; import ${packageName}.model.${modelName}; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface ${mapperName}{ List<${modelName}> getAll${modelName}s(); }
Mapper.xml.ftl
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="${packageName}.mapper.${mapperName}"> <resultMap id="BaseResultMap" type="${packageName}.model.${modelName}"> <#list columns as column> <<#if column.primary??>id<#else>result</#if> column="${column.columnName}" property="${column.propertyName?uncap_first}" jdbcType="<#if column.type='INT'>INTEGER<#elseif column.type='DATETIME'>TIMESTAMP<#elseif column.type='TEXT'>VARCHAR<#else>${column.type}</#if>" /> </#list> </resultMap> <select id="getAll${modelName}s" resultMap="BaseResultMap"> select * from ${tableName}; </select> </mapper>
2.代碼生成Controller
package com.example.generate_code.controller; import com.example.generate_code.model.RespBean; import com.example.generate_code.model.TableClass; import com.example.generate_code.service.GenerateCodeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.util.List; @RestController public class GenerateCodeController { @Autowired GenerateCodeService generateCodeService; @PostMapping("/generateCode") public RespBean generateCode(@RequestBody List<TableClass> tableClassList, HttpServletRequest req) { return generateCodeService.generateCode(tableClassList, req.getServletContext().getRealPath("/")); } }
3.編寫service
package com.example.generate_code.service; import com.example.generate_code.model.ColumnClass; import com.example.generate_code.model.RespBean; import com.example.generate_code.model.TableClass; import com.example.generate_code.utils.DBUtils; import com.google.common.base.CaseFormat; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; @Service public class GenerateCodeService { Configuration cfg = null; { cfg = new Configuration(Configuration.VERSION_2_3_30); cfg.setTemplateLoader(new ClassTemplateLoader(GenerateCodeService.class, "/templates")); cfg.setDefaultEncoding("UTF-8"); } public RespBean generateCode(List<TableClass> tableClassList, String realPath) { try { Template modelTemplate = cfg.getTemplate("Model.java.ftl"); Template mapperJavaTemplate = cfg.getTemplate("Mapper.java.ftl"); Template mapperXmlTemplate = cfg.getTemplate("Mapper.xml.ftl"); Template serviceTemplate = cfg.getTemplate("Service.java.ftl"); Template controllerTemplate = cfg.getTemplate("Controller.java.ftl"); Connection connection = DBUtils.getConnection(); DatabaseMetaData metaData = connection.getMetaData(); for (TableClass tableClass : tableClassList) { ResultSet columns = metaData.getColumns(connection.getCatalog(), null, tableClass.getTableName(), null); ResultSet primaryKeys = metaData.getPrimaryKeys(connection.getCatalog(), null, tableClass.getTableName()); List<ColumnClass> columnClassList = new ArrayList<>(); while (columns.next()) { String column_name = columns.getString("COLUMN_NAME"); String type_name = columns.getString("TYPE_NAME"); String remarks = columns.getString("REMARKS"); ColumnClass columnClass = new ColumnClass(); columnClass.setRemark(remarks); columnClass.setColumnName(column_name); columnClass.setType(type_name); columnClass.setPropertyName(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, column_name)); primaryKeys.first(); while (primaryKeys.next()) { String pkName = primaryKeys.getString("COLUMN_NAME"); if (column_name.equals(pkName)) { columnClass.setPrimary(true); } } columnClassList.add(columnClass); } tableClass.setColumns(columnClassList); String path = realPath + "/" + tableClass.getPackageName().replace(".", "/"); generate(modelTemplate, tableClass, path + "/model/"); generate(mapperJavaTemplate, tableClass, path + "/mapper/"); generate(mapperXmlTemplate, tableClass, path + "/mapper/"); generate(serviceTemplate, tableClass, path + "/service/"); generate(controllerTemplate, tableClass, path + "/controller/"); } return RespBean.ok("代碼已生成", realPath); } catch (Exception e) { e.printStackTrace(); } return RespBean.error("代碼生成失敗"); } private void generate(Template template, TableClass tableClass, String path) throws IOException, TemplateException { File folder = new File(path); if (!folder.exists()) { folder.mkdirs(); } String fileName = path + "/" + tableClass.getModelName() + template.getName().replace(".ftl", "").replace("Model", ""); FileOutputStream fos = new FileOutputStream(fileName); OutputStreamWriter out = new OutputStreamWriter(fos); template.process(tableClass,out); fos.close(); out.close(); } }
五、測試
這時候已經找到瞭,我們來驗證一下效果!
修改寫配置
spring.datasource.name=root spring.datasource.password=123456 spring.datasource.url=jdbc:mysql://localhost:3306/boot_crm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
pom.xml
<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources>
導入生成的代碼
運行測試
一個基本的mybatis逆向工程就完成瞭!
最後附上項目源代碼:Gitee
到此這篇關於springboot整合freemarker代碼自動生成器的文章就介紹到這瞭,更多相關springboot 代碼自動生成器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Spring的DI依賴註入詳解
- Java8 使用流抽取List<T>集合中T的某個屬性操作
- JPA如何設置表名和實體名,表字段與實體字段的對應
- SpringBoot+Vue+Flowable模擬實現請假審批流程
- mybatis mapper互相引用resultMap啟動出錯的解決