项目涉及的数据库表并不多,但每个select、insert、update和delete都去手动拼接字符串,是很低效的,尤其在时常要修改结构的情况下。开发的一个目标就是自动化,即能自动实现的事情就不要手动去做;还有一个原则是单一化,即尽量保证数据或逻辑一个入口一个出口。这个需求可以使用一些开源库解决,但因为需求简单,目标明确,没有必要引入多余的第三方库。于是自己写了一个,至少满足当前需求。
数据库表的封装,核心类有两个,表(Table)和记录(Record)。首先需要一个Table类保存数据库表结构的描述,并籍此自动生成相应SQL语句。其次需要一个Record类自动设置SQL参数,并从返回结果集中自动生成逻辑对象。
table类表结构描述可以有两个来源,自动从数据库获取,或从配置表加载。这里选择从配置表加载的方式,一来实现简单,二来应用面更广。
下面是一个账户表的配置示例(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对应数据库表的列名,field对应逻辑对象的成员变量名,type对应字段的类型,比如是int、string、timestamp等,有了名字和类型,就可以使用反射方式自动get和set数据。
Table类读取配置文件获得数据表的结构描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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的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类的功能就这么多,下面是关键的Record类,其使用反射自动存取数据。
1 2 3 4 5
| public class Record<T> {
private Table<T> table = null; private T object = null; ...
|
模板参数T即一个表记录对应的逻辑对象。在我们的示例里,即账户数据类:
1 2 3 4 5 6 7 8 9 10 11
| public class UserData implements Serializable {
public int userId = 0;
public String username = "";
public String password = ""; ...
|
有了SQL语句,要先设置参数,才能执行。主键和普通字段分开设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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; }
|
就是根据表结构描述,通过反射获取对应字段的值然后设置。
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逻辑类型转成对应数据库类型,比如时间,在逻辑里是Long,而数据库类型是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; }
|
以设置update SQL参数为例:
1 2 3 4
| public void setUpdateParams(PreparedStatement pst) throws Exception { final int start = setNormalParams(1, pst); setPrimaryParams(start, pst); }
|
之后执行该SQL语句就可以了。如果是select语句还会返回结果集(ResultSet),从结果集自动生成逻辑对象原理类似,算是一个逆过程,详细参看文末代码。
下面给出一个使用的完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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; }
|
代码封装得很简易,有更多需要可据此改进。
公共库仓库:JMetazion
服务器示例仓库:JGameDemo