構建支持多種數據庫類型的代碼自動生成工具
背景:
一般的業務代碼中寫來寫去,無外乎是先建好model,然後針對這個model做些CRUD的操作。(主要針對單表的業務操作)針對於數據庫dao、mapper等的代碼自動生成已經有了mybatisGenerator這種工具,但是針對於controller、service這些我們現在的接口api一般遵循的是restful風格,因此這些也是有規則可循的。舉例有個goodsInfo 的model,針對於他的操作,肯定有 單個查詢、list查詢、修改、刪除等。而這些代碼沒必要複製粘貼來一遍,完全可以由工具自動生成,若有特殊業務場景重寫即可。本工具就算解決這類問題的。
效果截圖
運行生成示例結果:
表選擇界面:
思路:
代碼自動生成說起來很神祕,其實無外乎兩個方面:
- 從數據庫拿到需要自動生成的代碼對應表。
- 從表結構、字段名生成對應的mapper、model、及controller、service等
如何拿到需要自動生成的代碼對應表
sqlservr、mysql、oracle等這些主流數據庫中都存在系統表結構的表,存儲的是所有用戶自己建立表的名稱、字段等,所以直接查詢這些系統表即可羅列出所有業務表。然後做個可視化界面供用戶選擇即可。(這裏做一下更新,我實際項目中沒有用sql查詢的方式,因爲不同數據庫對於系統表的存儲方式各不相同,查詢語句寫的太蛋疼了,實際採用的是 conn.getMetaData() 的方式,採用元數據來拿到指定數據庫中各種表結構信息)
如何自動生成代碼
有了表結構、字段名等如何自動生成代碼呢,這個時候就需要模板引擎了。簡單來講可以理解爲把固定的地方寫死,變化的地方按照規則替換。
可以用我們小時候寫作文的例子來說明。我們(作文厲害的請自動忽略 “們” 😃)小時候寫作文,一般是3段式,開頭、結尾、和中間流水賬。 開頭一般是描寫環境心情、中間講述具體故事,結尾總結讚美。
今天天氣不錯,風和日麗的,我們早早就來到了學些,大家都很開心。(開頭)
小明,突然在地上撿到了一個錢包……(一頓思想鬥爭,最後交給了警察叔叔)
最後,這個故事告訴了我們……(結尾)
從上面的示例範文中是不是很熟悉,。基本上都是這個結構,中間基本可以隨意替換,最後都能湊成一篇基本合格的小學作文。而我們現在要做的就是把一些表名稱、字段名稱當做需要填充的內容填充到指定的代碼段中去。
具體實現
獲取數據庫表、字段等信息
好了,上面講了一大堆廢話(背景和思路,個人覺得還是有必要的),下面到具體實現中來。
獲取數據庫表結構(表、字段)信息關鍵代碼如下
@Service
public class DbServiceImpl implements IDbService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${spring.datasource.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String user;
@Value("${spring.datasource.password}")
private String pwd;
@Override
public List<TableEntity> getTables(String tableName) {
List<TableEntity> tables = new ArrayList<>();
try {
Class.forName(driverClassName);// 動態加載mysql驅動
Connection connection = DriverManager.getConnection(url, user, pwd);
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getTables(null, null, "%", new String[]{"TABLE"});
// metaData.getTables("yjc", "", "%", new String[]{"TABLE"})
while (resultSet.next()) {
if (resultSet.getString("TABLE_NAME").contains(tableName)) {
TableEntity tmpTable = new TableEntity();
tmpTable.setTbName(resultSet.getString("TABLE_NAME"));
tmpTable.setComments(resultSet.getString("REMARKS"));
tmpTable.setCatalog(resultSet.getString("TABLE_CAT"));
tmpTable.setSchema(resultSet.getString("TABLE_SCHEM"));
tables.add(tmpTable);
}
}
} catch (Exception e) {
logger.error("獲取數據庫表列表失敗", e);
}
return tables;
}
@Override
public List<ColumnEntity> getColumns(String tableName) {
List<ColumnEntity> columnEntityList = new ArrayList<>();
try {
Class.forName(driverClassName);// 動態加載mysql驅動
Connection connection = DriverManager.getConnection(url, user, pwd);
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getColumns(null, null, tableName, "%");
while (resultSet.next()) {
ColumnEntity tmpColumnEntity = new ColumnEntity();
tmpColumnEntity.setColumnName(resultSet.getString("COLUMN_NAME"));
tmpColumnEntity.setBufferLength(resultSet.getInt("BUFFER_LENGTH"));
tmpColumnEntity.setColumnSize(resultSet.getInt("COLUMN_SIZE"));
tmpColumnEntity.setComments(resultSet.getString("REMARKS"));
tmpColumnEntity.setDecimalDigits(resultSet.getInt("DECIMAL_DIGITS"));
tmpColumnEntity.setDataType(resultSet.getInt("DATA_TYPE"));
tmpColumnEntity.setTypeName(resultSet.getString("TYPE_NAME"));
tmpColumnEntity.setIsNullAble(resultSet.getString("IS_NULLABLE"));
tmpColumnEntity.setIsAutoIncrement(resultSet.getString("IS_AUTOINCREMENT"));
columnEntityList.add(tmpColumnEntity);
}
} catch (Exception e) {
logger.error("查詢表的列發生異常", e);
}
return columnEntityList;
}
@Override
public TableEntity getTableEntity(String tableName) {
TableEntity tableEntity = new TableEntity();
try {
Class.forName(driverClassName);// 動態加載mysql驅動
Connection connection = DriverManager.getConnection(url, user, pwd);
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getTables(null, null, tableName, new String[]{"TABLE"});
while (resultSet.next()) {
if(tableName.equals(resultSet.getString("TABLE_NAME")))
{
tableEntity.setTbName(resultSet.getString("TABLE_NAME"));
tableEntity.setComments(resultSet.getString("REMARKS"));
tableEntity.setCatalog(resultSet.getString("TABLE_CAT"));
tableEntity.setSchema(resultSet.getString("TABLE_SCHEM"));
tableEntity.setPk(this.getPrimaryKeyColumnName(metaData,tableEntity.getCatalog(),tableEntity.getSchema(),tableEntity.getTbName()));
}
}
} catch (Exception e) {
logger.error("獲取表對象失敗", e);
}
return tableEntity;
}
private String getPrimaryKeyColumnName(DatabaseMetaData metaData,String catalog,String schema,String tableName)
{
String primaryKeyColumnName="";
try {
ResultSet resultSet = metaData.getPrimaryKeys(catalog, schema, tableName);
while (resultSet.next())
{
primaryKeyColumnName= resultSet.getString("COLUMN_NAME");
}
} catch (SQLException e) {
logger.error("獲取主鍵發生異常",e);
}
return primaryKeyColumnName;
}
}
另外在使用 DatabaseMetaData獲取表、列信息的時候,如
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getColumns(null, null, tableName, "%");
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getTables(null, null, "%", new String[]{"TABLE"});
獲取表格信息、獲取列信息都是返回的 ResultSet,這個ResultSet 有點蛋疼,需要按照字段來查詢,或者指定索引順序來獲取想要的結果,對照關係如下面截圖
使用模板生成代碼
使用的是velocity引擎(當然也可以使用freemarker等,這個不重要)
模板代碼示例如下:
package ${package}.${moduleName}.entity;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
#if(${hasBigDecimal})
import java.math.BigDecimal;
#end
import java.io.Serializable;
import java.util.Date;
/**
* ${comments}
*
* @author ${author}
* @email ${email}
* @date ${datetime}
*/
@TableName("${tableName}")
public class ${className}Entity implements Serializable {
private static final long serialVersionUID = 1L;
#foreach ($column in $columns)
/**
* $column.comments
*/
#if($column.columnName == $pk.columnName)
@TableId
#end
private $column.attrType $column.attrname;
#end
#foreach ($column in $columns)
/**
* 設置:${column.comments}
*/
public void set${column.attrName}($column.attrType $column.attrname) {
this.$column.attrname = $column.attrname;
}
/**
* 獲取:${column.comments}
*/
public $column.attrType get${column.attrName}() {
return $column.attrname;
}
#end
}
package ${package}.${moduleName}.controller;
import java.util.Arrays;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ${package}.${moduleName}.entity.${className}Entity;
import ${package}.${moduleName}.service.${className}Service;
import ${mainPath}.common.utils.PageUtils;
import ${mainPath}.common.utils.R;
/**
* ${comments}
*
* @author ${author}
* @email ${email}
* @date ${datetime}
*/
@RestController
@RequestMapping("${moduleName}/${pathName}")
public class ${className}Controller {
@Autowired
private ${className}Service ${classname}Service;
/**
* 列表
*/
@GetMapping("/list")
public ResponseEntity<BaseResponse<Page<${className}>>> list(@PageableDefault(value = 15, sort = { "${pk}" }, direction = Sort.Direction.DESC) Pageable pageable)
{
BaseResponse<Page<${className}> > baseResponse=new BaseResponse<>();
Page<${className}> all = ${classname}Repository.findAll(pageable)};
if(all!=null && !all.isEmpty())
{
return new ResponseEntity<BaseResponse<Page<${className}>>>(BaseResponseFactory.success(all),HttpStatus.OK);
}
else
{
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
/**
* 單個查詢
*/
@GetMapping("/{${pk}}")
@ApiOperation(value = "/{${pk}}", httpMethod = "GET", notes = "查詢單個${className}信息}")
public ResponseEntity<BaseResponse<${className}>> info(@PathVariable long ${pk}) {
Optional<${className}> optional = ${classname}Repository.findById(${pk});
if(optional.isPresent())
{
return new ResponseEntity<>(BaseResponseFactory.success(optional.get()), HttpStatus.OK);
}else
{
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
}
/**
* 保存
*/
@PostMapping("/add")
public ResponseEntity<BaseResponse<${className}>> save(@Validated @RequestBody ${className} goodsInfo, BindingResult bindingResult)
{
ResponseEntity<BaseResponse<${className}>> responseEntity;
BaseResponse baseResponse=new BaseResponse<>();
if(bindingResult.hasErrors())
{
StringBuilder sb=new StringBuilder();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getDefaultMessage());
sb.append(" ");
}
baseResponse.setCode(400);
baseResponse.setMessage(sb.toString());
responseEntity=new ResponseEntity<>(baseResponse,HttpStatus.BAD_REQUEST);
}
else
{
${className} save = ${classname}Repository.save(${classname});
baseResponse.setCode(200);
baseResponse.setMessage("保存成功");
baseResponse.setData(save);
responseEntity=new ResponseEntity<>(baseResponse, HttpStatus.OK);
}
return responseEntity;
}
}
字段對應轉換
如何將數據庫的字段類型對應到java代碼上,比如數據庫中的varchar,需要對應到java的String,本例是參考了一個自動生成工具的方式,使用了對應配置表,內容如下。
#代碼生成器,配置信息
mainPath=com.
#包名
package=redheart
moduleName=erp
#作者
author=pf
#Email
[email protected]
#表前綴(類名不會包含表前綴)
tablePrefix=yjc_
#類型轉換,配置信息
TINYINT=Integer
SMALLINT=Integer
MEDIUMINT=Integer
INT=Integer
INTEGER=Integer
BIGINT=Long
FLOAT=Float
DOUBLE=Double
DECIMAL=BigDecimal
BIT=Boolea
CHAR=String
VARCHAR=String
TINYTEXT=String
TEXT=String
MEDIUMTEXT=String
LONGTEXT=String
DATE=Date
DATETIME=Date
TIMESTAMP=Date