数据库连接和数据库操作

1. 数据库连接 

  ZCF连接数据库是通过从连接池中获取已经在database.xml文件中配置好的连接来实现的。连接配置比较简单,都是在配置文件database.xml中进行配置,详细配置说明请参看数据库连接池。 执行数据库操作的时候默认是取name=” Default”配置的连接。如果想获得其他连接可以通过以下代码(代码摘自附件DBConnExample.java.)实现:

  DataAccess da=new DataAccess(DBConnPool.getConnection("example"));

  使用以上方法请注意关闭数据库连接,标准用法代码示例:

  //取得配置连接名字为example的连接

  DataAccess da=new DataAccess(DBConnPool.getConnection("example"));

  //构造QueryBuilder实例

  QueryBuilder qb=new QueryBuilder("select * from zcexample");

  //将QueryBuilder实例置入取得连接的da,这样数据库进行sql查询的时候使用的是example连接,执行查询后得到DataTable.

  DataTable dt=da.executeDataTable(qb);

  //输出dt

  System.out.println(dt);

  //输出dt中列名为Name的所有值

  for (int i = 0; i < dt.getRowCount(); i++) {

  System.out.println("姓名:"+dt.getString(i"Name"));

  }

  //最后关闭数据库连接

  if(da!=null){

  try {

  da.close();

  } catch (Exception e) {

  e.printStackTrace();

  // TODO: handle exception

  }

  }

  结果图如下:

1

数据库连接程序运行结果

  演示中使用默认连接配置如下:

  <database name="Default">

  <config name="Type">MYSQL</config>

  <config name="ServerAddress">localhost</config>

  <config name="Port">3306</config>

  <config name="Name">泽元开发框架</config>

  <config name="UserName">root</config>

  <config name="Password">10301</config>

  <config name="MaxConnCount">1000</config>

  <config name="InitConnCount">0</config>

  <config name="TestTable">ZDMaxNo</config>

  </database>

 

 

2. 数据库操作

2. 1 数据库操作系统概述

  Web应用中数据持久化的首选当属关系型数据库, 所以J2EE开发人员大部分的开发时间会花费在数据库操作上,但使用JDBC直接操作数据库存在很多不便之处,因此需要对JDBC进行进一步的封装,以更为合理的方式使数据库操作与业务逻辑接合。

  一般而言,数据库操作需要考察以下几个问题:

  1)能不能不修改程序自动支持多种数据库?

  2) 能不能对数据库连接进行有效的监控以防止连接泄漏?

  3)能不能不需要开发人员手工管理连接的关闭?

  4)能不能一条语句就得到想要的数据集?

  5)得到数据集后能不能便利地按行、按列、按字段名找到指定值?

  6)得到数据集后能不能对行、列进行增、删、改操作?

  7)得到数据集后能不能进行排序、筛选等操作?

  8)能不能方便地分页提取数据集?

  9) 一张数据表通常代表着一个业务实体,有没有一种面向对象的方式操作数据,使得开发人员不需要小心翼翼地拼写字段名称?

  10) 能不能方便地使用事务?

  11) 数据经常需要进行修改、删除等对业务逻辑有着重要影响的操作,如何保存数据的历史版本,以便于错误操作之后进行恢复?

  12)SQL注入是Web应用最为主要的安全问题,能不能提供一种统一的方法防止SQL注入?

  各个软件开发机构数据库操作的开发框架一般只对上述问题中的某几个给出肯定答案,对于其它方面则需要开发人员手工解决,而Zving Framework通过一个轻量级的数据库操作API,在保持与直接操作JDBC同等性能的情况下对全部问题给出了肯定答案。

 

2. 2 数据库连接池

  通过修改类文件根目录下(WEB应用是WEB-INF/classes)的database.xml中的databaeses节点来配置连接池,配置文件示例如下:

  <?xml version="1.0" encoding="UTF-8"?>

  <framework>

  <databases>

  <database name="Default">

  <config name="Type">MYSQL</config>

  <config name="ServerAddress">10.1.43.78</config>

  <config name="Port">3306</config>

  <config name="Name">platform</config>

  <config name="UserName">root</config>

  <config name="Password">password</config>

  <config name="MaxConnCount">1000</config>

  <config name="InitConnCount">0</config>

  <config name="TestTable">ZDMaxNo</config>

  </database>

  <database name="DefaultOracle">

  <config name="Type">ORACLE</config>

  <config name="ServerAddress">10.1.43.78</config>

  <config name="Port">1521</config>

  <config name="Name">orcl</config>

  <config name="UserName">zcms</config>

  <config name="Password">password</config>

  <config name="MaxConnCount">1000</config>

  <config name="InitConnCount">0</config>

  <config name="TestTable">ZDMaxNo</config>

  </database>

  <database name="Vounteer">

  <config name="DataSourceRef">JNDIRef</config>

  <config name="TestTable">MDMaxNo</config>

  </database>

  </databases>

  </framework>

  其中databaeses节点下可以有多个database子节点,每个子节点对应着一个连接池,name=Default的节点为默认连接池,各config子节点含义如下:

  name="Type"数据库类型,目前支持Oracle、DB2、Mssql、Mysql;

  name="ServerAddress"数据库服务器域名或IP地址;

  name="Port" :数据库服务器端听的端口;

  name="Name" :数据库用户名称;

  name="Password"数据库用户密码;

  name="MaxConnCount"连接池允许的最大连接数;

  name="InitConnCount": 连接池初始化时建立的连接数量;

  name="TestTable" :用来测试连接是否有效的数据库表名。

  也可以通过配置<config name="DataSourceRef">Ref</config>项使用中间件的连接池,其中JNDIRef表示中间件连接池在JNDI中的名称。

 

3. 一般数据库操作

3. 1 执行SQL语句

  通过使用QueryBuilder类,可以方便地在数据库中执行SQL语句。QueryBuilder需要给定一个参数化的SQL语句,并设置它的参数,然后指定相应的操作方法即可操作数据库,而不需要手工管理Connection、Statement、ResultSet等对象,之所以使用参数化的SQL语句,而不是直接用字符串拼接SQL,是由于以下几点原因:

  1) 用字符串拼接SQL会引入外部参数中不安全的SQL逻辑,从而受到SQL注入攻击(请参考SQL注入相关资料);

  2) QueryBuilder内部调用PreparedStatement处理参数化SQL,能够有效地避免SQL注入攻击;

  3) 参数化SQL使得代码具有良好的可读性和可维护性。

  QueryBuilder有两类构造函数:

  1) 带一个String参数,即指定的SQL语句;

  2) 带二或三个参数,第一个参数为指定的参数化SQL语句,第二、第三个参数可以是int、long、String或者Object。

  通常的参数化SQL语句参数个数小于2个,所以通常使用第二类构造函数,直接指定参数。

  有的SQL语句参数个数大于2个,则需要使用第一类构造函数,先指定SQL语句,然后通过add方法设置参数。

  参数设置后,可以通过set方法进行修改。

  QueryBuilder拥有executePagedDataTable方法,以提供分页查询的能力。QueryBuilder自动针对当前数据库类型形成分页SQL语句,使得只有当前页的数据载入内存,从而获得良好的性能。

  

  以下是代码示例:

  简单SQL查询:

  QueryBuilder qb = new QueryBuilder("select * from user where userid=?", userid);

  DataTable dt = qb.executeDataTable();

  

  多参数SQL查询:

  QueryBuilder qb = new QueryBuilder("select * from user where userid=? and addtime=? and lastlogintime=?");

  qb.add(userid);

  qb.add(DateUtil.parse("2006-01-01"));

  qb.add(new Date());

  DataTable dt = qb.executeDataTable();

 

  分页查询:

  DataTable dt = new QueryBuilder("select * from user where userid=?", userid).executePagedDataTable(15,0);

 

  删除、修改操作:

  new QueryBuilder("delete from user where userid=?",userid).executeNoQuery();

 

  批量操作:

  QueryBuilder qb = new QueryBuilder("update user set mobile=?,email=? Where username=?");

  qb.add("0086-13988877001");

  qb.add("user1@hotmail.com");

  qb.addBatch();

  qb.add("0086-13988877001");

  qb.add("user1@hotmail.com");

  qb.addBatch();

  qb.add("0086-13988877001");

  qb.add("user1@hotmail.com");

  qb.addBatch();

  qb. executeNoQuery();//批量更新3条数据,这比分别更新3条数据性能要好

 

  返回单个值:

  Object count = new QueryBuilder("select count(*) from user").executeOneValue();

 

  除了上述方法,QueryBuilder还提供了简写Q,以及fetch()方法。详情请查阅API文档。

 

3. 2 Data Table类

  通过QueryBuilder类的executeDataTable或者executePagedDataTable方法,可以得到一个DataTable实例。DataTable是对ResultSet的封装,提供了众多ResultSet不具备的功能:

  1)不需要移动当前位置,直接存取任意一行数据;

  2)直接存取任意一行的任意一列(通过列索引或列名);

  3) 直接修改任意一行的任意一列(通过列索引或列名)的值;

  4)可以对DataTable的行进行增、删、改等操作(只在内存中操作,以便于利用数据,不反馈到数据库);

  5)可以对DataTable的列进行增、删、改等操作(只在内存中操作,以便于利用数据,不反馈到数据库);

  6) 可以合并两个DataTable,如果这两个DataTable列相同的话;

  7) 可以筛选掉不符合条件的列,得到一个新的DataTable;

  8)支持按Comparable排序;

  9)可以将所有行的某一列转成数组;

  10)可以将某一行转成以字段名为key的HashMap。

  

  以下是代码示例:

  获取值:

  Object obj = dt.get(3, 4);//取第4行,第5列的值,下标从0开始

  String str = dt.getString(0, "username");//取第1行username列的值

  

  设置值(设置的值不反馈回数据库,但可以用get方法取到,以便于数据利用)

  dt.set(0, 0, "TRUE");//将第1行第1列的值设为字符串TRUE

  dt.set(0, "username""mike");//第1行username列的值设为mike

  

  行操作:

  Object[] rowValue = new Object[] { "john""password""2008-01-11""220.194.110.74" };

  dt.insertRow(rowValue);// 在DataTable最后追加一行,各列数据按rowValue中的值依次填充

  dt.insertRow(rowValue, 0);// 在DataTable最前面插入一行

  dt.deleteRow(9);// 删除第10行

  DataRow dr = dt.getDataRow(0);// 得到第1行的DataRow实例,DataRow是对一行数据的封装

  String username dr.getString("username");// 从dr中取username的值;

  dr.fill(map);//将map中的值按字段名匹配(不区分大小写)到行的字段中

  dt.getDataRow(0).toMapx();// 将第1行转成Mapx

  Mapx是对HashMap的扩展,实现了按数字索引的功能,代码示例如下:

  Mapx map = user.toMapx();

  map.put("key1""value1");

  map.put("key2""value2");

  map.put("key3""value3");

  String v3 = map.getString("key3");//得到字符串value3

  Object v2 = map.get(1);//得到第二个值,下标从0开始

 

  列操作:

  dt.insertColumn("FirstName");// 增加FirstName列

  dt.insertColumn(new DataColumn("Age", DataColumn.INTEGER));// 增加Age列,类型为int

  dt.deleteColumn(4);// 删除第5列

  dt.deleteColumn("FristName");// 删除FirstName列

  Object[] columnValues = dt.getColumnValues(0);// 得到全部行的第一列的值组成的数组

  dt.getDataColumn(0).setColumnName("UserName");// 将第一列的列名修改为UserName

  

  其他操作:

  dt.clone();// Cloneable

  dt.union(anotherDataTable);// 两个DataTable合并

  dt.getColCount();// 返回列数

  dt.getRowCount();// 返回行数

  dt.filter(new Filter() {// 过滤掉Age<18的记录

  public boolean filter(Object obj) {

  DataRow dr = (DataRow) obj;

  if (Integer.parseInt(dr.getString("Age")) < 18) {

  return false;

  }

  return true;

  }

  });

  dt.sort(new Comparator() {// 按Age列排序

  public int compare(Object obj1, Object obj2) {

  DataRow dr1 = (DataRow) obj1;

  DataRow dr2 = (DataRow) obj2;

  return Integer.parseInt(dr1.getString("Age")) - 

  Integer.parseInt(dr2.getString("Age"));

  }

  });

 

4. ORM数据操作

  数据库中的一张表往往对应着一个业务实体,以数据集的方式来处理不够便利,如果能够对数据库表以面向对象的方式进行操作,则符合一般的思维模式。Zving Framework通过解析ZDM文件格式(Zving Data Model),自动生成相应的DAO类,提供了轻量级、零配置的ORM实现。

4. 1 DAO的生成

  修改ZDM文件时,开发插件会自动在com.zving.schema下生成DAO类。

  DAO类的名称和表名相同,表User则会生成User.java。除了DAO外,框架还提供了DAOSet类,调用DAO类的query()方法可得到记录的集合。DAO对应表的一行记录,表中的每个字段DAO都有对应的Getter和Setter方法,DAOSet对应记录的集合。

  

4. 2 DAO的使用

  DAO对应着数据表中的一行完整的记录,DAO类提供了对记录进行操作的众多方法,示例如下:

  取数据:

  ZDUser user = new ZDUser ();// new一个DAO实例

  user.setUserName("alex");// 设置字段UserName的值

  user.fill();// 根据主键值从数据库中取所有字段,如果主键值未set,则报错

  user.getMobile();// 取得Mobile字段的值

  user.getEmail();// 取得Email字段的值

 

  插入数据:

  ZDUser user = new ZDUser ();// new一个DAO实例

  user.setUserName("test");

  user.setPassword(StringUtil.md5Hex(password));

  user.setMobile("0086-13012340000");

  user.setEmail("test@hotmail.com");

  user.setAddUser("admin");

  user.setAddTime(new Date());

  user.insert();// 插入记录到数据库;

 

  更新数据:

  ZDUser user = new ZDUser ();// new一个Schema实例

  user.setUserName("alex");

  user.setMobile("0086-13012340000");

  user.setEmail("test@hotmail.com");

  boolean flag = user.update();// 更新用户alex的Mobile和Email两个字段

 

  删除数据:

  ZDUser user = new ZDUser ();// new一个Schema实例

  user.setUserName("alex");

  boolean flag = user. delete ();// 删除用户alex

 

  其他操作:

  user.toDataRow();//转换成DataRow

  user.toMapx();//转换成Mapx

  user.setValue(dr);//将DataRow中的数据按字段名称匹配到Schema中

  user.setValue(map);//将Mapx中的数据按字段名称匹配到Schema中

  user.clone();//Cloneable

  user.getColumnCount();//返回列数

  user.getV(0);//取第1个字段的值

 

4. 3 DAOSet的使用

  DAOSet对应着数据表中的多条记录,DAOSet类提供了对记录集进行操作的方法,示例如下:

  取记录集:

  ZDUser user = new ZDUser ();

  user.setAddUser("admin");

  DAOSet<ZDUser> set = user.query();// 查询所有由admin添加的用户

  for (int i = 0; i < set.size(); i++) {

  ZDUser u = set.get(i);

  System.out.println(u.getUserName());

  }

  

  添加新的DAO到DAOSet中:

  ZDUser alex = new ZDUser ();

  alex.setUserName("alex");

  alex.fill();// 查询出alex的所有字段

  set.add(alex);// 将alex加入到set中

  

  记录集反馈回数据库:

  set.insert();//将记录集插入数据库

  set.update();//将记录集更新到数据库

  set.delete();//从数据库中删除记录集

  set.backup();//备份记录集到B表

  set.deleteAndBackup();//删除记录集同时备份到B表

  set.deleteAndInsert();//插入记录,插入之前先删除己有的记录,以避免可能的主键冲突

 

  其他操作:

  set.clear();// 清空Set中的所有Schema

  set.clone();// Cloneable

  set.toDataTable();// 转换成DataTable;

  set.filter(new Filter() {// 只要Email是hotmail邮箱的用户

  public boolean filter(Object obj) {

  ZDUserSchema user = (ZDUserSchema) obj;

  if (user.getEmail().endsWith("@hotmail.com")) {

  return true;

  }

  return false;

  }

  });

  set.sort(UserName);

  set.sort(new Comparator() {// 按添加时间排序

  public int compare(Object obj1, Object obj2) {

  ZDUser user1 = (ZDUser) obj1;

  ZDUser user2 = (ZDUser) obj2;

  return user1.getAddTime().compareTo(user2.getAddTime());

  }

  });

  Dao更详细的说明,请参见集合DAO--DataTable

 

5. 事务支持

  Zving Framework支持传统型事务和非阻塞事务两种事务模式

5. 1 传统型事务

  传统型事务即JDBC本身提供的事务支持,在这种事务模式下,一开始便打开数据连接,然后执行Java业务逻辑,在Java逻辑中不时执行数据库操作,但这些操作并不立刻改变数据库中的数据,而是等最后执行commit动作时才一次性写入数据库。Zving Framework针对这种类型的事务提供了DataAccess类,代码示例如下:

  DataAccess da = new DataAccess();

  try {

  da.setAutoCommit(false); 

  da.executeNoQuery(new QueryBuilder("delete from zduser where username='test'"));

  da.insert(user);

  da.delete(alex);

  da.commit();

  da.setAutoCommit(true); 

  } catch (SQLException e) {

  try {

  da.rollback();

  } catch (SQLException e1) {

  e1.printStackTrace();

  }

  e.printStackTrace();

  } finally {

  try {

  da.close();

  } catch (SQLException e) {

  e.printStackTrace();

  }

  }

  注意:使用DataAccess需要手动调用close()方法,以实现连接的关闭。

 

5. 2 非阻塞事务

  非阻塞事务是Zving Framework中提供的事务模式,在这种事务模式下,一开始并不占用连接,执行Java业务逻辑过程不时声明需要执行的操作,在最后执行commit动作时才申请连接并依次执行先前声明的操作,并且开发人员不需要手工管理连接。代码示例如下:

  Transaction tran = new Transaction();

  tran.add(new QueryBuilder("delete from zduser where username='test'"));

  tran.add(user, Transaction.INSERT);

  tran.add(alex, Transaction.DELETE);

  tran.add(set, Transaction.DELETE_AND_BACKUP);

  tran.commit();

  同时Transaction还提供了一些更简单的书写方式。代码示例如下:

  tran.insert(param);

  tran.update(param);

  tran.delete(param);

  tran.backup(param);

  tran.deleteAndBackup(param);

  tran. deleteAndInsert(param);

  上述代码中param可以为DAO或DAOSet。

  在传统型事务中下一个数据库操作可以依赖于上一个数据库操作产生的结果,但无阻塞事务则不能。而传统型事务数据库一开始就占用连接,在两个数据库操作之间的业务逻辑处理过程中本不需要连接,但也一直被占用;而无阻塞型事务则只有最后才使用连接,连接占用的时间被压缩到了极致。如果Web应用同时在线用户数较大且业务较复杂,则无阻塞型事务比传统型事务具有相当大的性能优势。另一方面,传统型事务必须手工管理资源释放,而无阻塞型事务则不需要。

  实践开发中几乎全部事务都可以改造成无阻塞事务,建议开发人员只使用无阻塞型事务。

  有关事务更详细的说明,请参见集合Transaction-事务类

 

hackIE