1. 程式人生 > >反射+列舉+freemarker,自動生成實體類,自動建表建索引(二)之建表建索引,註解和DatabaseMetaData 獲取資訊

反射+列舉+freemarker,自動生成實體類,自動建表建索引(二)之建表建索引,註解和DatabaseMetaData 獲取資訊

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);
	}

}