一 生成原理
1 先编写一套模版代码
2 在模版代码中加入特殊占位符
3 连接数据库读表和字段,对占位符做替换
二 具体实现
1 编写maven代码
2 集成到maven插件 引入pom.xml文件
3 双击运行可生成代码
三 实现代码解析
项目结构如下所示 上面是代码下面是模版插件
1 定义模版我这里用的是mybatis-plus所以有那么几个文件是需要自动生成的,这个根据大家自己所需要的自己根据自己公司的需要定制自己的模版就可以了.
dao
package packagePrefix:.dao;
import packagePrefix:.entity.${entity};
import com.baomidou.mybatisplus.extension.service.IService;
public interface ${entity}Dao: extends IService<${entity}> {
}
如上所示 entityName数据库中表的下滑线转驼峰的方式.
entity
package packagePrefix:.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
@Data
@Builder
@TableName("${table}")
public class ${entity} {
${entityContent}
}
impl
package packagePrefix:.dao.impl;
import packagePrefix:.dao.${entity}Dao;
import packagePrefix:.dao.mapper.${entity}Mapper;
import packagePrefix:.entity.${entity}:;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class ${entity}DaoImpl extends ServiceImpl<${entity}Mapper, ${entity}> implements ${entity}Dao {
}
mapper
package packagePrefix:.dao.mapper;
import packagePrefix:.entity.${entity}:;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ${entity}Mapper extends BaseMapper<${entity}> {
}
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<generatorProperty>
<dalRelevant>
<commonConfig>
<driverName>com.mysql.cj.jdbc.Driver</driverName>
<url>jdbc:mysql://192.168.106.101:3306/test_hll_premier_driver</url>
<user>user_hllop</user>
<password>BicfmqsrEBUjZU6tSnp25AqaA99B62vb</password>
<packagePrefix>cn.worn.risk.driver.access</packagePrefix>
<dirPrefix>xxx/src/main/java</dirPrefix>
</commonConfig>
<tables>
<table name="premier_driver_join" fileName="Test" lineToHump="">
<fieldTypeMap>
<element field="createdAt" type="Date"></element>
</fieldTypeMap>
<columnTypeMap>
<element origin="Date" target="String"></element>
</columnTypeMap>
</table>
<table name="table"></table>
</tables>
</dalRelevant>
</generatorProperty>
配置文件对应的类
DalRelevant.java
@Data
public class DalRelevant {
private String driverName;
private String url;
private String user;
private String password;
private String packagePrefix;
private String dirPrefix;
private List<Table> tables;
}
Table.java
@Data
public class Table {
private String tableName;
private String fileName;
private Integer lineToHump;
private Map<String, String> fieldTypeMap;
private Map<String, String> columnTypeMap;
}
1 定义maven插件
abstract class AutoCreateFile extends AbstractMojo {
@Parameter(property = "configurationFile",
defaultValue = "${project.basedir}/src/test/java/generatorConfig.xml", required = true)
protected File configurationFile;
protected DalRelevant dalRelevant = new DalRelevant();
protected JsonToObject jsonToObject = new JsonToObject();
protected void readXMLConfig() throws MojoExecutionException {
System.out.printf("=====configurationFile文件:%s", configurationFile);
if (configurationFile == null) {
throw new MojoExecutionException("configurationFile 配置不能为空");
}
if (!configurationFile.exists()) {
throw new MojoExecutionException("configurationFile 不是一个文件");
}
System.out.println("======当前调用类:"+this.getClass());
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(configurationFile);
Element rootElement = document.getRootElement();
//dal
if (rootElement.element("dalRelevant") != null && this.getClass() == DalRelevantMojo.class) {
//获取DB配置
if (rootElement.element("dalRelevant").element("commonConfig") == null ||
rootElement.element("dalRelevant").element("commonConfig").element("driverName") == null ||
rootElement.element("dalRelevant").element("commonConfig").element("url") == null ||
rootElement.element("dalRelevant").element("commonConfig").element("user") == null ||
rootElement.element("dalRelevant").element("commonConfig").element("password") == null ||
rootElement.element("dalRelevant").element("commonConfig").element("packagePrefix") == null ||
rootElement.element("dalRelevant").element("commonConfig").element("dirPrefix") == null
) {
throw new MojoExecutionException("commonConfig 必填参数缺失!");
}
Element dbConfig = rootElement.element("dalRelevant").element("commonConfig");
dalRelevant.setDriverName(dbConfig.elementText("driverName"));
dalRelevant.setUrl(dbConfig.elementText("url"));
dalRelevant.setUser(dbConfig.elementText("user"));
dalRelevant.setPassword(dbConfig.elementText("password"));
dalRelevant.setPackagePrefix(dbConfig.elementText("packagePrefix"));
dalRelevant.setDirPrefix(dbConfig.elementText("dirPrefix"));
System.out.printf("=====数据库配置:%s", dalRelevant);
//tables
List<Element> listTables = rootElement.element("dalRelevant").element("tables").elements("table");
List<Table> tableList = new ArrayList<>();
for (Element table: listTables) {
if (table.attribute("name") == null || StringUtils.isBlank(table.attribute("name").getValue())) {
continue;
}
Table tableConfigMap = new Table();
tableConfigMap.setTableName(table.attribute("name").getValue());
if (table.attribute("fileName") != null) {
tableConfigMap.setFileName(table.attribute("fileName").getValue());
}
if (table.attribute("lineToHump") != null && StringUtils.isNotBlank(table.attribute("lineToHump").getValue())) {
tableConfigMap.setLineToHump(Integer.parseInt(table.attribute("lineToHump").getValue()));
}
if (table.element("fieldTypeMap") != null && table.element("fieldTypeMap").elements("element") != null) {
Map<String, String> fieldTypeMap = new HashMap<>();
List<Element> fieldTypeElements = table.element("fieldTypeMap").elements("element");
for (Element fieldTypeElement: fieldTypeElements) {
fieldTypeMap.put(fieldTypeElement.attribute("field").getValue(), fieldTypeElement.attribute("type").getValue());
}
tableConfigMap.setFieldTypeMap(fieldTypeMap);
}
if (table.element("columnTypeMap") != null && table.element("columnTypeMap").elements("element") != null) {
Map<String, String> columnTypeMap = new HashMap<>();
List<Element> columnTypeElements = table.element("columnTypeMap").elements("element");
for (Element columnTypeElement: columnTypeElements) {
columnTypeMap.put(columnTypeElement.attribute("origin").getValue(), columnTypeElement.attribute("target").getValue());
}
tableConfigMap.setColumnTypeMap(columnTypeMap);
}
tableList.add(tableConfigMap);
}
if (tableList != null) {
dalRelevant.setTables(tableList);
}
}
System.out.printf("====结果%s", dalRelevant);
//jsonToObject
if (rootElement.element("jsonToObject") != null && this.getClass() == JsonToObjectMojo.class) {
if (rootElement.element("jsonToObject").element("jsonString") == null ||
rootElement.element("jsonToObject").element("lineToHump") == null ||
rootElement.element("jsonToObject").element("fileName") == null ||
rootElement.element("jsonToObject").element("packagePrefix") == null ||
rootElement.element("jsonToObject").element("dirPrefix") == null
) {
throw new MojoExecutionException("jsonToObject 必填参数缺失!");
}
jsonToObject.setJsonString(rootElement.element("jsonToObject").element("jsonString").getStringValue());
jsonToObject.setLineToHump(Integer.parseInt(rootElement.element("jsonToObject").element("lineToHump").getText()));
jsonToObject.setFileName(rootElement.element("jsonToObject").element("fileName").getStringValue());
jsonToObject.setPackagePrefix(rootElement.element("jsonToObject").element("packagePrefix").getStringValue());
jsonToObject.setDirPrefix(rootElement.element("jsonToObject").element("dirPrefix").getStringValue());
}
} catch (Exception e) {
e.printStackTrace();
throw new MojoExecutionException("解析xml失败:".concat(e.getMessage()));
}
}
/**
* 文件内容渲染
* @param templateName
* @param replaceMap
* @throws IOException
*/
protected static void render(String templateName, Map<String, String> replaceMap, String extDir) throws Exception {
InputStream templateStream = Objects.requireNonNull(AutoCreateFile.class.getClassLoader().getResource("template/"+templateName)).openStream();
String implDir = replaceMap.get("completeDir");
if (extDir != null) {
implDir = implDir.concat(extDir);
}
String outFile = implDir.concat(replaceMap.get("fileName")).concat(".java");
BufferedWriter bw= new BufferedWriter(new FileWriter(outFile));
InputStreamReader in = new InputStreamReader(templateStream);
BufferedReader bufIn = new BufferedReader(in);
String line = null;
StringBuilder stringBuilder=new StringBuilder();
while ((line = bufIn.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append(System.getProperty("line.separator"));
}
String template = stringBuilder.toString();
TokenHandler handler = content -> (String) replaceMap.get(content);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
String file = parser.parse(template);
bw.write(file);
bw.flush();
in.close();
bw.close();
}
/**
* 获取完整地址
* @param dirPrefix
* @param packagePrefix
* @return
*/
protected static String getCompleteDir(String dirPrefix, String packagePrefix) {
String completeDir = "";
if (!StringUtils.isBlank(dirPrefix)) {
completeDir = dirPrefix.concat(".");
}
completeDir = completeDir.concat(packagePrefix);
if (!completeDir.endsWith("/")) {
completeDir = completeDir.concat("/");
}
return completeDir.replaceAll("\\.", "/");
}
}
如上代码所示抽象插件对象,
1 主要负责获取类路径下面的generatorConfig.xml文件,读取文件中配置的数据库连接,生成的包名称,生成的包路径,生成的表名什么的.
2 负责按照配置的制定路径对模版文件进行渲染,输出到指定的配置路径下面
DalRelevantMojo 核型类
@Mojo(name = "risk-commons",defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true)
public class DalRelevantMojo extends AutoCreateFile{
private static final Map<String, String> fieldTypeMapConfig = new HashMap<String, String>() {
{
put("BIGINT", "Long");
put("INT", "Integer");
put("TINYINT", "Integer");
put("VARCHAR", "String");
put("DATETIME", "Date");
put("CHAR", "String");
put("TEXT", "String");
}
};
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
readXMLConfig();
try {
//1.加载驱动程序
Class.forName(dalRelevant.getDriverName());
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(dalRelevant.getUrl(), dalRelevant.getUser(), dalRelevant.getPassword());
//3.操作数据库,实现增删改查
DatabaseMetaData metaData = conn.getMetaData();
Map<String, String> replaceMap = new HashMap<>();
String completeDir = getCompleteDir(dalRelevant.getDirPrefix(), dalRelevant.getPackagePrefix());
replaceMap.put("packagePrefix:", dalRelevant.getPackagePrefix());
replaceMap.put("completeDir", completeDir);
for (Table table: dalRelevant.getTables()) {
try {
Map<String, String> fieldTypeMap = table.getFieldTypeMap();
Map<String, String> columnTypeMap = table.getColumnTypeMap();
ResultSet rs = metaData.getColumns(null, "%", table.getTableName(), "%");
//如果有数据,rs.next()返回true
StringBuilder entityContent = new StringBuilder();
while(rs.next()){
String columnType = rs.getString("TYPE_NAME");
String fieldType = fieldTypeMapConfig.getOrDefault(columnType, "String");
String field = (table.getLineToHump() == null || table.getLineToHump().equals(1)) ? HumpLineUtil.lineToHump(rs.getString("COLUMN_NAME")) : rs.getString("COLUMN_NAME");
if (fieldTypeMap != null && fieldTypeMap.containsKey(field)) {
fieldType = fieldTypeMap.get(field);
} else if (columnTypeMap != null && columnTypeMap.containsKey(fieldType)) {
fieldType = columnTypeMap.get(fieldType);
}
entityContent.append("\t").append("private ").append(fieldType).append(" ").append(field).append(";").append(System.getProperty("line.separator"));
}
//强迫症 去除最后一个换行
entityContent.deleteCharAt(entityContent.length()-1);
String entityName;
if (table.getFileName() != null && StringUtils.isNotBlank(table.getFileName())) {
entityName = table.getFileName();
} else {
String entityNameHump = HumpLineUtil.lineToHump(table.getTableName());
entityName = entityNameHump.substring(0,1).toUpperCase().concat(entityNameHump.substring(1));
}
replaceMap.put("entity", entityName);
replaceMap.put("table", table.getTableName());
replaceMap.put("entityContent", entityContent.toString());
System.out.printf("======dalRelevantFiles参数%s", replaceMap);
//处理entity
replaceMap.put("fileName", entityName);
render("entity", replaceMap, "/entity/");
//处理mapper
replaceMap.put("fileName", entityName.concat("Mapper"));
render("mapper", replaceMap, "/dao/mapper/");
//处理dao
replaceMap.put("fileName", entityName.concat("Dao"));
render("dao", replaceMap, "/dao/");
//处理impl
replaceMap.put("fileName", entityName.concat("DaoImpl"));
render("impl", replaceMap, "/dao/impl/");
} catch (Exception e) {
System.out.printf("====【%s】创建失败%s", table.getTableName(), e.getMessage());
}
}
} catch (Exception e) {
System.out.printf("dal相关文件自动创建失败:%s", e.getMessage());
}
}
}
如上所示是一个maven 插件的实现类,可以看到代码主要就是通过jdbc连接数据库获取表名,字段名进行解析然后生成对应的实体名放到一个map里面调用父类的渲染方法,替换模版中的关键子,把文件输出到指定路径下面.这里面两个工具比较重要说一下
1 驼峰下滑线转化
public class HumpLineUtil {
private static Pattern linePattern = Pattern.compile("_(\\w)");
private static Pattern humpPattern = Pattern.compile("[A-Z]");
/**
* 下划线转驼峰
* @param str
* @return
*/
public static String lineToHump(String str) {
str = str.toLowerCase();
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 驼峰转下划线
* @param str
* @return
*/
public static String humpToLine(String str) {
Matcher matcher = humpPattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
}
2 模版替换来自Mybatis源码
inal Map<String,Object> mapper = new HashMap<String, Object>();
mapper.put("name", "张三");
mapper.put("pwd", "123456");
//先初始化一个handler
TokenHandler handler = new TokenHandler() {
@Override
public String handleToken(String content) {
System.out.println(content);
return (String) mapper.get(content);
}
};
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
System.out.println("************" + parser.parse("用户:${name},你的密码是:${pwd}"));
至此整个代码生成插件就已经处理完了.
四 使用方式
#pom.xml 添加以下内容
<build>
<plugins>
<plugin>
<groupId>cn.xxxx.risk.generator</groupId>
<artifactId>risk-common-maven-plugins</artifactId>
<version>0.0.2</version>
<configuration>
<configurationFile>/Users/worn/code/risk-common-boot-starter/src/test/java/generatorConfig.xml</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
#/Users/worn/code/risk-common-boot-starter/src/test/java/generatorConfig.xml# 内容
<?xml version="1.0" encoding="UTF-8"?>
<generatorProperty>
<dalRelevant>
<commonConfig>
<driverName>com.mysql.cj.jdbc.Driver</driverName>
<url>jdbc:mysql://host:port/test_hll_premier_driver</url>
<user>password</user> <password>BicfmqsrEBUjZU6tSnp25AqaA99B62vb</password>
<packagePrefix>cn.xxxx.risk.driver.access</packagePrefix>
<dirPrefix>xxxxx/src/main/java</dirPrefix>
</commonConfig>
<tables>
<table name="tableName" fileName="Test" lineToHump="">
<fieldTypeMap>
<element field="createdAt" type="Date"></element>
</fieldTypeMap>
<columnTypeMap>
<element origin="Date" target="String"></element>
</columnTypeMap>
</table>
<table name="xxxxx"></table>
</tables>
</dalRelevant>
</generatorProperty>
正如你所期待的双击就可以生成你不想重复写来写去的代码了