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=?"http://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();