// 现获取DatabaseMetaData Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "root"; Connection con = DriverManager.getConnection(url, user, password); DatabaseMetaData dbMetaData = con.getMetaData(); // 表信息 /** * 每个表描述都有以下列: * TABLE_CAT String => 表类别(可为 null) * TABLE_SCHEM String => 表模式(可为 null) * TABLE_NAME String => 表名称 * TABLE_TYPE String => 表类型。典型的类型是 "TABLE"、"VIEW"、"SYSTEM TABLE"、"GLOBAL TEMPORARY"、"LOCAL TEMPORARY"、"ALIAS" 和 "SYNONYM"。 * REMARKS String => 表的解释性注释 * TYPE_CAT String => 类型的类别(可为 null) * TYPE_SCHEM String => 类型模式(可为 null) * TYPE_NAME String => 类型名称(可为 null) * SELF_REFERENCING_COL_NAME String => 有类型表的指定 "identifier" 列的名称(可为 null) * REF_GENERATION String => 指定在 SELF_REFERENCING_COL_NAME 中创建值的方式。这些值为 "SYSTEM"、"USER" 和 "DERIVED"。(可能为 null) * */ // table type. Typical types are "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM". String[] types = { "TABLE" }; ResultSet rs = dbMetaData.getTables(null, schemaName, "%", types); while (rs.next()) { String tableName = rs.getString("TABLE_NAME"); //表名 String tableType = rs.getString("TABLE_TYPE"); //表类型 String remarks = rs.getString("REMARKS"); //表备注 System.out.println(tableName + "-" + tableType + "-" + remarks); } //获得表或视图中的所有列信息 /** * 每个列描述都有以下列: * * TABLE_CAT String => 表类别(可为 null) * TABLE_SCHEM String => 表模式(可为 null) * TABLE_NAME String => 表名称 * COLUMN_NAME String => 列名称 * DATA_TYPE int => 来自 java.sql.Types 的 SQL 类型 * TYPE_NAME String => 数据源依赖的类型名称,对于 UDT,该类型名称是完全限定的 * COLUMN_SIZE int => 列的大小。 * BUFFER_LENGTH 未被使用。 * DECIMAL_DIGITS int => 小数部分的位数。对于 DECIMAL_DIGITS 不适用的数据类型,则返回 Null。 * NUM_PREC_RADIX int => 基数(通常为 10 或 2) * NULLABLE int => 是否允许使用 NULL。 * columnNoNulls - 可能不允许使用 NULL 值 * columnNullable - 明确允许使用 NULL 值 * columnNullableUnknown - 不知道是否可使用 null * REMARKS String => 描述列的注释(可为 null) * COLUMN_DEF String => 该列的默认值,当值在单引号内时应被解释为一个字符串(可为 null) * SQL_DATA_TYPE int => 未使用 * SQL_DATETIME_SUB int => 未使用 * CHAR_OCTET_LENGTH int => 对于 char 类型,该长度是列中的最大字节数 * ORDINAL_POSITION int => 表中的列的索引(从 1 开始) * IS_NULLABLE String => ISO 规则用于确定列是否包括 null。 * YES --- 如果参数可以包括 NULL * NO --- 如果参数不可以包括 NULL * 空字符串 --- 如果不知道参数是否可以包括 null * SCOPE_CATLOG String => 表的类别,它是引用属性的作用域(如果 DATA_TYPE 不是 REF,则为 null) * SCOPE_SCHEMA String => 表的模式,它是引用属性的作用域(如果 DATA_TYPE 不是 REF,则为 null) * SCOPE_TABLE String => 表名称,它是引用属性的作用域(如果 DATA_TYPE 不是 REF,则为 null) * SOURCE_DATA_TYPE short => 不同类型或用户生成 Ref 类型、来自 java.sql.Types 的 SQL 类型的源类型(如果 DATA_TYPE 不是 DISTINCT 或用户生成的 REF,则为 null) * IS_AUTOINCREMENT String => 指示此列是否自动增加 * YES --- 如果该列自动增加 * NO --- 如果该列不自动增加 * 空字符串 --- 如果不能确定该列是否是自动增加参数 * */ ResultSet rs = dbMetaData.getColumns(null, schemaName, tableName, "%"); while (rs.next()){ String tableCat = rs.getString("TABLE_CAT");//表目录(可能为空) String tableSchemaName = rs.getString("TABLE_SCHEM");//表的架构(可能为空) String tableName_ = rs.getString("TABLE_NAME");//表名 String columnName = rs.getString("COLUMN_NAME");//列名 } //索引信息 /** * 每个索引列描述都有以下列: * * TABLE_CAT String => 表类别(可为 null) * TABLE_SCHEM String => 表模式(可为 null) * TABLE_NAME String => 表名称 * NON_UNIQUE boolean => 索引值是否可以不唯一。TYPE 为 tableIndexStatistic 时索引值为 false * INDEX_QUALIFIER String => 索引类别(可为 null);TYPE 为 tableIndexStatistic 时索引类别为 null * INDEX_NAME String => 索引名称;TYPE 为 tableIndexStatistic 时索引名称为 null * TYPE short => 索引类型: * tableIndexStatistic - 此标识与表的索引描述一起返回的表统计信息 * tableIndexClustered - 此为集群索引 * tableIndexHashed - 此为散列索引 * tableIndexOther - 此为某种其他样式的索引 * ORDINAL_POSITION short => 索引中的列序列号;TYPE 为 tableIndexStatistic 时该序列号为零 * COLUMN_NAME String => 列名称;TYPE 为 tableIndexStatistic 时列名称为 null * ASC_OR_DESC String => 列排序序列,"A" => 升序,"D" => 降序,如果排序序列不受支持,可能为 null;TYPE 为 tableIndexStatistic 时排序序列为 null * CARDINALITY int => TYPE 为 tableIndexStatistic 时,它是表中的行数;否则,它是索引中唯一值的数量。 * PAGES int => TYPE 为 tableIndexStatisic 时,它是用于表的页数,否则它是用于当前索引的页数。 * FILTER_CONDITION String => 过滤器条件,如果有的话。(可能为 null) * */ ResultSet rs = dbMetaData.getIndexInfo(null, schemaName, tableName, true, true); while (rs.next()){ boolean nonUnique = rs.getBoolean("NON_UNIQUE");//非唯一索引(Can index values be non-unique. false when TYPE is tableIndexStatistic ) String indexQualifier = rs.getString("INDEX_QUALIFIER");//索引目录(可能为空) String indexName = rs.getString("INDEX_NAME");//索引的名称 short type = rs.getShort("TYPE");//索引类型 String columnName = rs.getString("COLUMN_NAME");//列名 String ascOrDesc = rs.getString("ASC_OR_DESC");//列排序顺序:升序还是降序 } // 还有主键信息,外键信息,视图信息等就不一一列举了。。。。
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); } }
/* * 元注解@Target,@Retention,@Documented,@Inherited * * @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括: * ElemenetType.CONSTRUCTOR 构造器声明 * ElemenetType.FIELD 域声明(包括 enum 实例) * ElemenetType.LOCAL_VARIABLE 局部变量声明 * ElemenetType.METHOD 方法声明 * ElemenetType.PACKAGE 包声明 * ElemenetType.PARAMETER 参数声明 * ElemenetType.TYPE 类,接口(包括注解类型)或enum声明 * * @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括: * RetentionPolicy.SOURCE 注解将被编译器丢弃 * RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃 * RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。 * * @Documented 将此注解包含在 javadoc 中 * * @Inherited 允许子类继承父类中的注解 * */