反射+列舉+freemarker,自動生成實體類,自動建表建索引(二)之建表建索引,註解和DatabaseMetaData 獲取資訊
阿新 • • 發佈:2019-02-14
package com.test.common; import static com.test.common.EntityConfigData.DEFAULTS; import static com.test.common.EntityConfigData.INDEX; import static com.test.common.EntityConfigData.LENGTH; import static com.test.common.EntityConfigData.NULLABLE; import static com.test.common.EntityConfigData.TYPE; import static com.test.common.EntityConfigData.TYPE_DEFUALT_INT; import static com.test.common.EntityConfigData.TYPE_DEFUALT_LONG; import static com.test.common.EntityConfigData.TYPE_DEFUALT_STRING; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.test.PackageClass; /** * 自動建表建索引類 * @author Lufeng * */ public class GenDB { private String sourceDir; // 配置原始檔夾 public GenDB(String source) { this.sourceDir = source; } /** * 獲取不同型別預設長度 * @param clazz * @param obj * @param type * @return * @throws Exception */ public int getDefaultLength(String type) throws Exception { int length = 0; if(type == null) { throw new RuntimeException("不能識別的型別:" + type); } // 根據不同型別返回不同預設長度 if("int".equals(type)) { length = TYPE_DEFUALT_INT;; } else if("long".equals(type)) { length = TYPE_DEFUALT_LONG; } else if("String".equals(type)) { length = TYPE_DEFUALT_STRING; } return length; } public String getSqlType(String type) { String result = ""; if("int".equals(type)) { result = "integer"; } else if("long".equals(type)) { result = "bigint"; } else if("String".equals(type)) { result = "varchar"; } return result; } /** * 獲取配置類中的所有欄位名 * @param clazz * @return */ public List<String> getColumns(Class<?> clazz) { List<String> result = new ArrayList<String>(); // 獲得所有列舉欄位成員(id, account, name, profession...),並遍歷獲取欄位名 Object[] enums = clazz.getEnumConstants(); result.add("id"); // id是預設新增的 for (Object e : enums) { result.add(e.toString()); } return result; } /** * 獲取所有約束資訊 * @param clazz * @param obj * @return * @throws Exception */ public Map<String, Object> getFieldInfo(Class<?> clazz, Object obj) throws Exception { Map<String, Object> result = new HashMap<String, Object>(); // 獲取所有約束資訊 String name = obj.toString(); String typeName = ((Class<?>) GenUtils.getFieldValue(clazz, obj, TYPE)).getSimpleName(); String type = getSqlType(typeName); int length = (Integer) GenUtils.getFieldValue(clazz, obj, LENGTH); boolean index = (Boolean) GenUtils.getFieldValue(clazz, obj, INDEX); String nullable = (Boolean) GenUtils.getFieldValue(clazz, obj, NULLABLE) == true ? "NULL" : "NOT NULL"; //預設值 Object def = GenUtils.getFieldValue(clazz, obj, DEFAULTS); String defaults = def == null ? "" : " DEFAULT '" + def.toString() + "'"; // 如果長度為0,即沒設長度,則提取預設值 if(length == 0) { length = getDefaultLength(typeName); } result.put("name", name); result.put(TYPE, type); result.put(LENGTH, length); result.put(INDEX, index); result.put(NULLABLE, nullable); result.put(DEFAULTS, defaults); return result; } /** * 獲取表中所有欄位資訊 * @param clazz * @return * @throws Exception */ public List<Map<String, Object>> getTableInfo(Class<?> clazz) throws Exception { List<Map<String, Object>> tableInfo = new ArrayList<Map<String, Object>>(); // 獲得所有列舉欄位成員(id, account, name, profession...),並遍歷獲取資訊 Object[] enums = clazz.getEnumConstants(); for (Object e : enums) { // 獲取欄位約束資訊 Map<String, Object> field = getFieldInfo(clazz, e); tableInfo.add(field); } return tableInfo; } /** * 獲取某個欄位的約束資訊 * @param clazz * @param name * @return * @throws Exception */ public Map<String, Object> getOneFieldInfo(Class<?> clazz, String name) throws Exception { Map<String, Object> fieldInfo = new HashMap<String, Object>(); //返回所有列舉型別 Enum<?>[] enums = (Enum[]) clazz.getEnumConstants(); for (Enum<?> e : enums) { // 如果不是想要的欄位資訊, 則跳過 if(!e.toString().equals(name)) { continue; } // 獲取欄位約束資訊 fieldInfo = getFieldInfo(clazz, e); } return fieldInfo; } /** * 獲取配置表中需要建立索引的欄位 * @param clazz * @return * @throws Exception */ public List<String> getIndexField(Class<?> clazz) throws Exception { List<String> result = new ArrayList<String>(); result.add("id"); // 預設id是索引 // 找出class中所有需要建立索引的欄位 Object[] fields = clazz.getEnumConstants(); for(Object f : fields){ boolean index = (Boolean) GenUtils.getFieldValue(clazz, f, INDEX); if(index) result.add(f.toString()); } return result; } /** * 在表上建立索引 * @param conn * @param tableName * @param clazz * @param columns * @throws SQLException */ public void checkCreateIndex(Connection conn, String tableName, Class<?> clazz) throws Exception { // 反射獲取配置中待建立索引的列 List<String> indexConfs = getIndexField(clazz); // 表中加索引的列資訊 List<String> indexTables = new ArrayList<String>(); DatabaseMetaData dbMeta = conn.getMetaData(); String schema = null; // 獲取表中索引資訊 ResultSet indexs = dbMeta.getIndexInfo(null, schema, tableName, false, true); while(indexs.next()) { indexTables.add(indexs.getString("COLUMN_NAME")); } indexs.close(); // 若資料表索引包含配置類中全部索引,則不用建索引,直接返回 if(indexTables.containsAll(indexConfs)) { return ; } // 找出配置中有,資料表中沒有的索引 List<String> indexDifs = new ArrayList<String>(); for(String i : indexConfs) { if(!indexTables.contains(i)) { indexDifs.add(i); } } // 建立索引 Statement st = conn.createStatement(); for(String column : indexDifs) { String indexSql = "CREATE INDEX " + tableName + "_" + column + " ON " + tableName +"(" + column + ")"; System.out.println("建索引: " + indexSql); st.executeUpdate(indexSql); } st.close(); } /** * 建表操作 * @param conn * @param tableName * @param clazz * @throws Exception */ public void createTable(Connection conn, String tableName, Class<?> clazz) throws Exception { // 拼成SQL語句 StringBuilder sql = new StringBuilder(); sql.append("CREATE TABLE `").append(tableName).append("`"); // 建表 sql.append("("); sql.append("`id` bigint(20) NOT NULL,"); // 建立預設主鍵 // 獲取並遍歷配置表字段 List<Map<String, Object>> tableInfo = getTableInfo(clazz); for(Map<String, Object> t : tableInfo) { sql.append("`").append(t.get("name")).append("` "); // 欄位名 sql.append(t.get(TYPE)); // 型別 sql.append("(").append(t.get(LENGTH)).append(") "); // 長度 sql.append(t.get(NULLABLE)); // 是否為空 sql.append(t.get(DEFAULTS)); // 預設值 sql.append(","); } sql.append("PRIMARY KEY (`id`)"); // 設定主鍵 sql.append(")"); System.out.println("\n建表: " + sql); // 執行建表操作 Statement st = conn.createStatement(); st.executeUpdate(sql.toString()); st.close(); // 建索引 checkCreateIndex(conn, tableName, clazz); } /** * 更新表操作 * @param con * @param tableName * @param clazz * @throws Exception */ public void updateTable(Connection con, String tableName, Class<?> clazz) throws Exception { //獲取表中列資訊 DatabaseMetaData dBMetaData = con.getMetaData(); ResultSet colSet = dBMetaData .getColumns(null, "%", tableName, "%"); //表中已有的列名 List<String> colTables = new ArrayList<String>(); while(colSet.next()) { colTables.add(colSet.getString("COLUMN_NAME")); } colSet.close(); //配置中的列名 List<String> colConfs = getColumns(clazz); // 如果資料表中列名包含配置表中全部列名, 則檢查建立索引,不用更新表,直接返回 if(colTables.containsAll(colConfs)){ checkCreateIndex(con, tableName, clazz); return; } // 找出兩表列名不同 List<String> colDifs = new ArrayList<String>(); for(String col : colConfs) { if(!colTables.contains(col)) { colDifs.add(col); } } // 取得配置中的表字段資訊, 拼成SQL語句 StringBuffer sql = new StringBuffer(); sql.append("ALTER TABLE `").append(tableName).append("` "); // 更新表 for(int i = 0; i < colDifs.size(); i++) { String col = colDifs.get(i); Map<String, Object> field = getOneFieldInfo(clazz, col); if(i > 0) sql.append(", "); sql.append("ADD `").append(col).append("` "); // 增加列名 sql.append(field.get(TYPE)); // 型別 sql.append("(").append(field.get(LENGTH)).append(") "); // 長度 sql.append(field.get(NULLABLE)); // 是否為空 sql.append(field.get(DEFAULTS)); // 預設值 } System.out.println("\n更新表: " + sql.toString()); // 更新表操作 Statement st = con.createStatement(); st.executeUpdate(sql.toString()); st.close(); // 建索引 checkCreateIndex(con, tableName, clazz); } // TODO 資料庫連線方面需要改進 private static Connection getDBConnection(String driver, String urlDB, String user, String pwd) throws Exception { // 連線MYSQL資料庫 Class.forName(driver); Connection conn = DriverManager.getConnection(urlDB, user, pwd); return conn; } /** * 根據配置原始檔夾檢查建資料表 */ public void genDB(Connection conn) { try { // 獲取原始檔夾下的所有類 Set<Class<?>> sources = PackageClass.find(sourceDir); // 遍歷所有類,取出有註解的生成實體類 for(Class<?> clazz : sources) { // 過濾沒有EntityConfig註解的類, 並建表 if(clazz.isAnnotationPresent(EntityConfig.class)) { checkAndCreat(clazz, conn); } } // 關閉連線 conn.close(); } catch (Exception e) { throw new RuntimeException(e); } } /** * 檢查並建表或更新表結構 * @param clazz * @throws Exception */ public void checkAndCreat(Class<?> clazz, Connection conn) throws Exception { // 獲取表的資訊 String catalog = null; String schema = "%"; String tableName = GenUtils.getTableName(clazz); String[] types = new String[] { "TABLE" }; DatabaseMetaData dBMetaData = conn.getMetaData(); // 從databaseMetaData獲取表資訊 ResultSet tableSet = dBMetaData.getTables(catalog, schema, tableName, types); // 如果表不存在, 則建表 if (!tableSet.next()) { createTable(conn, tableName, clazz); } else { //表存在, 則更新表 updateTable(conn, tableName, clazz); } // 關閉資料庫連線 tableSet.close(); } public static void main(String[] args) throws Exception { args = new String[] {"com.test.testentity"}; String sourceDir = args[0]; String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/game"; String user = "root"; String pwd = ""; Connection conn = getDBConnection(driver, url, user, pwd); GenDB db = new GenDB(sourceDir); db.genDB(conn); } }