搞過Java的碼農都知道,在J2EE開發中一個(確切地說,應該是一類)很重要的框架,那就是ORM(Object Relational Mapping,對象關係映射)。它把Java中的類和資料庫中的表關聯起來,可以像操作對象那樣操作數據表,十分方便。給碼農們節約了大量的時間去摸魚。其實它的本質一點都不 ...
搞過Java的碼農都知道,在J2EE開發中一個(確切地說,應該是一類)很重要的框架,那就是ORM(Object Relational Mapping,對象關係映射)。它把Java中的類和資料庫中的表關聯起來,可以像操作對象那樣操作數據表,十分方便。給碼農們節約了大量的時間去摸魚。其實它的本質一點都不複雜,而最核心的就是怎麼實現對象和表之間的轉換。之前對反射和註解有了一點瞭解,所以就試著來實現咱們自己的縫合怪。
首先,需要建立一個「表格」:
/** * 類註解,將類註解成資料庫表 * * @author xiangwang */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DBTable { String name() default ""; }
然後,定義需要的資料庫數據類型:
/** * 欄位類型枚舉 * * @author xiangwang */ public enum Type { CHAR, STRING, BOOLEAN, INTEGER, LONG, FLOAT, DOUBLE, DATETIME } /** * 資料庫欄位類型 * * @author xiangwang */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ColumnType { Type value() default Type.INTEGER; }
再來完善欄位相關信息:
/** * 欄位信息 * * @author xiangwang */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ExtraInfo { String name() default ""; int length() default 0; } /** * 明確欄位約束 * * @author xiangwang */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Constraints { boolean primaryKey() default false; boolean allowNull() default true; boolean unique() default false; // 還可以增加預設值 }
把他們拼起來,成為完整的欄位描述:
/** * 拼裝註解,形成完整的欄位嵌套註解 * * @author xiangwang */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface TableColumn { ColumnType columntype() default @ColumnType; ExtraInfo extrainfo() default @ExtraInfo; Constraints constraints() default @Constraints; }
最後,創建實體類,應用剛纔寫好的這些註解:
/** * 用戶實體類 * * @author xiangwang */ @DBTable(name = "User") public class User { @TableColumn( columntype = @ColumnType(Type.INTEGER), extrainfo = @ExtraInfo(name = "id", length = 4), constraints = @Constraints(primaryKey = true)) private String id; @TableColumn( columntype = @ColumnType(Type.STRING), extrainfo = @ExtraInfo(name = "name", length = 32), constraints = @Constraints(primaryKey = false, allowNull = false, unique = true)) private String name; @TableColumn( columntype = @ColumnType(Type.INTEGER), extrainfo = @ExtraInfo(name = "age", length = 4), constraints = @Constraints(primaryKey = false)) private Integer age; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
來看看ORM是怎麼工作的吧:
/** * 解析類型註解 */ private static String getColumnType(ColumnType columntype) { String type = ""; switch (columntype.value()) { case CHAR: type += "CHAR"; break; case STRING: type += "VARCHAR"; break; case BOOLEAN: type += "BIT"; break; case INTEGER: type += "INT"; break; case LONG: type += "BIGINT"; break; case FLOAT: type += "FLOAT"; break; case DOUBLE: type += "DOUBLE"; break; case DATETIME: type += "DATETIME"; break; default: type += "VARCHAR"; break; } return type; } /** * 解析信息註解 */ private static String getExtraInfo(ExtraInfo extrainfo) { String info = ""; if (null != extrainfo.name()) { info = extrainfo.name(); } else { return null; } if (0 < extrainfo.length()) { info += " (" + extrainfo.length() + ")"; } else { return null; } return info; } /** * 解析約束註解 */ private static String getConstraints(Constraints con) { String constraints = ""; if (con.primaryKey()) { constraints += " PRIMARY KEY"; } if (!con.allowNull()) { constraints += " NOT NULL"; } if (con.unique()) { constraints += " UNIQUE"; } return constraints; }
做了那麼多的鋪墊,終於到了臨門一腳了,實現一個縫合怪了:
/** * 臨門一腳:實現一個縫合怪 */ private static void createTable(List<String> list) { for (String className : list) { Class<?> clazz; try { clazz = Class.forName(className); DBTable dbTable = clazz.getAnnotation(DBTable.class); if (dbTable == null) {// 無DBTable註解 continue; } // 轉大寫 String tableName = clazz.getSimpleName().toUpperCase(); StringBuilder sql = new StringBuilder("CREATE TABLE " + tableName + "("); for (Field field : clazz.getDeclaredFields()) { // 反射得到註解 Annotation[] anns = field.getDeclaredAnnotations(); if (anns.length < 1) { continue; } String columnInfo = ""; // 類型判斷 if (anns[0] instanceof TableColumn) { TableColumn column = (TableColumn) anns[0]; String type = getColumnType(column.columntype()); columnInfo = getExtraInfo(column.extrainfo()); // 代替( columnInfo = columnInfo.replace("(", type + "("); columnInfo += getConstraints(column.constraints()); } sql.append("\n " + columnInfo + ","); } // 刪除尾部的逗號 String tableCreate = sql.substring(0, sql.length() - 1) + "\n);"; System.out.println(tableCreate); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
驗證效果的時候到了:
public static void main(String[] args) { Class<?> clazz = User.class; List<String> list = new ArrayList<>(); list.add(clazz.getName()); createTable(list); }
當然,實際的運營於生產環境中的ORM框架可要比這個小玩意複雜多了。但千變萬變,原理不變,ORM的核心——反射 + 註解——就是這麼玩的。