項目涉及的數(shù)據(jù)庫表并不多,但每個select、insert、update和delete都去手動拼接字符串,是很低效的,尤其在時常要修改結(jié)構(gòu)的情況下。開發(fā)的一個目標(biāo)就是自動化,即能自動實現(xiàn)的事情就不要手動去做;還有一個原則是單一化,即盡量保證數(shù)據(jù)或邏輯一個入口一個出口。這個需求可以使用一些開源庫解決,但因為需求簡單,目標(biāo)明確,沒有必要引入多余的第三方庫。于是自己寫了一個,至少滿足當(dāng)前需求。
數(shù)據(jù)庫表的封裝,核心類有兩個,表(Table)和記錄(Record)。首先需要一個Table類保存數(shù)據(jù)庫表結(jié)構(gòu)的描述,并籍此自動生成相應(yīng)SQL語句。其次需要一個Record類自動設(shè)置SQL參數(shù),并從返回結(jié)果集中自動生成邏輯對象。
table類表結(jié)構(gòu)描述可以有兩個來源,自動從數(shù)據(jù)庫獲取,或從配置表加載。這里選擇從配置表加載的方式,一來實現(xiàn)簡單,二來應(yīng)用面更廣。
下面是一個賬戶表的配置示例(user.xml)。
1
2
3
4
5
6
7
|
< Table name = "user" primaryKey= "user_id" primaryField= "userId" > < Column name = "username" field= "username" type= "2" /> < Column name = "password" field= "password" type= "2" /> < Column name = "salt" field= "salt" type= "1" /> < Column name = "reg_time" field= "registerTime" type= "3" /> < Column name = "last_login_time" field= "lastLoginTime" type= "3" /> </ Table > |
只定義了一個主鍵,有需要可對此擴充。每列name對應(yīng)數(shù)據(jù)庫表的列名,field對應(yīng)邏輯對象的成員變量名,type對應(yīng)字段的類型,比如是int、string、timestamp等,有了名字和類型,就可以使用反射方式自動get和set數(shù)據(jù)。
Table類讀取配置文件獲得數(shù)據(jù)表的結(jié)構(gòu)描述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class Table<T> { public class TableField { public static final int TYPE_INTEGER = 1 ; public static final int TYPE_STRING = 2 ; public static final int TYPE_TIMESTAMP = 3 ; public String columnName = "" ; public String fieldName = "" ; public int type = 0 ; } private String tableName = "" ; private TableField primaryField = new TableField(); private ArrayList<TableField> tableFields = new ArrayList<TableField>(); private String selectAllSql = "" ; private String selectSql = "" ; private String insertSql = "" ; private String updateSql = "" ; private String deleteSql = "" ; ... |
然后生成PrepareStatement方式讀寫的select、insert、update和delete的預(yù)處理SQL字符串。如update:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private String generateUpdateSql() { String sql = "UPDATE " + tableName + " SET " ; int size = tableFields.size(); for ( int index = 0 ; index < size; ++index) { TableField tableField = tableFields.get(index); String conjunction = index == 0 ? "" : "," ; String colSql = tableField.columnName + " = ?" ; sql = sql + conjunction + colSql; } sql = sql + " WHERE " + primaryField.columnName + "=?" ; return sql; } |
Table類的功能就這么多,下面是關(guān)鍵的Record類,其使用反射自動存取數(shù)據(jù)。
1
2
3
4
|
public class Record<T> { private Table<T> table = null ; private T object = null ; ... |
模板參數(shù)T即一個表記錄對應(yīng)的邏輯對象。在我們的示例里,即賬戶數(shù)據(jù)類:
1
2
3
4
5
6
7
8
|
public class UserData implements Serializable { // 用戶ID public int userId = 0 ; // 用戶名 public String username = "" ; // 密碼 public String password = "" ; ... |
有了SQL語句,要先設(shè)置參數(shù),才能執(zhí)行。主鍵和普通字段分開設(shè)置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public int setPrimaryParams( int start, PreparedStatement pst) throws Exception { Table<T>.TableField primaryField = table.getPrimaryField(); Object value = getFieldValue(primaryField); value = toDBValue(primaryField, value); pst.setObject(start, value); return start + 1 ; } public int setNormalParams( int start, PreparedStatement pst) throws Exception { ArrayList<Table<T>.TableField> normalFields = table.getNoramlFields(); final int size = normalFields.size(); for ( int index = 0 ; index < size; ++index) { Table<T>.TableField tableField = normalFields.get(index); Object value = getFieldValue(tableField); value = toDBValue(tableField, value); pst.setObject(start + index, value); } return start + size; } |
就是根據(jù)表結(jié)構(gòu)描述,通過反射獲取對應(yīng)字段的值然后設(shè)置。
1
2
3
4
|
private Object getFieldValue(Table<T>.TableField tableField) throws Exception { Field field = object.getClass().getDeclaredField(tableField.fieldName); return field.get(object); } |
toDBValue作用是將Java邏輯類型轉(zhuǎn)成對應(yīng)數(shù)據(jù)庫類型,比如時間,在邏輯里是Long,而數(shù)據(jù)庫類型是Timestamp。
1
2
3
4
5
6
|
private Object toDBValue(Table<T>.TableField tableField, Object value) { if (tableField.type == TableField.TYPE_TIMESTAMP) { value = new Timestamp(( long ) value); } return value; } |
以設(shè)置update SQL參數(shù)為例:
1
2
3
4
|
public void setUpdateParams(PreparedStatement pst) throws Exception { final int start = setNormalParams( 1 , pst); setPrimaryParams(start, pst); } |
之后執(zhí)行該SQL語句就可以了。如果是select語句還會返回結(jié)果集(ResultSet),從結(jié)果集自動生成邏輯對象原理類似,算是一個逆過程,詳細(xì)參看文末代碼。
下面給出一個使用的完整示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private static final Table<UserData> udTable = new Table<UserData>(); ... udTable.load( "user.xml" ); ... public static boolean updateUserData(UserData userData) { boolean result = false ; Record<UserData> record = udTable.createRecord(); record.setObject(userData); PreparedStatement pst = null ; try { String sql = udTable.getUpdateSql(); pst = DbUtil.openConnection().prepareStatement(sql); record.setUpdateParams(pst); result = pst.executeUpdate() > 0 ; } catch (Exception e) { e.printStackTrace(); } finally { DbUtil.closeConnection( null , pst); } return result; } |
代碼封裝得很簡易,有更多需求可據(jù)此改進。