代码生成器相信大家都见过或者用过吧?最典型的就是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类等,大体思路一样。
完整代码已上传,点击下载