Java游戏服务器之数据库表存取封装

前端技术 2023/09/01 Java

项目涉及的数据库表并不多,但每个select、insert、update和delete都去手动拼接字符串,是很低效的,尤其在时常要修改结构的情况下。开发的一个目标就是自动化,即能自动实现的事情就不要手动去做;还有一个原则是单一化,即尽量保证数据或逻辑一个入口一个出口。这个需求可以使用一些开源库解决,但因为需求简单,目标明确,没有必要引入多余的第三方库。于是自己写了一个,至少满足当前需求。

数据库表的封装,核心类有两个,表(Table)和记录(Record)。首先需要一个Table类保存数据库表结构的描述,并籍此自动生成相应SQL语句。其次需要一个Record类自动设置SQL参数,并从返回结果集中自动生成逻辑对象。

table类表结构描述可以有两个来源,自动从数据库获取,或从配置表加载。这里选择从配置表加载的方式,一来实现简单,二来应用面更广。

下面是一个账户表的配置示例(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语句,要先设置参数,才能执行。主键和普通字段分开设置。

 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);
  }

之后执行该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;
  }

代码封装得很简易,有更多需求可据此改进。

本文地址:https://www.stayed.cn/item/2837

转载请注明出处。

本站部分内容来源于网络,如侵犯到您的权益,请 联系我

我的博客

人生若只如初见,何事秋风悲画扇。