Java游戲服務器之數據庫表存取封裝
項目涉及的數據庫表并不多,但每個select、insert、update和delete都去手動拼接字符串,是很低效的,尤其在時常要修改結構的情況下。開發(fā)的一個目標就是自動化,即能自動實現(xiàn)的事情就不要手動去做;還有一個原則是單一化,即盡量保證數據或邏輯一個入口一個出口。這個需求可以使用一些開源庫解決,但因為需求簡單,目標明確,沒有必要引入多余的第三方庫。于是自己寫了一個,至少滿足當前需求。
數據庫表的封裝,核心類有兩個,表(Table)和記錄(Record)。首先需要一個Table類保存數據庫表結構的描述,并籍此自動生成相應SQL語句。其次需要一個Record類自動設置SQL參數,并從返回結果集中自動生成邏輯對象。
table類表結構描述可以有兩個來源,自動從數據庫獲取,或從配置表加載。這里選擇從配置表加載的方式,一來實現(xiàn)簡單,二來應用面更廣。
下面是一個賬戶表的配置示例(user.xml)。
<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類讀取配置文件獲得數據表的結構描述。
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:
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類,其使用反射自動存取數據。
public class Record<T> {
private Table<T> table = null;
private T object = null;
...
模板參數T即一個表記錄對應的邏輯對象。在我們的示例里,即賬戶數據類:
public class UserData implements Serializable {
// 用戶ID
public int userId = 0;
// 用戶名
public String username = "";
// 密碼
public String password = "";
...
有了SQL語句,要先設置參數,才能執(zhí)行。主鍵和普通字段分開設置。
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;
}
就是根據表結構描述,通過反射獲取對應字段的值然后設置。
private Object getFieldValue(Table<T>.TableField tableField) throws Exception {
Field field = object.getClass().getDeclaredField(tableField.fieldName);
return field.get(object);
}
toDBValue作用是將Java邏輯類型轉成對應數據庫類型,比如時間,在邏輯里是Long,而數據庫類型是Timestamp。
private Object toDBValue(Table<T>.TableField tableField, Object value) {
if (tableField.type == TableField.TYPE_TIMESTAMP) {
value = new Timestamp((long) value);
}
return value;
}
以設置update SQL參數為例:
public void setUpdateParams(PreparedStatement pst) throws Exception {
final int start = setNormalParams(1, pst);
setPrimaryParams(start, pst);
}
之后執(zhí)行該SQL語句就可以了。如果是select語句還會返回結果集(ResultSet),從結果集自動生成邏輯對象原理類似,算是一個逆過程,詳細參看文末代碼。
下面給出一個使用的完整示例:
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;
}
代碼封裝得很簡易,有更多需求可據此改進。
相關文章
SpringBoot注解@Import原理之關于ConfigurationClassPostProcessor源碼解析
這篇文章主要介紹了SpringBoot注解@Import原理之關于ConfigurationClassPostProcessor源碼解析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

