这个小小的数据库操作封装框架是参考IBM开发网上的两篇文章并在其基础上扩充了一些功能而得到的。所以首先要感谢两篇文章的作者。
学习JDBC以来一直想实现一个简单的封装来方便编程但是由于水平有限一直没有较好的办法,看了IBM开发网上的两篇文章以后感觉作者的设计思想很好一定能扩充成一个实用的JDBC封装。所以我在文章提供的源码基础上加了一些功能这些功能包括支持多种数据类型,处理了空值,利用反射方便的在Row对象和值对象之间进行转换,还有加了一个我自认为通用的DAO类来方便用户的操作。
我把源码提供出来有两个目的一个是希望能帮助比我还初学的初学者熟悉JDBC,另外就是请各位高手不吝赐教,改进程序中的错误如果能将你们的对JDBC的封装方法提供出来那就更好了(不要说你们只用EJB或者Hibernate,JDO什么的?)。
IBM开发网的那两篇文章分别是《一个简单的 JDBC 包装器》《对一个简单的 JDBC 包装器的扩展及应用》,我的邮箱是xsimple2003@yahoo.com.cn有事请与我联系。
设计思想
把DBMS抽象成类Database,这个类负责管理数据库连接以及提供表对象。
把数据库中的一张或多张表抽象成类Table,这个类中提供对表的添加,修改,删除的JDBC封装。
将数据库表中的一条记录抽象成类Row,这个类用HashMap保存关系数据库中表格中一行数据的字段名和值并提供一些相关操作。另外这个类还提供了两个静态方法用于在Row对象和ValueObject之间进行方便的转换。
把对个Row的集合抽象成RowSet,这个类中用一个vector把多个Row对象保存起来并提供一些相关操作。
代码分析
由于已经给出源码所以我只对代码中关键的和需要注意的地方加以说明,大家可以执行源码一边演示一边体会。
Database类源码如下:
package com.gdrj.util.database; import java.sql.*; import javax.sql.*; import com.gdrj.util.servicelocator.*; public class Database { /** * 这个数据库连接成员只有在与数据库直接建立连接的情况下是有效的 */ private Connection conn = null; /** * 当这个参数有效时,表明程序是直接与数据库建立的连接而不是从连接池里取得连接 */ private String url, user, password; /** * 当这个参数有效时,表明程序是从连接池里取得连接。 */ private String datasource; /** * 用数据库地址,用户名,密码初始化数据库对象,这个构造器用于程序是直接 * 与数据库建立连接的情况。 * @param url * @param user * @param password */ |
public Database(String url, String user, String password) { /** /** /** |
catch (Exception ex) {} /** |
这个类是对DBMS的抽象,所以使用时应用程序中只要有一个Database对象就够了,如果你是以与数据库之间建立连接的方式使用那么你用Database(String url, String user, String password)构造器进行初始化。如果是从应用服务器的连接池中取得连接的方式使用那么用Database(String datasource)构造器初始化,这样以后你使用这个对象进行getConnection和disConnection时就不用去考虑始终保持一个连接(C/S方式),还是将连接返回连接池了因为在disConnection中已经做了处理。集体使用方法将Table类。在getConnection中的从连接池中取连接的代码你只要参考以下《J2EE核心模式》中的服务定位器模式就知道是怎么回事了,你在用Database(String url, String user, String password)初始化时其中的代码不起作用。
Table类源码如下:
package com.gdrj.util.database; import java.sql.*; import java.util.*; import com.gdrj.util.*; public class Table { /** * 通过这个数据库对象得到数据库连接 */ private Database database; /** * 数据库中一个或多个(只限查询)表的名 */ private String name; /** * 初始化表对象,此时不作任何数据库相关操作 * 一般通过database的getTable调用 * @param database * @param name */ public Table(Database database, String name) { this.database = database; this.name = name; } /** * 查询某一行 * @return */ |
public Row getRow(String fields, String criteria, Object[] args) throws |
return null; } while (rs.next()) { Row row = new Row(); for (int i = 1; i <= cols; i++) { String name = rsmd.getColumnName(i); Object value = rs.getObject(i); //作通用类型处理,这样row中的类型都是Object型的。 /** * 这里要做空值处理,因为在进行RowToValueObject转换时如果是空值则不能得到值的类型 * 所以如果是空值那么把value设置成类型信息 */ if (value == null) { value = Class.forName(rsmd.getColumnClassName(i)); } // System.out.println(value.getClass());//用于得到数据库中的类型对应Java中的什么类型 row.put(name, value); } rows.add(row); } rs.close(); pstmt.close(); } catch (Exception ex) { throw new DBAccessException(InforGeter.getErrorInfor(this, "executeQuery", ex, "执行SQL(" + sql + ")查询时出错!")); } finally { database.disConnect(conn); //调用数据库对象的释放连接方法(此方法内对取得连接方式的不同情况做了处理) } return rows; } /** * 增加一行 * @param row */ public int putRow(Row row) throws DBAccessException { return putRow(row, null, null); } /** * 修改一行(没有条件就是增加) * @param row * @param conditions */ public int putRow(Row row, String conditions, Object[] args) throws DBAccessException { String ss = ""; |
int affectableRow = 0; //执行SQL后影响的行数 |
catch (Exception ex) { /** for (int i = 0; i < row.length(); ++i) { |
/** * 有条件的删除即删除多行 * @param condition * @param args */ public int delRow(String condition, Object[] args) throws DBAccessException { String ss = ""; int affectableRow = 0; ss = "delete from " + name + " where "; ss += condition; Connection conn = null; try { conn = database.getConnection(); PreparedStatement st = conn.prepareStatement(ss); if (args != null) { for (int i = 0; i < args.length; i++) { st.setObject(i + 1, args[i]); } } affectableRow = st.executeUpdate(); st.close(); } catch (Exception ex) { throw new DBAccessException(InforGeter.getErrorInfor(this, "delRow", ex, "删除表" + name + "中的数据时出错!")); } finally { database.disConnect(conn); } return affectableRow; } } |
使用时可以用Database对象的getTable方法传入数据库表的名称来得到一个Table对象。得到这个对象后就可以对这个数据库表进行操作了,这个类提供了六个方法根据传过来的参数对数据库表进行添加修改删除操作。代码中没有特别难懂的地方,需要注意的是我在原有代码的基础上对空值进行的处理,在查询时如果表中的数据是空值的话那么我把字段对应的Java类型放到Row对象里,因为在进行Row对象到值对象的转换时用到了java反射API必须知道Row中的字段值的类型才能去调用值对象的setXXXX方法(见Row对象的toValueObject方法)。
行对象的源码如下:
package com.gdrj.util.database; import java.util.*; import java.math.BigDecimal; import java.lang.reflect.*; public class Row { /** * 排序,由于Hashtable不提供通过索引取得值的方法,并且其中的键值对也不是按照put上去时的顺序排列的。 * 注意:Vector中加入的对象是有序的,即按加入的顺序排列并且能够根据索引访问,可以看成是可变大小的数组 * List可以取代Vector |
*/ private Vector ordering = new Vector(); /** * 存放键值对(表中字段名称与字段值) */ private HashMap map = new HashMap(); public Row() { } /** * 向HashMap中追加键值对,即字段名称与字段值 * @param name * @param value */ public void put(String name, Object value) { if (!map.containsKey(name)) { ordering.addElement(name); //将键保存起来 } map.put(name, value); } /** * 得到行对象中字段的个数 * @return */ public int length() { return map.size(); } /** * 根据字段名称取得字段值 * @param name * @return */ public Object get(String name) { return map.get(name); } /** * 根据字段在HashMap中的编号取得字段值 * @param which * @return */ public Object get(int which) { String key = (String) ordering.elementAt(which); return map.get(key); } /** * 根据字段序号取得字段名称 * @param which * @return */ public String getKey(int which) { String key = (String) ordering.elementAt(which); |
return key; |
public static Row fromValueObject(Object vo) throws Exception { Row row = new Row(); Class type = vo.getClass(); //得到Class用于进行反射处理 Field[] fields = type.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { String name = fields[i].getName(); String methodName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); Method method = type.getMethod(methodName, new Class[] {}); Object value = method.invoke(vo, new Object[] {}); String nameInRow = toInRowName(name);//在此进行值对象中的名称向行对象中的名称转换 row.put(nameInRow, value); } return row; } /** * 将值对象中属性名转换成对应的行对象中的字段名(因为行对象中的字段名 * 在更新数据库时必须与数据库表中字段名完全匹配) * 一般规则为 fsiId ---> fsi_id(现在假设的情况是如果出现有两个单词 * 以上的值对象属性名则数据库表中的字段名一定是有下划线的) * @param voName * @return */ public static String toInRowName(String voName) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < voName.length(); i++) { //遍历voName如果有大写字母则将大写字母转换为_加小写 char cur = voName.charAt(i); if (Character.isUpperCase(cur)) { sb.append("_"); sb.append(Character.toLowerCase(cur)); } else { sb.append(cur); } } return sb.toString(); } } |
Row对象中用了一个HashMap对象存放着对应数据库表中的字段名和对应值,由于Map对象的无序性,所以用了一个vector(当然也可以用List代替)来存放字段名(按用户添加的顺序)这样就可以提供get(int i)方法来顺序取得Map中的值了。要注意的是三个静态辅助方法toValueObject,fromVauleObject,toInRowName。toValueObject方法用于将一个行对象转换为值对象方法中利用了Java的多态和反射机制(请大家参考反射API)。FromValueObject是上一个方法的逆操作,toInRowName方法是实现值对象中的属性名向数据库表中字段名的转换,因为一般在数据库建表时是用的这种形式stu_id,而Java中JavaBean的属性是这样的stuId。
RowSet的代码如下:
package com.gdrj.util.database; import java.util.*; public class RowSet { private Vector vector = new Vector(); public RowSet() { |
} public void add(Row row) { public int length() { public Row get(int which) { public void dump() { |
这个类就是把Row对象放到Vector以便操作。就不多说了。
为了方便使用我写了一个GeneralDAO类(我对DAO模式还在理解中请各位高手批评指教)代码如下:
package com.gdrj.util.database; import java.util.*; public class GeneralDAO { /** * 这个DAO对应的表对象 */ private Table table; /** * 默认构造函数 */ public GeneralDAO() { } /** |
getTable(db, tableName); /** /** /** |
* @throws java.lang.Exception */ public int deleteData(Object vo) throws Exception { return table.delRow(Row.fromValueObject(vo)); } /** * 删除多条数据 * @param condition 删除条件 * @param args与条件对应的参数数组 * @return * @throws java.lang.Exception */ public int deleteDatas(String condition, Object[] args) throws Exception { return table.delRow(condition, args); } } |
这个DAO类是对Table类的一个方便的封装。用户如果向操作数据库只要创建一个Database对象,一个DAO对象,一个值对象(对应表结构),然后就可以进行方便的数据库操作了,下面给出一个实例来演示这个小小框架的用法。
演示程序
首先建立一个teacher表,语法如下
create table teacher ( id int not null, name varchar(20) not null, birthday smalldatetime null, address varchar(100) null, income money null, constraint id PRIMARY KEY NONCLUSTERED ( id ) ) |
然后建立一个与teacher表对应的值对象类。
public class TeacherVO implements Serializable { private Integer id; private String name; private String address; private BigDecimal income; private java.sql.Timestamp birthday; public TeacherVO() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } |
public void setName(String name) { this.name = name; } public java.sql.Timestamp getBirthday() { return birthday; } public void setBirthday(java.sql.Timestamp birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public java.math.BigDecimal getIncome() { return income; } public void setIncome(java.math.BigDecimal income) { this.income = income; } public String toString(){ return " 编号:" + id + " 姓名:" + name + " 生日:" + birthday + " 地址:" + address + " 收入:" + income; } } |
最后主程序的源码如下:
package org.together.jdbcwrap.test; import java.util.*; import com.gdrj.util.database.*; public class GeneralDAOExample { |