本文介紹如何用Java編寫高度自定義的程式碼生成器
MyBatis 是一款優秀的持久層框架,它支援定製化 SQL、儲存過程以及高階對映。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和對映原生資訊。
上面這一段話來自Mybatis官網
的介紹, 初用Mybatis時感覺這個框架相比於JDBC優雅多了, 用起來也如官網說的非常簡單。但是用了一段時間之後, 弊端就慢慢凸顯出來了
使用Mybatis時不得不為每個表建立一個Entity.java
、Mapper.xml(Mapper可以融合入Dao中)
、Dao.java
,Service.java
層次很清晰, 但是太多重複性的工作了, 費時間且易於出錯
並且當資料庫發生一點改動的時候... 苦不堪言
後來出現了自動生成程式碼的外掛, 但是總是不盡人意, 不能隨心所欲地控制, 畢竟每個人的需求都不一樣
本文就來介紹如何簡單的編寫一個自己的程式碼生成器
專案原始碼
程式碼實現
實現的思路很簡單, 首先查詢資料庫的表結構, 得到列名, 列型別...
等資訊
建立檔案模版, 將這些資訊插入模版中, 最後打包模版進壓縮包匯出
程式碼實現 一共五個Java類
- TableDO
- ColumnDO
- GeneratorMapper
- GeneratorUtils
- GeneratorService
首先來看兩個實體類
TableDO 和 ColumnDO
TableDO 存放表名, 對於的類名, 以及列資訊
完整類程式碼 TableDO.java
public class TableDO {
private String tableName;
private List<ColumnDO> columns;
private String className;
private String suffix;
// get()... set()...
}
複製程式碼
ColumnDO 存放列名, 資料庫欄位型別, 以及對應Java中的屬性名和型別
完整類程式碼 ColumnDO.java
public class ColumnDO {
private String columnName;
private String dataType;
private String attrName;
private String attrLowerName;
private String attrType;
// get()... set()...
}
複製程式碼
GeneratorMapper
在GeneratorMapper 中, 我們通過表名查詢表自動的資訊
完整類程式碼 GeneratorMapper.java
@Mapper
public interface GeneratorMapper {
@Select("select column_name columnName, data_type dataType from information_schema.columns where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position")
List<ColumnDO> listColumns(String tableName);
}
複製程式碼
GeneratorUtils
在GeneratorUtils 中進行類資訊與模版之間的轉換
完整類程式碼 GeneratorUtils.java
將表資訊放入Velocity
模版的上下文中
Map<String, Object> map = new HashMap<>();
map.put("tableName", table.getTableName());
map.put("className", table.getClassName());
map.put("pathName", getPackageName().substring(getPackageName().lastIndexOf(".") + 1));
map.put("columns", table.getColumns());
map.put("package", getPackageName());
map.put("suffix", table.getSuffix());
Properties prop = new Properties();
prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.init(prop);
VelocityContext context = new VelocityContext(map);
複製程式碼
新增模版
List<String> templates = new ArrayList<>();
templates.add("mybatis/Model.java.vm");
templates.add("mybatis/Query.java.vm");
templates.add("mybatis/Dao.java.vm");
templates.add("mybatis/Mapper.xml.vm");
templates.add("mybatis/Service.java.vm");
複製程式碼
編譯模版
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, "UTF-8");
tpl.merge(context, sw);
複製程式碼
Utils類完成了生成程式碼的主要工作, 但是程式碼也是比較簡單的
GeneratorService
在Service 中注入Mapper 查詢列資訊, 並用Utils生成程式碼, 然後匯出壓縮包
完整類程式碼 GeneratorService.java
@Service
public class GeneratorService {
@Resource
private GeneratorMapper generatorMapper;
@Resource
private Environment environment;
public void generateZip(String[] tableNames, String zipPath) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
for (String tableName : tableNames) {
TableDO table = new TableDO();
table.setTableName(tableName);
table.setColumns(generatorMapper.listColumns(tableName));
GeneratorUtils.generatorCode(table, zip,getConfig());
}
IOUtils.closeQuietly(zip);
FileOutputStream file = new FileOutputStream(zipPath);
file.write(outputStream.toByteArray());
file.close();
}
// getConfig ...
}
複製程式碼
VM模版
自己寫程式碼生成器的好處就是, 可以根據需求定製自己的模版, 下面是我的幾個模版可以供參考
- Mapper.xml.vm
- Dao.java.vm
- Service.java.vm
- Model.java.vm
- Query.java.vm
生成的程式碼是在commons-mybatis架構下使用的
Dao.java.vm
package ${package}.database.dao;
import ${package}.database.model.${className}${suffix};
import org.apache.ibatis.annotations.Mapper;
import org.laziji.commons.mybatis.dao.${suffix}Dao;
@Mapper
public interface ${className}Dao extends ${suffix}Dao<${className}${suffix}> {
}
複製程式碼
...
使用
配置檔案
在resources
下建立application-${name}.yml
檔案, ${name}
隨意, 例如: application-example.yml
, 可建立多個
配置檔案內容如下, 填入資料庫配置, 以及生成程式碼的包名, 原始檔路徑
spring:
datasource:
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/xxxx?characterEncoding=utf-8
username: xxxxxx
password: xxxxxx
generator:
package: com.xxx.xxx
resources: mapper
複製程式碼
Test
在test檔案下建立測試類
@ActiveProfiles("example")
中填入剛才配置檔名的name
tableNames
需要生成的表, 可以多個zipPath
程式碼匯出路徑 執行測試方法即可
package pg.laziji.generator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import pg.laziji.generator.mybatis.GeneratorService;
import javax.annotation.Resource;
import java.io.IOException;
@ActiveProfiles("example")
@RunWith(SpringRunner.class)
@SpringBootTest
public class ExampleTest {
@Resource
private GeneratorService generatorService;
@Test
public void test() throws IOException {
String[] tableNames = new String[]{"example_table1", "example_table2"};
String zipPath = "/home/code.zip";
generatorService.generateZip(tableNames,zipPath);
}
}
複製程式碼
歡迎關注我的部落格公眾號