這只是一個簡單的解析數據庫建表語句以後,根據解析結果生成java文件的一個簡單工具。寫的緣由有兩個。java
1:項目中沒有人寫實體類的註釋,字段的註釋,現有的工具也無法根據數據庫的註釋自動添加到class文件上。mysql
2:本身寫一個也彷佛不是很難。git
因此就本身寫了一個。github
這裏在生成java文件的時候用的是freemarker。用了jdbc做爲執行sql的工具。sql
這個項目已經放在github上了,地址:github.com/hjx60149632… 。數據庫
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
複製代碼
想要根據數據庫中的建表語句來建立java文件首先要先知道數據庫中都有那些表。so,開始。bash
在獲取數據庫連接前,咱們先寫一個文件用來保存數據庫連接的各類信息。mvc
新建文件config.xmlapp
<xml>
<jdbc.url></jdbc.url>
<jdbc.username></jdbc.username>
<jdbc.password></jdbc.password>
</xml>
複製代碼
jdbc.url:連接數據庫的url。例如:jdbc:mysql://127.0.0.1:3306/demo?useSSL=true框架
jdbc.username:數據庫的用戶名。
jdbc.password:數據庫的密碼。
這樣就配置好了。
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Scanner;
/** * 讀取xml */
public class XmlUtils {
/** * 讀取 Document * * @param xmlPath * @return */
public static Document getConfigDocument(String xmlPath) {
try {
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(xmlPath);
Scanner scanner = new Scanner(resourceAsStream);
StringBuilder stringBuilder = new StringBuilder();
while (scanner.hasNextLine()) {
stringBuilder.append(scanner.nextLine()).append("\n");
}
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilde = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilde.parse(new ByteArrayInputStream(stringBuilder.toString().getBytes()));
return document;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
複製代碼
//獲取數據庫配置信息
Document configXml = XmlUtils.getConfigDocument(CONFIG_PATH);
Element element = configXml.getDocumentElement();
String jdbcUrl = element.getElementsByTagName("jdbc.url").item(0).getTextContent();
String username = element.getElementsByTagName("jdbc.username").item(0).getTextContent();
String password = element.getElementsByTagName("jdbc.password").item(0).getTextContent();
//打開數據庫連接
Connection conn = (Connection) DriverManager.getConnection(jdbcUrl, username, password);
複製代碼
獲取到連接以後,下一步就是要讀取數據庫中的表數據了。
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SHOW TABLES;");
while (resultSet.next()) {
//這裏就獲取到了數據庫中的全部的表的名稱了。
String tableName = resultSet.getString(1);
}
複製代碼
這裏拿到表名稱後就能夠依次獲得建表語句,並解析建表語句了。
這裏之因此使用解析建表語句的方法是由於這樣能夠比較完整的獲得註釋信息。用另外一種方法的時候表的註釋一直獲取不到(另外一種方法我忘記怎麼說了~)。
這裏就是拼接sql,而後執行就行了。下面是代碼:
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("show CREATE TABLE 表名稱");
while (resultSet.next()) {
//這裏就獲得了表的建表語句
String createTableSql = resultSet.getString(2);
}
複製代碼
作到這一步,咱們就完整的獲得了數據庫中的全部的建表語句了。接下來就是要分析建表語句而且用來生成實體類了。
下面是執行 sql show create table user 的結果:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
`name` varchar(225) DEFAULT NULL COMMENT '用戶名',
`create_date` datetime DEFAULT NULL,
`status` int(11) DEFAULT NULL,
`age` int(11) DEFAULT NULL COMMENT '年齡',
`mark` varchar(225) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2104778081 DEFAULT CHARSET=latin1 COMMENT='用戶表'
複製代碼
這裏能夠看出,要建立的實體類的名稱就在第一行的CREATE TABLE 後的兩個**`**符號中間,這樣咱們就能夠經過正則將表的名稱取出來,而後轉換成爲咱們須要的class名稱。首先咱們先寫一個經過正則提取數據的方法,下面是代碼:
/** * 根據正則查找 * * @param sql * @param pattern * @param group * @return */
static String getByPattern(String sql, String pattern, int group) {
Pattern compile = Pattern.compile(pattern);
Matcher matcher = compile.matcher(sql);
while (matcher.find()) {
return matcher.group(group);
}
return null;
}
複製代碼
如今開始從建表語句中提取table的名稱(雖然在得到數據庫全部表的時候就已經知道了,可是在寫一次也沒有什麼問題不是嗎 ~~~),下面是代碼:
/** * 得到表的名稱 * * @param sql * @return */
public static String getTableName(String sql) {
return getByPattern(sql, "CREATE TABLE `(.*)`", 1);
}
複製代碼
這裏就已經將表的名稱取出來了。 接下來是獲取表上的註釋,這裏咱們取表的註釋。下面是代碼:
public static String getTableComment(String sql) {
return getByPattern(sql, "\\) .* COMMENT='(.*)'", 1);
}
複製代碼
如今開始獲取id信息。
獲取id依然是使用正則就行了,代碼以下:
public static String getId(String sql) {
return getByPattern(sql, "PRIMARY KEY \\(`(.*)`\\)", 1);
}
複製代碼
由於本來語句中有一對括號,因此在這裏對外面的括號作了轉義處理。接下來開始提取數據庫中的字段,字段類型,字段註釋。
2019年01月21日,修改:
沒想到有的表裏沒有id,就致使下面的代碼執行後出錯了,修改一下結束的判斷。
/** * 獲取建表語句中和字段相關的sql * * @param sql * @return */
public static List<String> getColumnSqls(String sql) {
List<String> lines = new ArrayList<>();
Scanner scanner = new Scanner(sql);
boolean start = false;
while (scanner.hasNextLine()) {
String nextLine = scanner.nextLine();
if (nextLine.indexOf("CREATE TABLE") != -1) {
start = true;
continue;
}
//沒想到有的表沒有id /(ㄒoㄒ)/~~
if (nextLine.indexOf("PRIMARY KEY") != -1 || nextLine.indexOf("ENGINE=") != -1) {
start = false;
continue;
}
if (start) {
lines.add(nextLine);
}
}
return lines;
}
複製代碼
這裏的運行結果是:
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
`name` varchar(225) DEFAULT NULL COMMENT '用戶名',
`create_date` datetime DEFAULT NULL,
`status` int(11) DEFAULT NULL,
`age` int(11) DEFAULT NULL COMMENT '年齡',
`mark` varchar(225) DEFAULT NULL,
複製代碼
這也就取到了table中全部的字段相關信息了,接下來咱們來獲取字段名稱:
List<String> columns = SqlUtils.getColumnSqls(sql);
for (String oneLine : columns) {
System.out.println(oneLine);
String columnName = SqlUtils.getByPattern(oneLine, "`(.*)`", 1);
String comment = SqlUtils.getByPattern(oneLine, "COMMENT '(.*)'", 1);
String columnType = SqlUtils.getByPattern(oneLine, "`" + columnName + "` ([A-Za-z]*)", 1);
System.out.printf("名稱:%-20s 類型:%-20s 註釋:%-20s \n", columnName, columnType, comment);
}
複製代碼
輸出結果:
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
名稱:id 類型:int 註釋:用戶id
`name` varchar(225) DEFAULT NULL COMMENT '用戶名',
名稱:name 類型:varchar 註釋:用戶名
`create_date` datetime DEFAULT NULL,
名稱:create_date 類型:datetime 註釋:null
`status` int(11) DEFAULT NULL,
名稱:status 類型:int 註釋:null
`age` int(11) DEFAULT NULL COMMENT '年齡',
名稱:age 類型:int 註釋:年齡
`mark` varchar(225) DEFAULT NULL,
名稱:mark 類型:varchar 註釋:null
複製代碼
到了這,我就就已經從建表語句裏拿到了全部須要的數據了,下面就開始使用這些數據來生成java文件了。
終於開始要建立java文件了。
可是~在建立java文件的時候要先吧以前獲取的數稍微處理一下,將sql中的格式轉換爲java中的格式。好比屬性名稱,數據類型,class名稱之類的,如今開始~
就是首字母大寫,駝峯式的命名規範。例如將user_log或者USER_LOG轉換爲UserLog。
咱們能夠這麼寫:
/** * 類名稱轉換 * * @param tableName * @return */
public static String entityName(String tableName) {
String lowerCaseName = tableName.toLowerCase();
StringBuilder newName = new StringBuilder();
char[] chars = lowerCaseName.toCharArray();
boolean change = false;
for (int i = 0; i < chars.length; i++) {
char aChar = chars[i];
if (aChar == '_' && !change) {
change = true;
continue;
}
//首字母大寫
if (i == 0) {
aChar = Character.toUpperCase(aChar);
}
if (change) {
aChar = Character.toUpperCase(aChar);
change = false;
}
newName.append(aChar);
}
return newName.toString();
}
複製代碼
這樣就獲得了咱們須要的class的名稱了。
這裏就是將上一步操做的首字母大寫去掉就行了,下面是代碼:
/** * 屬性名稱轉換 * * @param name * @return */
public static String fieldName(String name) {
name = name.toLowerCase();
StringBuilder newName = new StringBuilder();
char[] chars = name.toCharArray();
boolean change = false;
for (int i = 0; i < chars.length; i++) {
char aChar = chars[i];
if (aChar == '_' && !change) {
change = true;
continue;
}
if (change) {
aChar = Character.toUpperCase(aChar);
change = false;
}
newName.append(aChar);
}
return newName.toString();
}
複製代碼
接下來是將sql中的數據類型轉換爲java中的數據類型。
這裏用map作了一個映射,有本身特定要求的能夠本身修改。
public class ColumnFieldTypeMapping {
private Map<String, Class> sqlFieldTypeMapping = new HashMap<>();
{
sqlFieldTypeMapping.put("VARCHAR", String.class);
sqlFieldTypeMapping.put("CHAR", String.class);
sqlFieldTypeMapping.put("TEXT", String.class);
sqlFieldTypeMapping.put("MEDIUMTEXT", String.class);
sqlFieldTypeMapping.put("LONGTEXT", String.class);
sqlFieldTypeMapping.put("TINYTEXT", String.class);
sqlFieldTypeMapping.put("BIT", Boolean.class);
sqlFieldTypeMapping.put("INT", int.class);
sqlFieldTypeMapping.put("BIGINT", long.class);
sqlFieldTypeMapping.put("DOUBLE", double.class);
sqlFieldTypeMapping.put("TINYINT", int.class);
sqlFieldTypeMapping.put("FLOAT", float.class);
sqlFieldTypeMapping.put("DECIMAL", BigDecimal.class);
sqlFieldTypeMapping.put("INT UNSIGNED", int.class);
sqlFieldTypeMapping.put("BIGINT UNSIGNED", int.class);
sqlFieldTypeMapping.put("DECIMAL UNSIGNED", BigDecimal.class);
sqlFieldTypeMapping.put("DATETIME", Date.class);
sqlFieldTypeMapping.put("TIME", Date.class);
sqlFieldTypeMapping.put("DATE", Date.class);
sqlFieldTypeMapping.put("TIMESTAMP", Date.class);
}
/** * 根據sql數據類型獲取Java數據類型 * * @param columnType * @return */
public Class getFieldType(String columnType) {
Class aClass = sqlFieldTypeMapping.get(columnType);
if (aClass == null) {
return sqlFieldTypeMapping.get(columnType.toUpperCase());
}
return null;
}
}
複製代碼
寫到這裏,全部參與生成java文件的信息就已經獲取完成了。
這時候咱們須要把他們組裝起來,用來放進freemarker中來解析並生成java文件中的內容。
這裏可能我之後用這個代碼幹別的事情因此我建了兩個類,一個是ClassModel.java,一個是EntityModel.java。
EntityModel繼承了ClassModel。咱們主要用的是EntityModel.java。下面是代碼:
import java.util.*;
/** * 用於生成java Entity文件的類 */
public class ClassModel {
/** * java 中不須要引包的類型 */
private static List<Class> baseClass = Arrays.asList(
int.class,
double.class,
float.class,
long.class,
short.class,
byte.class,
char.class,
boolean.class,
String.class
);
/** * 類註釋 */
private String classDoc;
/** * 類名 */
private String className;
/** * 類 包名 */
private String packageName;
/** * K:屬性名稱 * V:屬性類型 */
private Map<String, Class> fields = new HashMap<>();
/** * 屬性的註釋 */
private Map<String, String> fieldDoc = new HashMap<>();
;
private List<Class> imports = new ArrayList<>();
/** * 添加須要導入的包 * * @param importClass */
public void addImport(Class importClass) {
if (baseClass.indexOf(importClass) != -1) {
return;
}
if (imports.indexOf(importClass) == -1) {
imports.add(importClass);
}
}
/** * 添加屬性 * * @param fieldName 屬性名稱 * @param fieldClass 屬性類型 */
public void addfield(String fieldName, Class fieldClass) {
if (!fields.containsKey(fieldName)) {
fields.put(fieldName, fieldClass);
}
}
/** * 添加屬性註釋 * * @param fieldName 屬性名稱 * @param fieldDoc 屬性註釋 */
public void addfieldDoc(String fieldName, String fieldDoc) {
if (!this.fieldDoc.containsKey(fieldName)) {
this.fieldDoc.put(fieldName, fieldDoc);
}
}
public List<Class> getImports() {
return imports;
}
public void setImports(List<Class> imports) {
this.imports = imports;
}
public String getClassDoc() {
return classDoc;
}
public void setClassDoc(String classDoc) {
this.classDoc = classDoc;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public Map<String, Class> getFields() {
return fields;
}
public void setFields(Map<String, Class> fields) {
this.fields = fields;
}
public Map<String, String> getFieldDoc() {
return fieldDoc;
}
public void setFieldDoc(Map<String, String> fieldDoc) {
this.fieldDoc = fieldDoc;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append(" \"classDoc\"=\"").append(classDoc).append('\"');
sb.append(", \"className\"=\"").append(className).append('\"');
sb.append(", \"packageName\"=\"").append(packageName).append('\"');
sb.append(", \"fields\"=").append(fields);
sb.append(", \"fieldDoc\"=").append(fieldDoc);
sb.append(", \"imports\"=").append(imports);
sb.append('}');
return sb.toString();
}
}
複製代碼
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** * 數據庫映射 */
public class EntityModel extends ClassModel {
/** * 數據庫名稱 */
private String tableName;
/** * 數據庫中的Id字段名稱 */
private List<String> idColumnNames = new ArrayList<>();
/** * 類屬性名對應數據庫字段映射 * key: class 屬性名稱 * value:數據庫字段名 */
private Map<String, String> fieldSqlName = new HashMap<>();
/** * 添加class 屬性映射和 數據庫 字段映射 * * @param fieldName * @param sqlName */
public void addfieldSqlName(String fieldName, String sqlName) {
if (!fieldSqlName.containsKey(fieldName)) {
fieldSqlName.put(fieldName, sqlName);
}
}
/** * 添加id字段名 * * @param idColumnName */
public void addIdColumnName(String idColumnName) {
idColumnNames.add(idColumnName);
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Map<String, String> getFieldSqlName() {
return fieldSqlName;
}
public void setFieldSqlName(Map<String, String> fieldSqlName) {
this.fieldSqlName = fieldSqlName;
}
public List<String> getIdColumnNames() {
return idColumnNames;
}
public void setIdColumnNames(List<String> idColumnNames) {
this.idColumnNames = idColumnNames;
}
}
複製代碼
在這裏將從數據庫中獲得的數據都組裝好,就可使用freemarker來生成Java文件的內容了。下面是代碼:
/** * 根據建表語句組裝EntityModel * * @param createTableSql * @return */
EntityModel makeModelBySql(String createTableSql) {
Formatter formatter = new Formatter();
EntityModel model = new EntityModel();
String tableComment = SqlUtils.getTableComment(createTableSql);
String tableName = SqlUtils.getTableName(createTableSql);
String id = SqlUtils.getId(createTableSql);
model.addIdColumnName(id);
model.setClassName(NameConvert.entityName(tableName));
model.setTableName(tableName);
//註釋是null的時候用數據庫表名做爲註釋
model.setClassDoc(tableComment == null ? tableName : tableComment);
List<String> line = SqlUtils.getColumnSqls(createTableSql);
for (String oneLine : line) {
String columnName = SqlUtils.getByPattern(oneLine, "`(.*)`", 1);
String comment = SqlUtils.getByPattern(oneLine, "COMMENT '(.*)'", 1);
String columnType = SqlUtils.getByPattern(oneLine, "`" + columnName + "` ([A-Za-z]*)", 1);
String fieldName = NameConvert.fieldName(columnName);
Class fieldClass = columnFieldTypeMapping.getFieldType(columnType);
if (fieldClass == null) {
formatter.format("table:%s columnName:%s sql類型:%s 沒有映射類型", tableName, columnName, columnType);
throw new UnsupportedOperationException(formatter.toString());
}
model.addfield(fieldName, fieldClass);
//字段註釋是null的時候用數據庫字段名做爲註釋
model.addfieldDoc(fieldName, comment == null ? columnName : comment);
model.addfieldSqlName(fieldName, columnName);
model.addImport(fieldClass);
}
return model;
}
複製代碼
這樣一個咱們須要的參數就組裝好了。如今開始編寫freemarker用的代碼。
用來加載freemarker模板和處理模板中的參數。FreeMarkerUtils.java,代碼以下:
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
import java.util.Scanner;
public class FreeMarkerUtils {
/** * freemarker工具, * * @param subjectParams * @param templetPath * @return * @throws Exception */
public static String getJavaClass(Object subjectParams, String templetPath) throws Exception {
StringTemplateLoader loader = new StringTemplateLoader();
Scanner scanner = new Scanner(Thread.currentThread().getContextClassLoader().getResourceAsStream(templetPath));
StringBuilder builder = new StringBuilder();
while (scanner.hasNext()) {
builder.append(scanner.nextLine()).append("\n");
}
String name = System.currentTimeMillis() + "";
loader.putTemplate(name, builder.toString());
//第一步:實例化Freemarker的配置類
Configuration conf = new Configuration();
conf.setObjectWrapper(new DefaultObjectWrapper());
conf.setLocale(Locale.CHINA);
conf.setDefaultEncoding("utf-8");
conf.setTemplateLoader(loader);
//處理空值爲空字符串
conf.setClassicCompatible(true);
Template template = conf.getTemplate(name);
Writer out = new StringWriter(2048);
template.process(subjectParams, out);
String javaClass = out.toString();
return javaClass;
}
}
複製代碼
如今有了工具類以後,還不能當即開始生成java文件,由於還要繼續設置java的package和生成文件的路徑,這時候咱們能夠修改以前寫的config.xml
<xml>
<jdbc.url></jdbc.url>
<jdbc.username></jdbc.username>
<jdbc.password></jdbc.password>
<basePath>/home/hjx/work/demo/src/main/java</basePath>
<entityPackage>top.hejiaxuan.demo.entity</entityPackage>
</xml>
複製代碼
這裏添加了兩個參數:basePath和entityPackage。一個是要生成java的文件的路徑,一個是java文件的包名。
而後咱們再寫一個寫出文件的工具類FileUtils.java
import java.io.*;
public class FileUtils {
/** * 寫入文件 * * @param path 文件路徑 * @param content 文件內容 */
public static void write(String path, String content) {
File file = new File(path);
File parentFile = file.getParentFile();
try {
if (!parentFile.exists()) {
parentFile.mkdirs();
}
if (!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(content);
fileWriter.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
這樣就萬事具有,就差生成文件啦。下面就開始啦~~~
在生成文件前,咱們還須要把basePath,entityPackage從配置文件裏取出來,這一步我就不寫了~~
static final String DOT = ".";
static final String FILE_TYPE = ".java";
static final String ENTITY_TEMPLET_PATH = "EntityTemp.ftl";
/** * 用於生成一個類文件 * * @param entityModel * @return */
boolean makeOneClass(EntityModel entityModel) {
entityModel.setPackageName(entityPackage);
String filePath = basePath + "/" + entityPackage.replace(DOT, "/") + "/" + entityModel.getClassName() + FILE_TYPE;
try {
String javaClassString = FreeMarkerUtils.getJavaClass(entityModel, ENTITY_TEMPLET_PATH);
FileUtils.write(filePath, javaClassString);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
複製代碼
好啦~~~大功告成。
好像少點啥~~~
模板文件沒有放出來~~~
編寫EntityTemp.ftl
package ${packageName};
<#--導入的包-->
<#list imports as import>
import ${import.name};
</#list>
<#--類名-->
<#if classDoc?length gt 0>
/**
* ${classDoc}
* @author hejiaxuan
*/
</#if>
public class ${className} {
<#--屬性名稱-->
<#list fields?keys as key>
<#assign fieldDocStr = fieldDoc[key]>
<#if fieldDocStr?length gt 0>
/**${fieldDocStr}*/
</#if>
<#if idColumnNames?seq_contains(fieldSqlName[key])>
</#if>
private ${fields[key].simpleName} ${key};
</#list>
<#list fields?keys as key>
<#assign fieldClass = fields[key].simpleName>
<#--setter-->
public void set${key?cap_first}(${fieldClass} ${key}) {
this.${key} = ${key};
}
<#--getter-->
public ${fieldClass} <#if fieldClass="boolean">is<#else>get</#if>${key?cap_first}() {
return this.${key};
}
</#list>
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("[");
<#list fields?keys as key>
sb.append("${key}:").append(${key}).append("; ");
</#list>
sb.append("]");
return sb.toString();
}
}
複製代碼
這裏面我只是貼出來了一些要用到的代碼片斷,沒有將全部的代碼所有寫出來。其實寫工具就是一個慢慢實現本身思路的過程,有思路的話一切都很簡單。
若是有人須要項目所有代碼的話請到 github.com/hjx60149632… 自行查看。
以爲我寫的還行的話請看看其餘的文章吧^_^