在码农的日常生活中,复杂度低但是工作量大的工作莫过于根据已有的表写对应的实体类。当字段多的时候,那叫一个苦啊!于是,很多代码生成器应运而生,最流行的莫过于Mybatis-Generator,自动生成Entity、Dao、Mapper,确实是一个不错的工具。但是,以博主的了解,这个工具还是有一些缺点的,算是鸡蛋里挑骨头吧:样式是固定的,有时候不符合我们的习惯(当然,大神可以去改源码)。
现在呢,我们也来开发一个自动生成实体类的工具,准确的说是Maven插件。它可以根据自己配置的数据库连接、用户密码、库表在指定的项目包下生成Java文件,可按需要设置自己的模板。还有一个功能是根据实体类生成库表,这个使用的条件比较苛刻,后面也一并介绍吧。啥也不说,先上实际效果图镇楼!
让我们进入学习吧!
一、了解自定义Maven插件开发。
这里我不会做太多介绍,毕竟我也不是很精通。网上有很多文章写的比较好,比较细,感兴趣的话可以去学习。但是,我也会一步步引导大家,开发出一个完整的Maven插件。
推荐大家使用IDEA进行开发,因为这个工具自带需要的插件,如果使用了Eclipse,要自己网上找教程安装一下。
1、新建项目,选择JDK1.8+,下图所示的archetype,然后点击Next。
2、 写项目的坐标,GroupId一般是域名反写,主要说一下ArtifactId:官方给了两种可以使用简写的规范,一种是maven-xxx-plugin,这种命名一般是apache自己的插件用了,我们不使用,免得侵权什么的;第二种就是推荐使用的xxx-maven-plugin这种命名。按规范写的话使用的时候可以简写,就是写"xxx:goal"就好了,比如maven-install-plugin,我们使用的时候直接是install。
3、选择Maven版本。
4、选择目录和项目名称。最后点击Finish即可完成项目构建。
二、pom.xml文件介绍。
三、定义Table类,用来保存从数据库读到的表的信息。
/**
* 表的信息
* @author z_hh
* @date 2018-04-12
*/
public class Table {
// 表名
private String tableName;
// 备注
private String tableRemark;
// 列
private List<Column> columns = new ArrayList<>();
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getTableRemark() {
return tableRemark;
}
public void setTableRemark(String tableRemark) {
this.tableRemark = tableRemark;
}
public List<Column> getColumns() {
return columns;
}
public void setColumns(List<Column> columns) {
this.columns = columns;
}
class Column {
// 名称
private String columnName;
// 类型
private String columnType;
// 长度
private Integer columnSize;
// 是否可为空
private Integer columnNullable;
// 默认值
private String columnDefaultValue;
// 备注
private String columnRemark;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getColumnType() {
return columnType;
}
public void setColumnType(String columnType) {
this.columnType = columnType;
}
public Integer getColumnSize() {
return columnSize;
}
public void setColumnSize(Integer columnSize) {
this.columnSize = columnSize;
}
public Integer getColumnNullable() {
return columnNullable;
}
public void setColumnNullable(Integer columnNullable) {
this.columnNullable = columnNullable;
}
public String getColumnDefaultValue() {
return columnDefaultValue;
}
public void setColumnDefaultValue(String columnDefaultValue) {
this.columnDefaultValue = columnDefaultValue;
}
public String getColumnRemark() {
return columnRemark;
}
public void setColumnRemark(String columnRemark) {
this.columnRemark = columnRemark;
}
@Override
public String toString() {
return "Column{" +
"columnName='" + columnName + '\'' +
", columnType='" + columnType + '\'' +
", columnSize=" + columnSize +
", columnNullable=" + columnNullable +
", columnDefaultValue='" + columnDefaultValue + '\'' +
", columnRemark='" + columnRemark + '\'' +
'}';
}
}
@Override
public String toString() {
return "Table{" +
"tableName='" + tableName + '\'' +
", tableRemark='" + tableRemark + '\'' +
", columns=" + columns +
'}';
}
}
四、定义需要生成的文件信息类。
/**
* Java文件信息
*
* @author z_hh
* @version 1.0
* @since 2018/4/12
*/
public class JavaFileInfo {
/**
* 文件名
*/
private String fileName;
/**
* 文本内容
*/
private String text;
private JavaFileInfo() {}
public static JavaFileInfo create() {
return new JavaFileInfo();
}
public String getFileName() {
return fileName;
}
public JavaFileInfo setFileName(String fileName) {
this.fileName = fileName;
return this;
}
public String getText() {
return text;
}
public JavaFileInfo setText(String text) {
this.text = text;
return this;
}
@Override
public String toString() {
return "JavaFileInfo{" +
"fileName='" + fileName + '\'' +
", text='" + text + '\'' +
'}';
}
}
五、定义最重要的类,插件执行的主类。
类的goal注解和父类
成员变量,根据注解从用户配置那里取到属性值
/**
* path of the source folder.
* @parameter expression="${sourceFolderPath}"
* @required
*/
private String sourceFolderPath;
/**
* name of the package.
* @parameter expression="${packageName}"
* @required
*/
private String packageName;
/**
* url of the database.
* @parameter expression="${dbUrl}"
* @required
*/
private String dbUrl;
/**
* user of the the database.
* @parameter expression="${dbUser}"
* @required
*/
private String dbUser;
/**
* password of the the database.
* @parameter expression="${dbPwd}"
* //@required
*/
private String dbPwd;
/**
* table name of generator java file.
* @parameter expression="${tableNames}"
*/
private String[] tableNames;
/**
* type of the database, for example:oracle、mysql.
*/
private String dbType;
重写execute方法,插件处理逻辑写在这里面
参数校验以及识别数据库类型
获取表信息并封装到自定义Table类
private List<Table> geTables(String[] tableNames) throws Exception {
Connection connection = getConnection();
DatabaseMetaData dbmd = connection.getMetaData();
ResultSet resultSet = dbmd.getTables(null, "%", "%", new String[] { "TABLE" });
List<Table> tables = new ArrayList<>();
while (resultSet.next()) {
String tableName = resultSet.getString("TABLE_NAME");
boolean need = Arrays.stream(tableNames).anyMatch(name -> name.equalsIgnoreCase(tableName));
boolean contains = tables.parallelStream().anyMatch(table -> table.getTableName().equalsIgnoreCase(tableName));
if (!need || contains) {
continue;
}
getLog().info("正在分析表" + tableName + "...");
Table table = new Table();
table.setTableName(tableName);
table.setTableRemark(resultSet.getString("REMARKS"));
List<Table.Column> columns = table.getColumns();
ResultSet rs = null;
if (Utils.ORACLE.equalsIgnoreCase(dbType)) {
rs = dbmd.getColumns(null, getSchema(connection), tableName.toUpperCase(), "%");
}
// 除了oracle和db2其它这么用
else {
rs = dbmd.getColumns(null, "%", tableName, "%");
}
while (rs.next()) {
Table.Column column = table.new Column();
column.setColumnName(rs.getString("COLUMN_NAME"));
column.setColumnType(rs.getString("TYPE_NAME"));
column.setColumnSize(rs.getInt("COLUMN_SIZE"));
column.setColumnNullable(rs.getInt("NULLABLE"));
column.setColumnDefaultValue(rs.getString("COLUMN_DEF"));
column.setColumnRemark(rs.getString("REMARKS"));
columns.add(column);
}
getLog().info("读取到" + columns.size() + "个字段");
tables.add(table);
}
connection.close();
return tables;
}
创建Java文件
根据Table得到JavaFIleInfo对象
/**
* 生成DO对象文本
* @param table
* @return
*/
private JavaFileInfo createDoJavaFileInfo(Table table) {
String tableName = table.getTableName(),
className = tableName.substring(0, 1).toUpperCase()
+ lineToHump(tableName).substring(1);
getLog().info("正在生成类" + className + "...");
// 替换类名、包名、表名
String classText = classTemplateText.replace("${packageName}", packageName)
.replace("${className}", className).replace("${tableName}", tableName);
List<String> fieldTexts = new ArrayList<>();
List<String> getSetMethodTexts = new ArrayList<>();
table.getColumns().stream().forEach(column -> {
String name = lineToHump(column.getColumnName()),
type = typeMap.get(column.getColumnType()),
nullAble = Objects.equals(column.getColumnNullable(), 1) ? "可空" : "非空",
remark = Optional.ofNullable(column.getColumnRemark()).orElse(""),
getterSetterName = name.substring(0, 1).toUpperCase() + name.substring(1);
// 得到一个字段的声明、get方法、set方法
String fieldText = fieldTemplateText.replace("${fieldRemark}", remark)
.replace("${otherInfo}", "长度:" + column.getColumnSize() + ","
+ nullAble + ",默认值:" + column.getColumnDefaultValue())
.replace("${fieldType}", type).replace("${fieldName}", name);
String getSetMethodText = getSetMethodTemplateText.replace("${fieldType}", type)
.replace("${fieldName}", name).replace("${u_fieldName}", getterSetterName);
fieldTexts.add(fieldText);
getSetMethodTexts.add(getSetMethodText);
});
// 得到全部字段的声明语句
String fieldPart = fieldTexts.stream().reduce((f1, f2) -> f1 + f2).orElse("// 什么也没有^_-_^");
// 得到全部get方法、set方法的语句
String getSetMethodPart = getSetMethodTexts.stream().reduce((f1, f2) -> f1 + "\n" + f2).orElse("// 什么也没有^_-_^");
// 替换类模板文件的字段部分和方法部分
String classFinalText = classText.replace("@{fieldPart}", fieldPart)
.replace("${getSetMethodPart}", getSetMethodPart);
return JavaFileInfo.create().setFileName(className).setText(classFinalText.toString());
}
在指定包生成对应实体类文件
最后运行效果
剩下内容和根据实体类生成库表将在后面讲解,敬请期待!!!
存在问题及修改意向:生成的实体类模板,字段声明模板,注释模板,getset方法模板,现在都是写死在代码里面的,要修改的话必须要动源代码,这样很不方便。所以后续打算,将这些模板内容写在外部文件里,然后在插件配置里面写上文件路径,为空的话就使用默认的模板。
项目完整代码已上传,前往下载