Risenet Database组件初级使用教程
我们的产品将运行在各种环境中,不同的操作系统(w2k、Linux、HP UNIX、Sun Solaris等),不同的应用服务器(BEA WebLogic、IBM WebSphere、Novell WebApps、Sybase EAServer等),不同的数据库(Oracle、Sql Server、Sybase等),不同的目录服务(Novell eDirectory、Netscape LDAP Server、Open LDAP等)。
Java虚拟机使我们不用关心操作系统的细节,J2EE规范让我们的程序可以运行在各种应用服务器上,我们的平台接口使我们可以调用各种目录服务,而Database组件将协助我们“跨越”不同的数据库。
下面是Database组件中定义的基本接口和功能描述
Insert 向数据库表中插入记录
Update 更新记录
Execute 可以执行INSERT、UPDATE、DELETE语句或DDL语句
Lob 读写LOB数据
PrimaryKey 获取主键信息
对于不同的数据库,工厂(DatabaseFactory)将创建不同的对象,这些对象实现了上面的这些接口,如:对于Oracle数据库,创建Insert对象,实际返回的是OracleInsert,只不过OracleInsert实现了Insert接口,你完全可以认为,那就是Insert对象。
我们看看如何用这些“接口”操作真正的数据库,先创建一个测试表,使用各种字段类型,包含BLOB和CLOB字段,由id和name作为复合主键(注1):
CREATE TABLE DATABASE_TEST_TABLE
id number(10,0) NOT NULL,
name char(38) NOT NULL,
postTime DATE,
fileName varchar2(255),
photo blob,
photoDesc clob,
PRIMARY KEY (id, name),
)
(注2)
首先看看最简单的,Execute定义了用于“执行sql”的对象,此处的“执行”是与查询相对应的,可以执行INSERT、UPDATE、DELETE语句,也可以执行SQL DDL (Data Definition Language)语句,如:CREATE TABLE、 DROP TABLE 或者 ALTER TABLE。使用方法:先创建一个对象,然后执行execute()方法,此对象的execute()方法可以反复使用,执行多个sql语句,如:(注2)
Execute e = DatabaseFactory.createExecute();
e.execute(insert into DATABASE_TEST_TABLE(id,name) values(88,'name1'));
e.execute(delete from DATABASE_TEST_TABLE where id=88 and name='name1');
接下来看看如何插入一条记录,很简单,先创建一个Map对象,将各个字段的值按照字段名放入Map对象中,然后从DatabaseFactory中创建一个Insert对象,执行insert.execute()就可以了,代码如下:
HashMap map = new HashMap();
map.put("id", new Integer(100);
map.put("nAMe", 'HERE is Name Field');
map.put("postTime", new java.util.Date());
map.put("FileName", "hhh.txt");
String s = "这是一个测试字符串。";
map.put("photo", s.getBytes());
map.put("photoDesc", s);
Insert insert = DatabaseFactory.createInsert(DATABASE_TEST_TABLE, map);
int return = insert.execute();
Update的使用方法和Insert几乎完全一样,我们就不再赘述了。
Lob是用来读写大对象字段的对象,对Oracle是blob和clob,对Sql Server是image和text。不过用它来写数据实在有点麻烦(注3),还是用Insert和Update来写比较方便。用它来读倒是很方便,只要定位到要读取数据的记录,代码如下:
Char[] charBuf;
Lob lob = DatabaseFactory.createLob();
String sql=“SELECT * FROM DATABASE_TEST_TABLE where id=88 and name='name1'”
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
charBuf = lob.readBLOB(rs, "photo"); //读BLOB数据
}
rs.close();
有时,并不能确定记录是否存在,如果存在就更新,不存在就插入,可以使用Operate类来完成,代码如下:
Operate p = DatabaseFactory.createOperate(TEST_TABLENAME, map);
op.execute();
说了这么的多,我们没有涉及最重要的数据库操作--查询,我们将把它放在RiseTable组件中介绍。
补充说明:
1、我们使用log4j来处理日志,如果不希望Database组件输出debug信息,请参考log4j的文档,修改net.risesoft.commons.log.LogFactory或相关配置文件,将Database组件的输出调至info或更高级别。
注1:表必须有主键,否则不能处理。
注2:对于创建表或执行某些sql语句,这些语句可能是数据库相关的,即对于不同的数据库,语句是不同的,如:创建表、左关联查询、不同数据库函数等情况,下面示例如何用Execute处理:
Execute e = DatabaseFactory.createExecute();
e.setSQL("oracle", oraclesqlString);
e.setSQL("mssql", mssqlString); //指定MS SQL Server使用mssqlString语句
e.setSQL(defaultsqlString); //设置缺省的
e.exeucte();
数据库组件将根据当前的数据库产品,自动调用相应的sql语句,如果没有对应的,就使用缺省的sql语句。
注3:如果真的要用Lob对象写数据,请仔细阅读实现Lob接口的各个子类的文档,并且很可能因此丧失跨数据库特性。所以,强烈建议,也使用Insert、Update来操纵Lob数据
??如何让我们的应用程序可以在不修改源代码、不用重新编译的情况下,运行于不同的数据库?如果能够跨越数据库,我们的产品将具有更强的适应性,更高的灵活性,具有更强的市场竞争力。
??JDBC作为Java操纵数据库的标准工具,在很大程度上消除了不同数据库的差异,如:getString(String fieldName)方法,对于各种数据库,都是获取字段的字符串值,对于Sql Server或Sybase,字段类型可能是varchar,对于Oracle,字段类型可能是Varchar2。但是,仅仅依靠jdbc还很不够,各个数据库对于我们还是有很大的差异,比如插入日期,Oracle的语法是:TO_Date('2003-5-1 15:24:59','YYYY-MM-DD HH24:MI:SS'),这肯定和Sql Server或Sybase不同,对于大对象字段的处理,各个数据库产品更是各显神通,没有统一的标准。
??长期以来,没有一个完美的方法解决这一难题,以前没有,以后也不会有,因为这个世界的变化太快了,我们只有“暂时”的解决方案。
??数据库产品非常复杂,基于数据库的应用更是百花齐放,如果我们的目光被这些奇花异草所吸引,可能永远都无法处理这个问题。我们要把目光放在树根上,所有这一切的最根本、最基础的就是我们所熟知的对数据库的操作:增删改查,只要我们很好的处理了这四种操作,也就可以实现跨数据库了。
??查询数据库可能是最复杂、使用频率最高的操作,我们将在RiseTable组件中专门介绍,今天只处理“增删改”操作。
??我们先来讨论新增,一定要用java的面向对象的设计思想来看待新增操作,我们所需要的是一个数据库的链接,数据库表名和要插入的数据,数据中要包含字段名和值,这正好可以用java.util.Map表示,所以,我们设计了Insert接口,能够接受表名和数据,当然也可以指定数据库链接,如果不指定的话,我们就自己创建一个,当执行Insert接口execute()方法时,数据就插入了,如果发生错误,抛出SQLException。接口设计完成后,我们需要针对不同的实据库进行实现,于是,就有了OracleInsert、SqlInsert、SybaseInsert。而对于setConnection()、setTableName()等无聊的方法,我们将他放在AbstractInsert中,这样各个子类只需要实现execute()方法就可以了。
(Insert接口及其实现的类图)
??修改和新增非常相似,我们采用几乎完全相同的方法进行设计,我们就不再赘述了,Update类图在这里。
??对于删除操作,我们并没有设计Delete接口,因为那没有什么意义,相反我们设计了Execute接口,用于执行各种数据库操作,并且使用DefaultExecute作为缺省实现。此处的“执行”是与查询相对应的,可以执行INSERT、UPDATE、DELETE语句,也可以执行SQL DDL (Data Definition Language)语句,如:CREATE TABLE、 DROP TABLE 或者 ALTER TABLE。当然,为了我们的目标--跨越数据库,要执行的sql必须是各个数据库通用的,或者是针对每个数据库进行“本地化”的。对于删除记录这样的操作,如果只使用字符串或数字作主键,那么delete from table1 where id=9对于各个数据库是通用的,如果使用了日期等比较特殊的字段作为主键,那么我们可以用TableUtil的convert2DataStr(),转换成该数据库中的日期的字符串表示。如果要执行的sql语句没有简单的换算关系,即对于不同的数据库,语句可能是完全不同的,这可以用资源文件、或针对不同的数据库子类化进行处理。
(Execute接口及RSMetaData类图)
??操作大对象字段的Lob接口,提供实用方法的TableUtil接口,也是通过针对不同的数据库进行子类化来实现的,类图在这里。
??现在,我们有了统一的接口,和一大堆针对不同数据库的子类,对于Oracle数据库,我们将使用OracleInsert、OracleUpdate、OracleLob、OracleTableUtil等等,如何正确的创建这些对象成了最大的问题。我们使用Abstract Factory和Singleton设计模式来解决这个问题,在DatabaseFactory中决定使用哪一个工厂,对于Oracle使用OracleFactory,对于Sql使用SqlFactory,并且保持了工厂的单一实例,由那个工厂去创建自己的对象。
(DatabaseFactory及各个数据库的Factory的类图)
??现在,Database组件的整体结构已经有了,但如果仅仅支持Oracle数据库的话,就失去存在的必要了。我们需要加入对其它数据库的支持,如某种数据库XXX,我们需要实现XXXFactory、XXXInsert、XXXLob等,并且把他们放在net.risesoft.commons.database.xxx包下,在DatabaseConstant中加入对此数据库的标识Xxx,然后,完成了。
??真的完成了吗?至少我们需要对它进行测试,可以使用net.risesoft.commons.database包下的AllDatabaseTests进行unit test,然后,再实际测试我们的产品。
Powered by Discuz! X2.5
© 2001-2012 Comsenz Inc.