JDBC 2.0 API 包括两个package:
1. java.sql 为JDBC 2.0的核心包,其中包括了JDBC 1.0规范中规定的API和新的核心API,这个包包含于Java 2 Standard Edition中.
2. javax.sql 为JDBC 2.0的标准扩展包,相对与JDBC 1.0而言是全新的,这个包包含于Java 2 Enterprise Edition中.
★ 采用了新的方法连接数据库 DataSource Interface
JDBC 2.0 提供了新的接口DataSource用来实现数据库连接,可以替代1.0中提供的DriverManager类。好处是:
☆ 增强了代码的可移植性
☆ 方便了代码的维护
一个DataSource对象代表一个实际的数据源。这个数据源可以是从关系数据库到表格形式的文件,完全依赖于它是怎样实现的.一个数据源对象注册到JNDI名字服务后,应用程序就可以从JNDI服务器上取得该对象,并使用之和数据源建立连接.
数据源及如何装入的信息(名字,地址,端口等等)以Properties的形式保存在DataSource对象中.这样就增强了应用程序的可移植性,
因为程序中不需要像使用DriverManager那样给出硬性的驱动器名字(往往包含了特定厂商的名字).
这种做法还增强了代码的可维护性,比如数据源移植到另一台服务器上后,
所需要作的就是更新一下相关的property,使用数据源的代码更不不必改动.
系统管理员或者有相应权限的人实现DataSource对象.
DataSource对象的实现需要设置对象的properties并把它注册到JNDI名字服务器上, 这些活动可能会用特定工具来实现.
系统管理员用一个逻辑名字对应DataSource对象,这个名字可是是任意的.
在下面的例子中DataSource对象的名字是InventoryDB. 依照传统习惯, DataSource对象的名字包含在jdbc下,
所以这个数据源对象的完整名字是:jdbc/InventoryDB.
实现数据源对象后, 应用程序员就可以使用它来建立和数据源之间的连接了. 下面的代码片断演示了通过这种方式获得连接. 上面两行使用了JNDI API获得DataSource对象,第三行代码使用JDBC API获得连接:
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("jdbc/InventoryDB"):
Connection conn = ds.getConnection("password","username");
DataSource对象中获得的的Connection对象和用DriverManager.getConnection方法获得的对象是等同的.
由于DataSource方法的优点, 该方法成为获得连接的推荐方法. 所有基于JDBC
2.0的驱动器应该会包含DataSource接口的实现以及javax.sql包.
对普通程序员而言, DataSource对象方法只是一种选择. 如果要使用连接缓冲池(Connection pooling)或者分布式交换, 则必须使用DataSource对象获得连接.原因在下文中阐述.
★ Connection Pooling
连接缓冲池是这样工作的:当一个应用程序关闭一个连接时, 这个连接并不真正释放而是被循环利用.因为建立连接是消耗较大的操作, 循环利用连接可以显著的提高性能,因为可以减少新连接的建立.
比如一个应用程序需要连接一个由名字是EmployeeDB的DataSource对象代表的数据源, 通过缓冲池获得连接的代码演示如下:
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("jdbc/EmloyeeDB");
Connection con = ds.getConnection("password","username");
因为连接的数据源不同所以和上面的例子中的名字不一样.
DataSource.getConnction方法返回的连接是否被缓存完全依赖于该DataSource对象的实现方式.
如果它将用于支持连接缓冲的中间层服务器(Middle Tier Server), 则DataSource对象将自动返回将被缓存、循环利用的连接.
基本上不需要改变任何代码就可以获得缓冲池连接. 唯一需要注意的就是(我们已经这样做了:P),需要在finally块中释放连接,这应该是释放所有连接的较好方法.这样, 即使抛出了异常, 连接也会还给连接缓冲池:
finally{
if(con != null) con.close();
}
finally块保证了连接的循环利用.
★ Distributed Transactions(分布式交换)
获得用于分布式交换的情形类似于获得缓冲池连接. 差别仍然是DataSource对象的实现方式, 而不是获得连接程序代码的不同.
假定DataSource类被实现为用于中间件的分布式交换设施,下面的代码将获得用于分布式交换的连接:
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("jdbc/EmloyeeDB");
Connection con = ds.getConnection("password","username");
出于性能的考虑, 用于获得分布式交换的连接的DataSource对象差不多总是会同时实现缓冲池连接.
从程序员的角度来看, 获得普通连接和获得用于分布式交换的连接之间没有区别.
唯一的不同是交换界限(什么时候开始,什么时候结束)由交换管理器在后台处理. 应用程序不应该做任何妨碍交换管理器的事情.
所以程序不能直接调用commit或者rollback方法, 不能设置auto-commit模式.
下面几行代码中con是可用于分布式交换的Connection对象, 演示了在con参与分布式交换是不能作的事情:
con.commit();
or
con.rollback();
or
con.setAutoCommit(true);
普通连接默认打开auto-commit模式. 用于分布式交换的连接对象默认关闭auto-commit模式. 应注意的是用于分布式交换的连接也可以用于非分布式交换模式, 交换边界的限制仅在连接是分布式交换一部分是有效.
实现支持连接缓冲池的DataSource对象,需要实现ConnectionPoolDataSource对象,
三层构架的中间件的连接缓冲模块将会使用它. 同样, 实现支持分布式交换的DataSource对象也需要实现XADataSource对象,
中间件的分布式交换构件会使用该对象.
ConnectionPoolDataSource和XADataSource对象对程序员而言是完全透明的, 应有驱动程序厂商提供.
★ Rowsets
RowSet对象是一系列rows的容器. 根据不通的目的,可以通过多种方式实现. RowSet接口和其相关接口与JDBC 2.0标准扩展的其他部分的差别是它们不是驱动程序实现的一部分. RowSet对象在驱动程序的上层实现,可以被任何人实现.
任何rowset类型都要实现RowSet接口(继承了ResultSet接口). 所以RowSet对象拥有ResultSet对象的所有功能:可以用getXXX方法取值,用updateXXX方法更新, 移动游标, 执行其他相关任务.
当然rowset也有其新功能.作为JavaBeans组件,
RowSet对象提供了方法监听属性的get/set.其中一个属性是command串,这个属性往往是一个查询,RowSet接口提供可设置
command属性和执行之的方法. 这意味这RowSet对象能够执行自己的检索命令用查询结果填充自己的内容.
或者RowSet对象可以实现用来使用任何表格式的数据源填充自己, 而不是局限于关系数据库.
从数据源取得数据后, RowSet对象可以断开于数据源的连接,这使得该对象很小(如果数据量特别不大). rowset也可以序列化. 所以rowset很小并可以序列化, 一个断开连接的rowset是传送数据给瘦客户机的理想工具.
rowset可以被更新,然后重新连接数据源以传送更新的值. 如果设置了监听者, 当rowset中的游标被移动和内容变化时,会通知监听者.
如, 图形界面组件(如条图)可以被注册为监听者,当rowset上有事件发生将会同志它, 它可以重画自身反映变化.
RowSet接口可以有多种实现方式, 依赖于你想做什么.Sun提供了实现的例子CachedRowSet:
http://developer.java.sun.com/developer/earlyAccess/crs/index.html
如果您想测试DataSource,一种较为方便的方法是使用支持JDBC2.0和JNDI的Application Server,如Jrun,
如果你已经安装了Jrun,则可以到JMC中配置DataSource了,请看Jrun手册学习使用DataSource的方法。
★通过ResultSet进行数据更新
Statement stmt2 = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs2 = stmt.executeQuery("SELECT * FROM user WHERE username='aa'");
rs2.next();
rs2.updateDate("lastdatetime", new Date(Calendar.getInstance().getTimeInMillis()));
rs2.updateRow();
这里面最主要的特征就是ResultSet.TYPE_FORWARD_ONLY和ResultSet.CONCUR_UPDATABLE,通过初
始化Statement时传不同的参数,可以对ResultSet进行不用的错作限制。con.createStatement的时候,有三种可以掉用的
函数:
1、createStatement();
2、createStatement(int resultSetType, int resultSetConcurrency)
3、createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
其中resultSetType可选值是:
1、ResultSet.TYPE_FORWARD_ONLY 在ResultSet中只能先前移动游标,
2、ResultSet.TYPE_SCROLL_INSENSITIVE 在ResultSet中可以随心所欲的先前向后移动游标,
3、ResultSet.TYPE_SCROLL_SENSITIVE 在ResultSet中可以随心所欲的先前向后移动游标,同时ResultSet的值有所改变的时候,他可以得到改变后的最新的值
其中resultSetConcurrency可选值是:
1、ResultSet.CONCUR_READ_ONLY 在ResultSet中的数据记录是只读的,可以修改
2、ResultSet.CONCUR_UPDATABLE 在ResultSet中的数据记录可以任意修改,然后更新会数据库
其中resultSetHoldability可选值是:
1、ResultSet.HOLD_CURSORS_OVER_COMMIT 表示修改提交时,不关闭ResultSet的游标
2、ResultSet.CLOSE_CURSORS_AT_COMMIT 表示修改提交时,关闭ResultSet的游标
对于查询操作第一种初始化方法createStatement(),相当于第二种方法的createStatement
(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY),第三种方法的createStatement
(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY,
ResultSet.CLOSE_CURSORS_AT_COMMIT)
下面写一段demo的代码,我把一些特征函数都用出来,但是只是用来查考和说明名灵活性的。
Statement stmt2 = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs2 = stmt.executeQuery("SELECT * FROM user");
rs2.next();
rs2.updateDate("lastdatetime", new Date(Calendar.getInstance().getTimeInMillis()));
rs2.updateRow();//更新当前行
rs2.afterLast();
while(rs2.previous()){ /**....*/ }
rs.beforeFirst();
while(rs2.next()){ /**....*/ }
rs.last();
rs.first();
rs.absolute(5); //游标移动到第5条
rs.absolute(-1); //游标移动到最后一条
rs.relative(-5); //游标向上移动5条
rs.relative(2); //游标向下移动2条
rs.deleteRow(); //删除当前行
rs.last(); //游标移动到最后
rs.updateString("summary", "This is ..."); //设置更新的字段值
rs.cancelRowUpdates(); //取消刚才输入的更新
rs.getRow(); //得到当前行号
rs.moveToInsertRow(); //游标移动到要新增的那条记录上
rs.updateInt("id", 1);
rs.updateString(2, "my name");
rs.insertRow(); //插入新记录
/////另外说明:要实现rs.moveToInsertRow(),要注意Select语句
1.Select语句不能用*,必须用Select A, B, C from table. (A,B, C是要Insert的字段)
2.Select语句只能是单表,不能存在两个或以上的表。
3.updateString的字段必须在Select语句中列出。(如上面, 只能更新或插入A, B, C字段)
4.用ResultSet.CONCUR_UPDATABLE参数建立Statement.
5.Select语句不能使用Order by
6.Select语句必须包含主Key或所有not null的字段
★批量更新操作
在JDBC2.0中,Statement、PrepareStatement以及CallableStatement的对象都维护了一个SQL语句列表,使用addBatch()方法向列表中增加SQL语句。列表中的语句将作为一个单元发送到DBMS,以进行批量执行。
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String url="jdbc:odbc:lian";
Connection con=DriverManager.getConnection(url);
Statement stmt=con.createStatement();
con.setAutoCommit(false);
stmt.addBatch("INSERT INTO people " + "VALUES ('蔡义霖', '27')");
stmt.addBatch("INSERT INTO people " + "VALUES ('吴宗先', '28')");
int s[]=stmt.executeBatch();
System.out.println(s[0]);
con.commit();//执行完毕以后一定要手动提交
stmt.close();
}
catch(Exception e)
{
System.out.println(e);
}
★使用SQL3中的数据类型
JDBC 2.0 引进了对应于SQL_99的许多新对象,这些新对象有BLOB,CLOB,ARRAY,REF,结构化类型,DISTINCT类型以及LOCATOR.
JDBC 3.0增加了Boolean和Datalink对象
插入这些高级数据类型到数据库中的主要手段是使用PreparedStatement对象,读取主要是ResultSet对象.下面介绍怎么在数据库中读取和写入高级数据类型
1:BLOB和CLOB
BLOB: 二进制大对象(Binary Large OBject)即一个字节序列(比喻说一个mp3文件可以存储为一个BLOB)
CLOB:一个对VARCHAR 或类似的列来说太长的字符串.
来自数据库的BLOB和CLOB数据可以通过Java.sql.Blob和java.sql.clob对象来操作.
ResultSet 和PreparedStatement对象提供的处理这两种数据的方法如下
ResultSet : PreparedStatement
Blob getBlob(int) void setBlob(int ,Blob)//第一个参数是PreparedStatement中的占位符的索引,以下相同
Blob getBlob(string) void setClob(int ,Clob)
Clob getClob(int)
Clob getClob(String)
使用PreparedStatement.setBlob(int,Blob)我们可以用BLOB数据来设置预备语句中的占位符,并且可以通过执行SQL语句把这些数据写入到另一个表中
如:
String sql="select blob_col from blob_table where id=?"//blob_colum ,id为blob_table 这个表的列名
PreparedStatement ps=connection.prepareStatement(sql);
ps.setInt(1,1);
ResultSet rset=ps.executeQuery();
Blob blob=null;
if(rset.next())
{
blob=rset.getBlob(1);
}
上叙中blob只是持有一个指向数据库中这些二进制数据的引用.并不持有实际二进制数据,然后代码可以使用这个相同的引用把这些二进制数据写入到另外的一个表中:
sql="insert into blob_table_2 values(?)";
ps=connection.prepareStatement(sql);
ps.setBlob(1,blob);
ps.executeUpdate();
jdbc 2.0中的BLOB和CLOB借口提供了一种从数据库中获取数据或写数据到数据库的手段,这个手段是通过从数据库中获得一个流(输入或者输出)对象.并从该流中读取数据或写入.
例:
OutputStream out=null;
BufferedInputStream in=null;
File file=new File("****");
ReslutSet rset=statement.executeQuery(sql);//从查询语句中取得一个结果集
if(rset.next())
{
Blob blob=rset.getBlob(1);
out=((Oracle.sql.Blob)blob).getBinaryOutputStream();//jdbc 2.0不支持写数据到blob,因此我们用Oracle的扩展
int bufferSize==((oracle.sql.Blob)blob).getBufferSize();
in=new BufferedInputStream(new fileInputStream(file),bufferSize);
byte[] b=new byte[bufferSize];
int count=in.read(b,0,bufferSize);
//开始存储数据到数据库中
while(cout!=-1)
{
out.write(b,o,count);
cout=in.read(b,o,bufferSize);
}
//数据写完
out.close();
in.close();
connection.commit();//提交改变
........
}
类似的,我们可以从blob中得到一个输入流,把blob数据写入到文件中去
InputStream in=blob.getBinaryStream();
int bufferSize =((oracle.sql.Blob)blob).getBufferSize();