代碼自動生成工具

構建支持多種數據庫類型的代碼自動生成工具

背景:

一般的業務代碼中寫來寫去,無外乎是先建好model,然後針對這個model做些CRUD的操作。(主要針對單表的業務操作)針對於數據庫dao、mapper等的代碼自動生成已經有了mybatisGenerator這種工具,但是針對於controller、service這些我們現在的接口api一般遵循的是restful風格,因此這些也是有規則可循的。舉例有個goodsInfo 的model,針對於他的操作,肯定有 單個查詢、list查詢、修改、刪除等。而這些代碼沒必要複製粘貼來一遍,完全可以由工具自動生成,若有特殊業務場景重寫即可。本工具就算解決這類問題的。

效果截圖

運行生成示例結果:
blob.jpg
blob.jpg

表選擇界面:
blob.jpg

思路:

代碼自動生成說起來很神祕,其實無外乎兩個方面:

  1. 從數據庫拿到需要自動生成的代碼對應表。
  2. 從表結構、字段名生成對應的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 有點蛋疼,需要按照字段來查詢,或者指定索引順序來獲取想要的結果,對照關係如下面截圖

blob.jpg

blob.jpg

使用模板生成代碼

使用的是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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章