代碼生成器相信大家都見過或者用過吧?最典型的就是MyBatis那個生成實體類和Mapper文件的工具。顯然,代碼自動生成可以減少一些重複代碼的編寫,從而提高開發效率。
代碼生成工具主要做法就是使用模板引擎,把公共的部分抽取出來形成一個模板,對於變化的一些數據暫時使用佔位符標記。到了生成具體代碼的時候,就把佔位符替換成實際需要的內容。
下面就做一個根據MySQL數據庫表自動生成實體類的小案例,希望達到拋磚引玉的效果。
1、首先我們在MySQL數據庫建立一張表,建表SQL如下
CREATE TABLE `student` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '姓名',
`code` varchar(100) NOT NULL DEFAULT '' COMMENT '編號',
`sex` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性別:1-男,2-女',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否刪除:1-是,0否',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_code` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='學生表';
2、然後我們新建一個maven項目,引入MySQL的驅動jar包和模板引擎Velocity的相關jar包。爲了代碼簡潔,引入Lombok工具。
3、在項目裏面,創建一個模板文件Entity.java.vm,存放在resources的template目錄下,內容如下
類的註釋、名稱和字段的註釋、類型、名稱都是從數據庫表讀取並轉換得來的,其它的包名、作者可以寫在配置文件裏面。
4、創建一個代表實體類信息的Java類,用於保存從數據庫讀取一個表並轉化的信息。
這裏的外部類記錄表對應的類信息,內部類記錄列對應的字段信息。
/**
* 實體類信息
*
* @author z_hh
* @date 2019/6/25
*/
@Data
public class EntityInfo {
/**
* 類名
*/
private String className;
/**
* 註釋
*/
private String classComment;
/**
* 屬性集合
*/
private List<FieldInfo> filedInfoList;
/**
* 字段信息
*
* @author z_hh
* @date 2019/6/25
*/
@Data
public static class FieldInfo {
/**
* 字段名稱
*/
private String fieldName;
/**
* 註釋
*/
private String fieldComment;
/**
* 字段類型
*/
private String fieldType;
}
}
5、從數據庫讀取表的信息,轉換爲表示實體類信息的Java類對象。
這裏使用最簡單的JDBC了。如果是其它數據庫,查詢的SQL和獲取表列信息的方式就需要變一下了。Oracle的話我之前寫的博客有現成的代碼,可以去看一下:https://blog.csdn.net/qq_31142553/article/details/81256516。
/**
* 數據訪問對象
*
* @author z_hh
* @date 2019/6/25
*/
public class Dao {
public static EntityInfo getEntityInfoByTableName(String tableName) throws Exception {
Connection connection = DriverManager.getConnection(Const.CONFIG.getString("database.url"),
Const.CONFIG.getString("database.username"),
Const.CONFIG.getString("database.password"));
// 表信息
PreparedStatement preparedStatement = connection.prepareStatement(Const.QUERY_TABLE_SQL);
preparedStatement.setString(1, tableName);
ResultSet resultSet = preparedStatement.executeQuery();
EntityInfo entityInfo = new EntityInfo();
while (resultSet.next()) {
entityInfo.setClassName(StrConvertUtils.underlineCase2CamelCase(tableName, true));
entityInfo.setClassComment(resultSet.getString("table_comment"));
}
// 列信息
preparedStatement = connection.prepareStatement(Const.QUERY_COLUMN_SQL);
preparedStatement.setString(1, tableName);
resultSet = preparedStatement.executeQuery();
List<EntityInfo.FieldInfo> fieldInfoList = new ArrayList<>();
while (resultSet.next()) {
EntityInfo.FieldInfo fieldInfo = new EntityInfo.FieldInfo();
// 字段名轉爲屬性名
String columnName = resultSet.getString("column_name");
fieldInfo.setFieldName(StrConvertUtils.underlineCase2CamelCase(columnName, false));
// 列類型轉爲Java類型
String tbType = resultSet.getString("data_type");
fieldInfo.setFieldType(StrConvertUtils.dbType2JavaType(tbType));
// 註釋
fieldInfo.setFieldComment(resultSet.getString("column_comment"));
fieldInfoList.add(fieldInfo);
}
entityInfo.setFiledInfoList(fieldInfoList);
return entityInfo;
}
這裏面用到的兩個工具類方法如下
/**
* 字符串轉換工具
*
* @author z_hh
* @date 2019/6/25
*/
public class StrConvertUtils {
/**
* 下滑線改爲駝峯式
*
* @param str 下劃線字符串
* @param upperFirst 首字母是否大寫
* @return 駝峯式字符串
*/
public static String underlineCase2CamelCase(CharSequence str, boolean upperFirst) {
if (Objects.isNull(str)) {
return null;
}
// 下滑線改爲駝峯式
String str2 = str.toString();
if (str2.contains("_")) {
str2 = str2.toLowerCase();
StringBuilder sb = new StringBuilder(str2.length());
boolean upperCase = false;
for (int i = 0; i < str2.length(); ++i) {
char c = str2.charAt(i);
if (c == '_') {
upperCase = true;
} else if (upperCase) {
sb.append(Character.toUpperCase(c));
upperCase = false;
} else {
sb.append(c);
}
}
str2 = sb.toString();
}
// 首字母大寫
if (upperFirst && str2.length() > 0) {
char firstChar = str2.charAt(0);
if (Character.isLowerCase(firstChar)) {
return Character.toUpperCase(firstChar) + str2.substring(1);
}
}
return str2;
}
/**
* 數據庫類型轉爲Java類型
*
* @param dbType 數據庫的列類型
* @return Java類型
*/
public static String dbType2JavaType(String dbType) {
return Optional.ofNullable(Const.TYPE_CONVERT.getString(dbType)).orElse("UknowType");
}
}
其中數據庫連接信息、查詢的SQL寫到了常量類裏面。然後數據庫連接信息又是從配置文件讀取的,這裏使用了ResourceBundle工具,注意裏面填的配置文件名是省略後綴的。
/**
* 常量
*
* @author z_hh
* @date 2019/6/25
*/
public interface Const {
String QUERY_TABLE_SQL = "select table_name, table_comment from information_schema.tables"
+ " where table_schema = (select database()) and table_name = ?";
String QUERY_COLUMN_SQL = "select column_name, data_type, column_comment, column_key, extra from information_schema.columns"
+ " where table_schema = (select database()) and table_name = ?";
ResourceBundle CONFIG = ResourceBundle.getBundle("config");
ResourceBundle TYPE_CONVERT = ResourceBundle.getBundle("type-convert");
}
對應的配置文件放在resources根目錄下,其中config.properties的內容如下,後面的三個配置是生成代碼時用到的。
#數據庫地址
database.url=jdbc:mysql://xxx000.com:9527/demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false
#數據庫用戶
database.username=admin
#數據庫密碼
database.password=admin
#路徑
basePath=C:/Users/dell/Desktop/java
#包名
packageName=cn.zhh.entity
#作者
author=ZhouHH
另一個配置文件type-convert.properties寫的是數據庫的數據類型和Java裏面的數據類型之間的對應關係,這裏先看一下吧
#類型轉換,配置信息
tinyint=Byte
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer
bigint=Long
float=Float
double=Double
decimal=BigDecimal
bit=Boolean
char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String
date=LocalDateTime
datetime=Date
timestamp=Date
6、得到了表示實體類信息的Java類對象,就可以渲染模板了。最後將內容寫入文件並保存到指定位置。
注意列columns是集合遍歷。
/**
* 業務處理對象
*
* @author z_hh
* @date 2019/6/25
*/
public class MainService {
public static void generateCode(String tableName) throws Exception {
// 設置模板填充數據
System.out.println("正在查詢表信息:" + tableName);
EntityInfo entityInfo = Dao.getEntityInfoByTableName(tableName);
Map contextMap = new HashMap();
contextMap.put("packageName", Const.CONFIG.getString("packageName"));
contextMap.put("classComment", entityInfo.getClassComment());
contextMap.put("author", Const.CONFIG.getString("author"));
contextMap.put("datetime", LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE));
contextMap.put("className", entityInfo.getClassName());
contextMap.put("packageName", Const.CONFIG.getString("packageName"));
contextMap.put("columns", entityInfo.getFiledInfoList());
// 渲染模板
System.out.println("正在渲染模板...");
Properties p = new Properties();
p.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(p);
String template = "template/Entity.java.vm";
VelocityContext velocityContext = new VelocityContext(contextMap);
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template);
tpl.merge(velocityContext, sw);
String context = sw.toString();
// 生成文件
System.out.println("正在生成文件...");
Path filePath = Paths.get(Const.CONFIG.getString("basePath"), entityInfo.getClassName() + ".java");
Files .write(filePath, context.getBytes());
System.out.println("生成代碼成功,路徑->" + filePath.toString());
}
}
7、最後對我們創建的那個表試一下
public class Test {
public static void main(String[] args) throws Exception {
MainService.generateCode("student");
}
}
控制檯輸出
文件效果展示
總結與反思
1、讀取列的信息可以更多,比如主鍵、是否爲空、長度、默認值等,有必要的話可以按需加上。
2、可以支持更多數據庫類型,但是查詢表和列的SQL會不一樣,可以使用策略模式靈活切換。
3、一般結合圖形界面使用會更加人性化,並且支持生成Service類、Dao類等,大體思路一樣。
完整代碼已上傳,點擊下載