欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-07 11:59:57
项目涉及的数据库表并不多,但每个select、insert、update和delete都去手动拼接字符串,是很低效的,尤其在时常要修改结构的情况下。开发的一个目标就是自动化...

项目涉及的数据库表并不多,但每个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;
  }

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