有了一個框架,只需要配置好數據庫連接,就可以在java代碼層操控database,對於寫個model便在數據庫中創建了一張表而感到十分神奇,隱約想起以前看《Thinking in Java》中關於注解(Annotation)一張中對於自動生成SQL語句的操作。
首先略微介紹下注解(亦稱為與數據metadata(ORM-對象/關系映射中的核心))。
Annotation源自JavaSE1.5,內置3個標准注解,4個元注解:
(1)java.lang.*中的@Override,@Deprecated, @SuppressWarnings
(2)java.lang.annotations.*中的@Target, @Inherited, @Retention, @Documented
對於后4個元注解,稍后再在代碼中解釋。
對於一個創建表的SQL Create語句,我們要確定幾個元素:表名,列名,列名類型,類型長度,約束等,這些都可以在實體類的屬性加以注解說明來實現。
對於表名注解:

1 1 package annotiation; 2 2 import java.lang.annotation.*; 3 3 4 4 @Inherited // 允許子類繼承父類中的注解 5 5 @Documented // 將此注解包含在Javadoc中 6 6 @Target(ElementType.TYPE) // 類、接口(包括注解類型)或枚舉類型聲明 7 7 @Retention(RetentionPolicy.RUNTIME) // VM在運行時保留注解,從而通過反射獲取信息 8 8 9 9 public @interface DBTable { 10 10 public String name() default ""; // 注解未賦值是,默認為空 11 11 }
對於字段注解:(這邊先只設定了String類型,其實實際情況沒這么單純,下篇再優化)

1 1 package annotiation; 2 2 import java.lang.annotation.*; 3 3 4 4 @Inherited 5 5 @Documented 6 6 @Target(ElementType.FIELD) // 域聲明(包括枚舉類型實例) 7 7 @Retention(RetentionPolicy.RUNTIME) 8 8 9 9 public @interface SQLInteger { 10 10 String name() default ""; 11 11 Constraints constraints() default @Constraints; // 約束注解,詳細見下面代碼 12 12 }
對於約束注解:

1 package annotiation; 2 import java.lang.annotation.*; 3 4 @Inherited 5 @Documented 6 @Target(ElementType.FIELD) 7 @Retention(RetentionPolicy.RUNTIME) 8 9 public @interface Constraints { 10 boolean primaryKey() default false; // 主鍵,默認為空 11 boolean allowNull() default true; // 默認允許為空 12 boolean unique() default false; // 默認允許重復 13 }
實體類:

1 package model; 2 3 import annotiation.*; 4 5 @DBTable(name = "User") // 設置表名為User 6 public class User { 7 @SQLString(size = 50) // 設置字段 username, varchar(50) 8 String username; 9 10 @SQLString(size = 50) 11 String password; 12 13 @SQLString(size = 30, constraints = @Constraints(primaryKey = true)) // 設置為主鍵 14 String handle; 15 16 static int memberCount; 17 18 public String getUsername() { return username; } 19 20 public void setUsername(String username) { this.username = username; }// 個人感覺set方法可以去掉 21 22 public String getPassword() { return password; } 23 24 public void setPassword(String password) { this.password = password; } 25 26 public String getHandle() { return handle; } 27 28 public void setHandle(String handle) { this.handle = handle; } 29 30 public String toString() { return handle; } 31 }
准備工作之后,就是如何根據注解和反射拼接SQL語句:

1 package creator; 2 import java.lang.reflect.*; 3 import java.lang.annotation.*; 4 import java.util.*; 5 6 import annotiation.Constraints; 7 import annotiation.DBTable; 8 import annotiation.SQLString; 9 10 11 public class TableCreator { 12 private static String getConstraints(Constraints constraints) { // 獲取字段約束屬性 13 String cons = ""; 14 if (!constraints.allowNull()) { 15 cons += " NOT NULL"; 16 } 17 if (constraints.primaryKey()) { 18 cons += " PRIMARY KEY"; 19 } 20 if (constraints.unique()) { 21 cons += " UNIQUE"; 22 } 23 return cons; 24 } 25 26 /* 這邊還需要通過IO來遍歷指定model包下所有實體類, 如上,待下一篇優化 27 private static ArrayList<String> getTables() { 28 ArrayList<String> tables = new ArrayList<String>(); 29 Package pckg = Package.getPackage("model"); 30 Class<?>[] cls = pckg.; 31 for (Class<?> cl : cls) { 32 tables.add(cl.getName()); 33 } 34 return tables; 35 } 36 */ 37 38 public static String getSql() throws ClassNotFoundException { 39 String sql = null; 40 //ArrayList<String> tables = getTables(); 41 String[] tables = {"model.User"}; 42 for (String className : tables) { 43 /* 44 String[] table = className.split("\\."); 45 for (String tb : table) { 46 System.out.println(tb); 47 } 48 */ 49 Class<?> cl = Class.forName(className); // 通過類名得到該實體類 50 DBTable dbtable = cl.getAnnotation(DBTable.class); // 通過注解得到表明 51 String tableName = dbtable.name().length() > 1 ? dbtable.name() : cl.getName().toUpperCase(); 52 /* comments 53 System.out.println("tableName: " + tableName); 54 */ 55 List<String> columns = new ArrayList<String>(); 56 for (Field field : cl.getDeclaredFields()) { // 得到該類下所有屬性 57 String columnName = null; 58 Annotation[] annotations = field.getAnnotations(); 59 if (annotations.length < 1) { 60 continue; 61 } 62 if (annotations[0] instanceof SQLString) { 63 SQLString sStr = (SQLString)annotations[0]; 64 columnName = sStr.name().length() < 1 ? field.getName() : sStr.name(); 65 columns.add(columnName + " VARCHAR(" + sStr.size() + ")" + getConstraints(sStr.constraints())); 66 } 67 } 68 69 StringBuilder sb = new StringBuilder("Create Table " + tableName + "("); 70 for (String column : columns) { 71 sb.append("\n " + column + ","); // 拼接各個字段的定義語句 72 } 73 sql = sb.substring(0, sb.length() - 1) +");"; 74 } 75 System.out.println("=========" + sql + "========="); // 測試輸出 76 return sql; 77 } 78 }
輸出的語句應該是:

1 Create Table User( 2 username VARCHAR(50), 3 password VARCHAR(50), 4 handle VARCHAR(30) PRIMARY KEY);
既然有了SQL語句,只需要通過JDBC連接數據庫執行即可(其實還可以封裝之后實現相同CRUD操作,下篇優化):

1 package dbconnect; 2 3 import java.sql.Connection; 4 import java.sql.DriverManager; 5 import java.sql.SQLException; 6 import java.sql.Statement; 7 8 9 public class DBConnect { 10 static Connection connect; 11 static String driver = "com.mysql.jdbc.Driver"; 12 static String password = "thoupin'spassword"; 13 static String username = "thoupin"; 14 static String dbName = "test"; 15 static String url = "jdbc:mysql://localhost/" + dbName; 16 17 public static void connect() { // 連接 18 try { 19 Class.forName(driver); 20 } catch (ClassNotFoundException e) { 21 System.out.println("Can not find the Driver!"); 22 e.printStackTrace(); 23 } 24 25 try { 26 connect = DriverManager.getConnection(url, username, password); 27 } catch (SQLException e) { 28 System.out.println("Database connect failed!"); 29 e.printStackTrace(); 30 } 31 } 32 33 public static void execute(String sql) { // 執行語句 34 Statement stmt; 35 try { 36 stmt = connect.createStatement(); 37 stmt.executeUpdate(sql); 38 } catch (SQLException e) { 39 // TODO Auto-generated catch block 40 e.printStackTrace(); 41 } 42 } 43 44 public static void close() { // 關閉連接 45 if (connect != null) { 46 try { 47 connect.close(); 48 } catch (SQLException e) { 49 e.printStackTrace(); 50 } 51 } 52 } 53 }
最后就是主程序了:

1 package Main; 2 3 import creator.TableCreator; 4 import dbconnect.DBConnect; 5 6 public class run { 7 public static void main(String[] args) { 8 DBConnect.connect(); 9 try { 10 DBConnect.execute(TableCreator.getSql()); 11 } catch (ClassNotFoundException e) { 12 // TODO Auto-generated catch block 13 e.printStackTrace(); 14 } 15 DBConnect.close(); 16 } 17 }
最后數據庫中變出現了一張新表:
至此,一個自己粗糙簡陋的自動生成工具算是做好了,但實際情況很復雜,遠遠沒有這么簡單, 類似不同字段類型的判斷,多張表的同時創建,判斷新舊表從而決定是否重新執行SQL, 實體改動對數據庫的影響等等問題,就此一系列后面幾篇做優化和研究。