本文是《輕量級 Java Web 框架架構設計》的系列博文。 java
爲了開發一款輕量級的 Java Web 開發框架,我不惜放棄了我最深愛的 Hibernate。我很是感謝它這麼多年來教會了我許多知識,讓我不會再走彎路。既然沒有彎路,那麼什麼纔是捷徑呢?那就是儘量的簡單,經過慣例(Convention)而不是配置(Configuration)。說通來說就是徹底不須要任何的配置,就連註解都不須要! 程序員
下面這個 Entity,我想足以讓你們明白。 sql
public class Product extends BaseEntity { private long productTypeId; private String productName; private String productCode; private int price; private String description; public long getProductTypeId() { return productTypeId; } public void setProductTypeId(long productTypeId) { this.productTypeId = productTypeId; } ... }
不須要 @Entity、@Table、@Column 這樣的註解,同時也放棄了 @OneToMany 這類具備爭議的功能。僅僅繼承了 BaseEntity。對於 BaseEntity 以及有關 Entity 的架構思路,請參考我另一篇博文《對 Entity 的初步構思》。 數據庫
那麼框架又是怎樣加載 classpath 中全部的 Entity 呢?仍是用代碼來講明一切吧。 架構
public class EntityHelper { private static final Map<Class<?>, Map<String, String>> entityMap = new HashMap<Class<?>, Map<String, String>>(); static { // 獲取並遍歷全部 Entity 類 List<Class<?>> entityClassList = ClassHelper.getClassList(BaseEntity.class); for (Class<?> entityClass : entityClassList) { // 獲取並遍歷該 Entity 類中的全部方法(不包括父類中的方法) Field[] fields = entityClass.getDeclaredFields(); if (ArrayUtil.isNotEmpty(fields)) { // 建立一個 Field Map(用於存放 Column 與 Field 的映射關係) Map<String, String> fieldMap = new HashMap<String, String>(); for (Field field : fields) { String fieldName = field.getName(); String columnName = StringUtil.toUnderline(fieldName); // 將駝峯風格替換爲下劃線風格 // 若 Field 與 Column 不一樣,則須要進行映射 if (!fieldName.equals(columnName)) { fieldMap.put(columnName, fieldName); } } // 將 Entity 類與 Field Map 放入 Entity Map 中 if (MapUtil.isNotEmpty(fieldMap)) { entityMap.put(entityClass, fieldMap); } } } } public static Map<Class<?>, Map<String, String>> getEntityMap() { return entityMap; } }
這個 EntityHelper 類會自動初始化 Entity 類與 Column/Field 的映射關係,並放入 entityMap中。此後,就能夠經過 getEntityMap() 這個靜態方法隨時獲取這些映射關係了,這個也就實現了所謂的 ORM(固然是輕量級的了)。 框架
以上這些都是框架爲咱們提供的基礎支持,還有一個成員不得不說,那就是 DBHelper 類,有了它以後,程序員就能夠無需面對 JDBC 返回的 ResultSet 了,而是面對 JavaBean(或者說是 Entity),也就是說,自動將 ResultSet 轉換爲 JavaBean。在這裏我得感謝 Apache Commons DbUtils 類庫,它真的是太好了,很是輕量級,我只用了一次就愛上它了! ide
public class DBHelper { private static final BasicDataSource ds = new BasicDataSource(); private static final QueryRunner runner = new QueryRunner(ds); static { ds.setDriverClassName(ConfigHelper.getProperty("jdbc.driver")); ds.setUrl(ConfigHelper.getProperty("jdbc.url")); ds.setUsername(ConfigHelper.getProperty("jdbc.username")); ds.setPassword(ConfigHelper.getProperty("jdbc.password")); ds.setMaxActive(Integer.parseInt(ConfigHelper.getProperty("jdbc.max.active"))); } // 獲取數據源 public static DataSource getDataSource() { return ds; } // 執行查詢語句(返回一個對象) public static <T> T queryBean(Class<T> cls, String sql, Object... params) { Map<String, String> map = EntityHelper.getEntityMap().get(cls); return DBUtil.queryBean(runner, cls, map, sql, params); } // 執行查詢語句(返回多個對象) public static <T> List<T> queryBeanList(Class<T> cls, String sql, Object... params) { Map<String, String> map = EntityHelper.getEntityMap().get(cls); return DBUtil.queryBeanList(runner, cls, map, sql, params); } // 執行更新語句(包括 UPDATE、INSERT、DELETE) public static int update(String sql, Object... params) { return DBUtil.update(runner, sql, params); } }
如今只是提供了最爲典型的數據庫操做,也就是 SELECT、UPDATE、INSERT、DELETE 了。未來或許還會有一些平常使用的方法,在這個 DBHelper 類裏面進行擴展吧。 單元測試
細心的讀者可能已經看到了,在 DBHelper 中還有一個 DBUtil,這個類又是什麼呢?難道就是對 Apache Commons DbUtils 的封裝類?對頭! 測試
public class DBUtil { // 查詢(返回 Array) public static Object[] queryArray(QueryRunner runner, String sql, Object... params) { Object[] result = null; try { result = runner.query(sql, new ArrayHandler(), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢(返回 ArrayList) public static List<Object[]> queryArrayList(QueryRunner runner, String sql, Object... params) { List<Object[]> result = null; try { result = runner.query(sql, new ArrayListHandler(), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢(返回 Map) public static Map<String, Object> queryMap(QueryRunner runner, String sql, Object... params) { Map<String, Object> result = null; try { result = runner.query(sql, new MapHandler(), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢(返回 MapList) public static List<Map<String, Object>> queryMapList(QueryRunner runner, String sql, Object... params) { List<Map<String, Object>> result = null; try { result = runner.query(sql, new MapListHandler(), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢(返回 Bean) public static <T> T queryBean(QueryRunner runner, Class<T> cls, Map<String, String> map, String sql, Object... params) { T result = null; try { if (MapUtil.isNotEmpty(map)) { result = runner.query(sql, new BeanHandler<T>(cls, new BasicRowProcessor(new BeanProcessor(map))), params); } else { result = runner.query(sql, new BeanHandler<T>(cls), params); } } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢(返回 BeanList) public static <T> List<T> queryBeanList(QueryRunner runner, Class<T> cls, Map<String, String> map, String sql, Object... params) { List<T> result = null; try { if (MapUtil.isNotEmpty(map)) { result = runner.query(sql, new BeanListHandler<T>(cls, new BasicRowProcessor(new BeanProcessor(map))), params); } else { result = runner.query(sql, new BeanListHandler<T>(cls), params); } } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢指定列名的值(單條數據) public static <T> T queryColumn(QueryRunner runner, String column, String sql, Object... params) { T result = null; try { result = runner.query(sql, new ScalarHandler<T>(column), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢指定列名的值(多條數據) public static <T> List<T> queryColumnList(QueryRunner runner, String column, String sql, Object... params) { List<T> result = null; try { result = runner.query(sql, new ColumnListHandler<T>(column), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 查詢指定列名對應的記錄映射 public static <T> Map<T, Map<String, Object>> queryKeyMap(QueryRunner runner, String column, String sql, Object... params) { Map<T, Map<String, Object>> result = null; try { result = runner.query(sql, new KeyedHandler<T>(column), params); } catch (SQLException e) { e.printStackTrace(); } return result; } // 更新(包括 UPDATE、INSERT、DELETE,返回受影響的行數) public static int update(QueryRunner runner, String sql, Object... params) { int result = 0; try { result = runner.update(sql, params); } catch (SQLException e) { e.printStackTrace(); } return result; } }
在 DBUtil 中,已經對 Apache Commons DbUtils 作了大量的封裝,其彷佛這個類對於程序員們來說,不到萬不得已,儘可能不要用,最好仍是用 DBHelper 吧。若是 DBHelper 中的方法不夠用了,不妨給你的架構師提一個需求,讓他去擴展 DBHelper。 this
那麼 Service 層又是如何調用 DBHelper 的呢? 來看看這個 ProductService 吧。
public interface ProductService { Product getProduct(long productId); }以上只是一個接口而已,實現類應該如何編寫呢?彆着急,往下看。
public class ProductServiceImpl extends BaseService implements ProductService { @Override public Product getProduct(long productId) { String sql = SQLHelper.getSQL("select.product.id"); return DBHelper.queryBean(Product.class, sql, productId); } }
這裏先從 SQLHelper 中拿到 SQL 語句(帶有佔位符「?」的),而後調用 DBHelper 的 queryBean() 方法,傳入須要轉換的 Java 類(Product.class)、SQL 語句(sql)、填充 SQL語句佔位符的參數(productId)。返回值就是 Product 對象了。是否是很簡單呢? 不對!這裏爲何沒有數據庫鏈接呢? 這個問題留做讀者本身思考吧。
有必要看看 SQLHelper 類是如何實現的,雖然它很簡單,只是讀取 properties 文件,並經過 key 獲取 value(也便是 SQL)而已。
public class SQLHelper { private static final Properties sqlProperties = FileUtil.loadPropertiesFile("sql.properties"); public static String getSQL(String key) { String value = ""; if (sqlProperties.containsKey(key)) { value = sqlProperties.getProperty(key); } else { System.err.println("Can not get property [" + key + "] in sql.properties file."); } return value; } }
以上代碼真的很簡單吧?那麼,sql.properties 的內容還有懸念嗎?
select.product.id = select * from product where id = ?
程序員們要作的就是寫這個 sql.properties,還有 XxxService,以及 XxxServiceImpl 了。
萬事俱備,只欠東風。最後來一個單元測試吧!
public class ProductServiceTest { private ProductService productService = new ProductServiceImpl(); @Test public void loginTest() { long productId = 1; Product product = productService.getProduct(productId); Assert.assertNotNull(product); } }
這裏沒有對 productService 進行依賴注入,是否須要依賴注入呢?應該如何進行依賴注入呢?又是如何實現依賴注入的呢?這些問題留做下一篇博文與你們分享。
來一張高清無碼類圖吧。
灰色虛線圈出來得三個類是程序員要作的。灰色是 Base 類,黃色是 Helper 類,藍色是 Util 類,還有一個 sql.properties 也是程序員要寫的。
請你們不要吝嗇,隨便點評!
補充(2013-09-04)
很是感謝網友們的評價!對與 Entity 的結構有必要稍做修改,細節以下:
若是列名是 Java 關鍵字(例如:列名爲 class),那麼在 Entity 中必定不能定義一個名稱爲 class 的字段。因此,我仍是採用了 @Column 註解,將列名寫在註解中。
public class Product extends BaseEntity { private long productTypeId; private String productName; private String productCode; private int price; private String description; @Column("class") private int clazz; public long getProductTypeId() { return productTypeId; } public void setProductTypeId(long productTypeId) { this.productTypeId = productTypeId; } ... }在 Product 實體的 clazz 字段上定義了一個 @Column("class") 註解,這就覺得着將列名 class 映射爲字段名 clazz。並且,若是使用了 @Column 註解,那麼就不會採用默認的映射規則(用「下劃線風格」的列名映射「駝峯風格」的字段名)。那麼這又是如何實現的呢?對 EntityHelper 稍做修改便可實現。
... for (Field field : fields) { String fieldName = field.getName(); String columnName; // 若該字段上存在 @Column 註解,則優先獲取註解中的列名 if (field.isAnnotationPresent(Column.class)) { columnName = field.getAnnotation(Column.class).value(); } else { columnName = StringUtil.toUnderline(fieldName); // 將駝峯風格替換爲下劃線風格 } // 若字段名與列名不一樣,則須要進行映射 if (!fieldName.equals(columnName)) { fieldMap.put(columnName, fieldName); } } ...經過一個 if...else... 就輕易地解決了此問題。
那麼確定會有人要問:若是數據庫的表名是 Java 關鍵字,那麼必定會與 Entity 的名稱相沖突,那是否應該在 Entity 上標註一個 @Table("表名") 註解呢?
我我的認爲是不須要這樣作的,爲何呢?由於 SQL 語句都是程序員本身編寫的,並統一寫在 sql.properties 文件中,在 SQL 語句上用的就是表名,咱們須要映射的是列名,僅此而已。
至於 Entity 中有些字段是否必定要與列名對應?這個問題,我還要細想一下,也請廣大網友給出建議。