對象持久化,也就是可以把這個對象永遠的儲存起來,這裡的儲存不僅是對象本身,還包括他的屬性和所依賴的其他類。通常,對象可以持久化到檔案或者是資料庫中。我這裡只介紹如何將Object Storage Service到資料庫中。恰巧Oracle資料庫為我們提供了這樣的方便。
在Oracle中,有一種blog的欄位類型,它是用來儲存大量的位元據的。我們就利用這個欄位去儲存物件資訊。 首先建立一個測試表:
create table TESTBLOB ( NAME VARCHAR2(50) not null, CONTENT BLOB not null, ID NUMBER(8) not null )alter table TESTBLOB add constraint IDFORTEST primary key (ID); |
只用三個欄位,其中id是屬性,content是我們要儲存物件的欄位。
先來看看我們要存入的對象:
import java.io.Serializable; import java.util.Date; import java.util.List; public class TestObject implements Serializable { private static final long serialVersionUID = 4558876142427402513L; /** * @param args */ private String name; private String password; private Date date; private List<City> cityList; public List<City> getCityList() { return cityList; } public void setCityList(List<City> cityList) { this.cityList = cityList; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } |
記得要實現Serializable介面,可以看到這是一個包含了string,date,和list類型的對象,為了給測試增加複雜度,我們的list是另外一個對象(city)的list,如下:
import java.io.Serializable; public class City implements Serializable{ private static final long serialVersionUID = 4558876127402513L; private String name; private String code; public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
City對象包括了城市名稱和區號。下面是主要的應用了。
import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.Date; import java.util.List; import oracle.sql.BLOB; public class Test { public static void main(String[] args) { //建立測試用對象 City beijing = new City(); beijing.setName("北京"); beijing.setCode("010"); City shanghai = new City(); shanghai.setName("上海"); shanghai.setCode("020"); City tianjin = new City(); tianjin.setName("天津"); tianjin.setCode("021"); List<City> cityList = new ArrayList<City>(); cityList.add(beijing); cityList.add(shanghai); cityList.add(tianjin); TestObject obj = new TestObject(); obj.setName("yangsq"); obj.setPassword("111"); obj.setDate(new Date()); obj.setCityList(cityList); try{ //將對象存入blob欄位 ByteArrayOutputStream byteOut=new ByteArrayOutputStream(); ObjectOutputStream outObj=new ObjectOutputStream(byteOut); outObj.writeObject(obj) ; final byte[] objbytes=byteOut.toByteArray(); Class.forName("oracle.jdbc.driver.OracleDriver"); Connection con = DriverManager.getConnection( "jdbc:oracle:thin:@***.***.***.***:1521:****", "yangsq", "yangsq"); con.setAutoCommit(false); Statement st = con.createStatement(); st.executeUpdate("insert into TESTBLOB (ID, NAME, CONTENT) values (1, 'test1', empty_blob())"); ResultSet rs = st.executeQuery("select CONTENT from TESTBLOB where ID=1 for update"); if (rs.next()) { BLOB blob = (BLOB) rs.getBlob("CONTENT"); OutputStream outStream = blob.getBinaryOutputStream(); outStream.write(objbytes, 0, objbytes.length); outStream.flush(); outStream.close(); } byteOut.close(); outObj.close(); con.commit(); //取出blob欄位中的對象,並恢複 rs = st.executeQuery("select CONTENT from TESTBLOB where ID=1"); BLOB inblob = null; if (rs.next()) { inblob = (BLOB) rs.getBlob("CONTENT"); } InputStream is = inblob.getBinaryStream(); BufferedInputStream input = new BufferedInputStream(is); byte[] buff = new byte[inblob.getBufferSize()]; while(-1 != (input.read(buff, 0, buff.length))); ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buff)); TestObject w3 = (TestObject)in.readObject(); System.out.println(w3.getName()); System.out.println(w3.getPassword()); System.out.println(w3.getDate()); List<City> list = w3.getCityList(); for(City city : list){ System.out.println(city.getName() + " " + city.getCode()); } st.close(); con.close(); } catch (Exception ex) { ex.printStackTrace(); System.exit(1); } } }
|
代碼的藍色部分建立了要儲存的對象。再看紅色的對象寫入部分,它首先把對象轉化成二進位流的形式。對於blob欄位,我們不能簡單的在insert時插入,實際上,insert時,對於blob欄位,只能先插入一個空的blob對象empty_blob(),然後再進行"select CONTENT from TESTBLOB where ID=1 for update"對blob欄位進行更新。返回後,我們只要把對象的二進位流寫入即可。
OutputStream outStream = blob.getBinaryOutputStream(); outStream.write(objbytes, 0, objbytes.length); |
需要注意的是,上述步驟必須設定con.setAutoCommit(false),否則oracle會拋出異常。
接下來,綠色的代碼是讀取資料庫中blob欄位的對象,並恢複。我們要知道的是,所有對blob欄位的操作都是二進位的,即插入時是二進位流,讀出時也是二進位流。然後用io修飾器(ObjectInputStream)去修飾。以前學習java時,感覺它的io好繁瑣啊,現在感覺還真是各有其用。下面是測試的輸出結果。
yangsq 111 Tue Mar 27 12:11:28 CST 2007 北京 010 上海 020 天津 021 |
需要說明一下,buff的size一定要足夠大,否則將拋出異常。在這裡,我使用的是inblob.getBufferSize()來設定buff的size,這並不是一種好的方法,因為一般inblob.getBufferSize()都是32768,很可能出現異常,所以這個size最好自己設定,或系統運行時刻設定(幸好java提供數組長度的運行時刻設定)。
上面是我本人研究的結果,當然少不了網友們的知識奉獻。下面就轉自一位網友的blog。
轉:
1.建立記錄,插入BLOB資料
1.1首先建立記錄的時候,使用oracle的函數插入一個空的BLOB,假設欄位A是BLOB類型的:
insert xxxtable(A,B,C) values(empty_blob(),'xxx','yyyy')
1.2後面再查詢剛才插入的記錄,然後更新BLOB,在查詢前,注意設定Connection的一個屬性:
conn.setAutoCommit(false);如果缺少這一步,可能導致fetch out of sequence等異常.
1.3 查詢剛才插入的記錄,後面要加“ for update ”,如下:
select A from xxxtable where xxx=999 for update ,如果缺少for update,可能出現row containing the LOB value is not locked
的異常
1.4 從查詢到的 BLOB欄位中,擷取blob並進行更新,代碼如下:
BLOB blob = (BLOB) rs.getBlob("A");
OutputStream os = blob.getBinaryOutputStream();
BufferedOutputStream output = new BufferedOutputStream(os);
後面再使用output.write方法將需要寫入的內容寫到output中就可以了。例如我們將一個檔案寫入這個欄位中:
BufferedInputStream input = new BufferedInputStream(new File("c://hpWave.log").toURL().openStream());
byte[] buff = new byte[2048]; //用做檔案寫入的緩衝
int bytesRead;
while(-1 != (bytesRead = input.read(buff, 0, buff.length))) {
output.write(buff, 0, bytesRead);
System.out.println(bytesRead);
}
上面的代碼就是從input裡2k地讀取,然後寫入到output中。
1.5上面執行完畢後,記得關閉output,input,以及關閉查詢到的ResultSet
1.6最後執行conn.commit();將更新的內容提交,以及執行conn.setAutoCommit(true); 改回Connction的屬性
2.修改記錄,方法與上面的方法類似,
2.1首先更新BLOB以外的其他欄位
2.2 使用1.3中類似的方法擷取記錄
2.3 修改的過程中,注意以下:a 需要更新的記錄中,BLOB有可能為NULL,這樣在執行blob.getBinaryOutputStream()擷取的值可能為
null,那麼就關閉剛才select的記錄,再執行一次update xxxtable set A = empty_blob() where xxx, 這樣就先寫入了一個空的BLOB(不是null),然後再
使用1.3,1.4中的方法執行更新記錄.b 注意別忘了先執行setAutoCommit(false),以及"for update",以及後面的conn.commit();等。
3.讀取BLOB欄位中的資料.
3.1 讀取記錄不需要setAutoCommit(),以及 select ....for update.
3.2 使用普通的select 方法查詢出記錄
3.3 從ResultSet中擷取BLOB並讀取,如下:
BLOB b_to = (BLOB) rs.getBlob("A");
InputStream is = b_from.getBinaryStream();
BufferedInputStream input = new BufferedInputStream(is);
byte[] buff = new byte[2048];
while(-1 != (bytesRead = input.read(buff, 0, buff.length))) {
//在這裡執行寫入,如寫入到檔案的BufferedOutputStream裡
System.out.println(bytesRead);
}
通過迴圈取出blob中的資料,寫到buff裡,再將buff的內容寫入到需要的地方
4.兩個資料庫間blob欄位的傳輸
類似上面1和3的方法,一邊擷取BufferedOutputStream,另外一邊擷取BufferedInputStream,然後讀出寫入,需要注意的是寫入所用的
Connection要執行conn.setAutoCommit(false);以及擷取記錄時添加“ for update ”以及最後的commit();
總結以上方法,其根本就是先建立空的BLOB,再擷取其BufferedOutputStream進行寫入,或擷取BufferedInputStream進行讀取