Java Forum / General / November 2007
please confirm something (jdbc)
stc - 28 Nov 2007 19:03 GMT I've been googling for hours now and what I've found out is that there's just no way to use the same code for handling BLOBs in Oracle and other databases. Am I wrong?
All examples I've come across suggest that one should cast java.sql.ResultSet to oracle.jdbc.driver.OracleResultSet when retrieving oracle.sql.BLOB objects and using streams (or put/get bytes) to read from or write to BLOBs.
There are methods in java.sql.PreparedStatement and java.sql.ResultSet (get/set blob, get/set bytes, get/set binary stream) but they're not working for me.
I'm using Oracle 9i database and ojdbc14.jar that contains Oracle JDBC driver for Java 1.4 and beyond. So technically I could use Oracle JDBC classes and write database-specific code but I would like to write code that would work OK if I ever switch to DB2 or PostgreSQL.
Thomas Kellerer - 28 Nov 2007 19:28 GMT stc wrote on 28.11.2007 20:03:
> I've been googling for hours now and what I've found out is that there's > just no way to use the same code for handling BLOBs in Oracle and other [quoted text clipped - 13 lines] > classes and write database-specific code but I would like to write code that > would work OK if I ever switch to DB2 or PostgreSQL. I'm using the getBinaryStream() and setBinaryStream() methods without problems across several databases (Oracle, Postgres, SQL Server, MySQL, ...)
For Oracle you should use a recent 10.x driver. They work very well with an 9.x server and the support for BLOBs and CLOBs is a lot better in there.
The only drawback with the 10.x driver is that (I think) it is only available for Java 1.5 and above, so if you are stuck with 1.4 you are probably out of luck. But as 1.4 is de-supported soon, you should think about upgrading anyway.
Oracle's download page seems to be down right, now, but do try the 10.x (or even the 11.x) driver:
http://otn.oracle.com/software/tech/java/sqlj_jdbc/content.html
Thomas
stc - 28 Nov 2007 21:30 GMT > I'm using the getBinaryStream() and setBinaryStream() methods without > problems across several databases (Oracle, Postgres, SQL Server, MySQL, [quoted text clipped - 15 lines] > > Thomas This is great news! I'm using Java 1.5 so it won't be a problem. I'll download the driver tomorrow and try with getBinaryStream() and setBinaryStream() methods.
Thanks a lot...
John Maline - 28 Nov 2007 21:35 GMT > The only drawback with the 10.x driver is that (I think) it is only > available for Java 1.5 and above, so if you are stuck with 1.4 you are > probably out of luck. But as 1.4 is de-supported soon, you should think > about upgrading anyway. I don't believe that's true. I recently was testing out Oracle's 10.2.0.3 JDBC driver with Java 1.3.1. There's a classes12.zip version (for Java 1.2 and 1.3.x, I think).
It looks like Oracle is being very good about supporting old Java versions. Probably by necessity since some of us enterprise types get stuck w/ infrastructure tied to specific (old) java versions.
Mark Jeffcoat - 28 Nov 2007 20:38 GMT > I've been googling for hours now and what I've found out is that there's > just no way to use the same code for handling BLOBs in Oracle and other [quoted text clipped - 4 lines] > oracle.sql.BLOB objects and using streams (or put/get bytes) to read from or > write to BLOBs. I haven't had to do anything like that -- but I'm not supporting anything older than 10g.
I have run into one Oracle-specific limitation, in that a BLOB, if present, must be at the end of your PreparedStatement parameter list. See
http://forums.oracle.com/forums/thread.jspa?threadID=415560
for more. That still didn't require any Oracle-specific code in the end, since none of the other supported systems cared about the parameter order.
 Signature Mark Jeffcoat Austin, TX
stc - 28 Nov 2007 21:32 GMT > I haven't had to do anything like that -- but I'm not supporting > anything older than 10g. [quoted text clipped - 8 lines] > the end, since none of the other supported systems cared about > the parameter order. I'll take that into consideration. Thanks again...
steve - 29 Nov 2007 23:22 GMT >> I haven't had to do anything like that -- but I'm not supporting >> anything older than 10g. [quoted text clipped - 10 lines] > > I'll take that into consideration. Thanks again... no it does not!!
Heres one at the start, I also have 2 & 3 blobs at the same time , in any position.
String sql = "Select object_code,client_file_name,filedatestamp from client_code_java where deleted=0 and rep_index=?"; PreparedStatement st = dbconn.prepareStatement(sql);
st.setString(1, indexkey);
// Bind the replication index rset = st.executeQuery();
// Execute Query oracle.sql.BLOB blob = null;
while (rset.next()) { blob = (oracle.sql.BLOB) rset.getObject(1); filename = rset.getString(2); newModifiedTime = rset.getLong(3);
Thomas Kellerer - 28 Nov 2007 21:49 GMT Mark Jeffcoat wrote on 28.11.2007 21:38:
>> I've been googling for hours now and what I've found out is that there's >> just no way to use the same code for handling BLOBs in Oracle and other [quoted text clipped - 17 lines] > the end, since none of the other supported systems cared about > the parameter order. That thread speaks about LONG columns which is something different. Due to the nature of how they are stored, this limitation is indeed present for LONG columns but not for BLOB columns. I have happily updated and retrieved BLOB columns with getBinaryStream() at any position in my prepared statement (even more than one BLOB at a time).
Note that LONG is deprecated since 8.1 (or something near that version number).
Thomas
Mark Jeffcoat - 28 Nov 2007 23:58 GMT > That thread speaks about LONG columns which is something different. > Due to the nature of how they are stored, this limitation is indeed > present for LONG columns but not for BLOB columns. > I have happily updated and retrieved BLOB columns with > getBinaryStream() at any position in my prepared statement (even more > than one BLOB at a time). You're right, I'd mis-remembered. That thread doesn't mention BLOBs. On the other hand, the reason I found it in the first place is by searching on the error message I got on a column declared BLOB ("ORA-24816: Expanded non LONG bind data supplied after actual LONG or LOB column"), and re-ordering did solve my issue.
So there's some connection here. BLOB is a special case of LOB, maybe?
I am using ResultSet.getBytes() instead of getBinaryStream(); I wonder if that's what made the difference.
 Signature Mark Jeffcoat Austin, TX
Thomas Kellerer - 29 Nov 2007 07:43 GMT Mark Jeffcoat, 29.11.2007 00:58:
> You're right, I'd mis-remembered. That thread doesn't mention > BLOBs. On the other hand, the reason I found it in the > first place is by searching on the error message I got > on a column declared BLOB ("ORA-24816: Expanded non LONG bind > data supplied after actual LONG or LOB column"), and re-ordering > did solve my issue. Is there a LONG column in the ResultSet maybe?
> So there's some connection here. BLOB is a special case of > LOB, maybe? I wouldn't call it "special" A BLOB *is* a LOB (by definition)
> I am using ResultSet.getBytes() instead of getBinaryStream(); > I wonder if that's what made the difference. Could well be. When I tried to find a way to support LOB data across different DMBS, getBinaryStream() (and set..) was the only method that worked reliably (using an Oracle 10.x driver)
Thomas
stc - 29 Nov 2007 09:23 GMT > Could well be. When I tried to find a way to support LOB data across > different DMBS, getBinaryStream() (and set..) was the only method that > worked reliably (using an Oracle 10.x driver) I've downloaded the driver but still can't make it work. Here's the code:
// write conn.setAutoCommit(false); // because some data is stored in other tables as well PreparedStatement pstmt = conn.prepareStatement("insert into test (id, content) values (?, ?)"; pstmt.setString(1, "12345"); pstmt.setBinaryStream(2, bais, bais.available()); // bais contains bytes read from the local PDF file pstmt.executeUpdate(); pstmt.close(); conn.commit(); bais.close();
This works fine, that is, there are no exceptions, but is there a way to check what is stored in the table using Oracle Enterprise Manager Console. I got the warning that BLOB columns cannot be displayed. If I could only see the length of bytes stored in the column, I could compare it to the size of the PDF files locally on the disk.
And here's the code to read the BLOB:
// read conn.setAutoCommit(true); pstmt = conn.prepareStatement("select content from test where id = ? for update"; pstmt.setString(1, "12345"); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { InputStream is = rs.getBinaryStream("content"); int n; byte[] tmp = new byte[is.available()]; // available() returns 0! FileOutputStream fos = new FileOutputStream("C:/Temp/12345.pdf"); while ((n = is.read(tmp)) != -1) { fos.write(tmp, 0, n); } fos.close(); is.close(); } rs.close(); pstmt.close(); conn.close();
The problem is that "available()" method returns 0 and "read()" method blocks. What am I doing wrong?
stc - 29 Nov 2007 10:05 GMT >> Could well be. When I tried to find a way to support LOB data across >> different DMBS, getBinaryStream() (and set..) was the only method that [quoted text clipped - 20 lines] > see the length of bytes stored in the column, I could compare it to the > size of the PDF files locally on the disk. Query "select length(content) from test" returns correct number of bytes so I guess storing BLOBs is working fine. The problem is with the reading of BLOBs...
Martin Gregorie - 29 Nov 2007 16:35 GMT > Query "select length(content) from test" returns correct number of bytes so > I guess storing BLOBs is working fine. The problem is with the reading of > BLOBs... I think this might be a general problem with some InputStream instances when they are asked to process byte streams. I'm being tentative, because I found something that looks like the same problem last night, diagnosed it just now, but haven't tried fixing it yet.
I'm using J2SE 1.4 at present because I want to get this application running and stable before moving to Java 6.
In my case I'm reading from the InputStream returned by the JavaMail Message.getContent() method. It returns a PipedInputStream instance which should transfer the contents of a byte array. I plug this directly into PreparedStatement.setBinaryStream() to write the message content into a bytea (binary equivalent of text or varchar) in a Postgres database, having got the field size from InputStream.available().
As part of my diagnostic code, if debugging is on I execute the following before loading data into the database: - I call Message.getContent() and read the stream a byte at a time - convert each byte to a char and append it to a StringBuffer. - the StringBuffer is displayed by writing it to System.err.
The diagnostic display is ALWAYS a complete version of the message content, but the database field content varies depending on whether the message content is MIME or non-MIME: - non-MIME: (plaintext UTF-8 or bit-7) the database field is always correct.
- MIME: the database field is sometimes empty and sometimes incomplete.
It looks to me as if, for a PipedInputStream, the value returned by available() can be invalidated by the content of the byte array but that read() will always correctly recognise the end of the byte array.
I'm about to modify my code to check this assumption and will let you know the result.
 Signature martin@ | Martin Gregorie gregorie. | Essex, UK org |
Martin Gregorie - 29 Nov 2007 22:04 GMT > It looks to me as if, for a PipedInputStream, the value returned by > available() can be invalidated by the content of the byte array but that > read() will always correctly recognise the end of the byte array. > > I'm about to modify my code to check this assumption and will let you > know the result. Here's the follow-up: I was right in my guess. My solution is to replace:
InputStream is = msg.getContent(); prepsql.setBinaryStream("content", is, is.available());
with InputStream is = msg.getContent(); int isc = is.available(); ByteArrayOutputStream bos = new ByteArrayOutputStream(isc); int b; while ((b = is.read()) != -1) bos.write(b);
byte[] ba = bos.getByteArray(); bos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(ba); prepsql.setBinaryStream("content", bis, ba.length);
Its a bit ugly but it works reliably and, by declaring the ByteOutputStream and byte[] locally in a method the overall storage can be minimised. The value of isc might be anywhere in the range 0 to ba.length, so supplying it as an argument to the ByteArrayOutputStream constructor may help performance and certainly can't do any harm.
FWIW the value returned by InputStream.available() does seem to be implementation independent. It seems to always be the size of the array for ByteArrayInputStream.
 Signature martin@ | Martin Gregorie gregorie. | Essex, UK org |
stc - 29 Nov 2007 14:35 GMT > // read > conn.setAutoCommit(true); [quoted text clipped - 19 lines] > The problem is that "available()" method returns 0 and "read()" method > blocks. What am I doing wrong? I managed to read the BLOB successfully using "getBytes()" method from ResultSet. Then I tried to store BLOB using "setBytes()" and it worked. So the summary is as following:
- Oracle 9i, 10g JDBC driver, Java 1.5 - writing BLOBs - both "setBinaryStream()" and "setBytes()" work - reading BLOBs - works only with "getBytes()"
I would really like to have "getBinaryStream()" working so if you could help I'd appreciate it...
Thomas Kellerer - 29 Nov 2007 14:43 GMT stc, 29.11.2007 15:35:
> I managed to read the BLOB successfully using "getBytes()" method from > ResultSet. Then I tried to store BLOB using "setBytes()" and it worked. So [quoted text clipped - 6 lines] > I would really like to have "getBinaryStream()" working so if you could help > I'd appreciate it... I'm not sure that getBytes() works for other JDBC drivers as well. I think get/setBinaryStream() was the only reliable (cross-dbms) way that I found.
What I do, to read the BLOB (I removed the error handling obviously)
InputStream in = rs.getBinaryStream(1); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024];
int bytesRead = in.read(buff);
while (bytesRead > -1) { baos.write(buff, 0, bytesRead); bytesRead = in.read(buff); }
in.close(); baos.close();
Now you can e.g. use baos.toByteArray() to access the raw data as a byte array.
Note that using ByteArrayOutputStream.toByteArray() will create a copy of its internal buffer so that will double the amount of memory that you need. That's the reason I wrote my own dynamic byte array (where I can access the byte array without copying it around)
Thomas
Lew - 29 Nov 2007 15:01 GMT > Note that using ByteArrayOutputStream.toByteArray() will create a copy > of its internal buffer so that will double the amount of memory that you > need. That's the reason I wrote my own dynamic byte array (where I can > access the byte array without copying it around) Using ByteArrayOutputStream.toByteArray() will also guarantee that "the valid contents of the buffer have been copied into [the resultant array]", and that the resultant array is the right size. I find these guarantees to be worth the copy overhead for reasonable-sized objects (under a couple MB, as is typical for, say, graphic images, one use case for BLOBs).
Given all the memory re-allocation that goes on inside the ByteArrayOutputStream anyway, I'm not sure that one more allocation is all that much extra overhead. If you do it right, the internal array of the ByteArrayOutputStream will immediately become eligible for GC right away anyway, so the "efficiency" you gain from avoiding that one copy might not be worth the extra risk and effort of maintaining a custom class.
It depends on how L your BLOB is, naturally. I can see how a large enough object would require striped processing and other tricks, but like most optimization this strikes me as one of those "Don't do it 'til you have to, and don't think you have to until you've measured" scenarios.
 Signature Lew
Thomas Kellerer - 29 Nov 2007 19:26 GMT Lew wrote on 29.11.2007 16:01:
>> Note that using ByteArrayOutputStream.toByteArray() will create a copy >> of its internal buffer so that will double the amount of memory that [quoted text clipped - 19 lines] > like most optimization this strikes me as one of those "Don't do it 'til > you have to, and don't think you have to until you've measured" scenarios. Valid points, indeed, especially the one about the array beeing GC'ed pretty soon. But still, it will make a difference if I hold 20 or 40MB in memory.
Well I am processing BLOB where I don't know the size (and the number) in advance, and they could potentially be quite big. My own ByteBuffer does basically the same as ByteArrayOutputStream with the exception that I expose access to the array (which is also guaranteed to have the right size).
I only partially agree with your optimization strategy though. If I have two ways of coding something, and I know that one will definitely use less memory (or be quicker for whatever reason) then I will choose the one "better" one. I think one reason for slow software is the attitude "I'll fix the performance issue later", because "later" you'll have so many places to fix that it will probably be hard to nail down the real cause.
Cheers Thomas
Thomas Kellerer - 29 Nov 2007 17:13 GMT stc, 29.11.2007 10:23:
> // read > conn.setAutoCommit(true); [quoted text clipped - 19 lines] > The problem is that "available()" method returns 0 and "read()" method > blocks. What am I doing wrong? available() cannot be used to retrieve the number of bytes for an InputStream.
From the Javadocs:
"Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this input stream"
It does not claim to return the length of the underlying stream. Actually I doubt that it even knows the size of the "source".
You should create your tmp buffer with a fixed size (e.g. new byte[1024]), then it should work. I'm not sure I understand what you mean with "read() blocks" but that could well be caused by your buffer of length zero.
Thomas
stc - 29 Nov 2007 19:28 GMT > available() cannot be used to retrieve the number of bytes for an > InputStream. > > It does not claim to return the length of the underlying stream. Actually > I doubt that it even knows the size of the "source".
> You should create your tmp buffer with a fixed size (e.g. new byte[1024]), > then it should work. I'm not sure I understand what you mean with "read() > blocks" but that could well be caused by your buffer of length zero. That's probably it. I used available() because it gave me what I needed when called on ByteArrayInputStream so I didn't bother to check the javadocs. I'll try with a fixed-size buffer tomorrow and see what happens but there's no reason not to work :-)
Regarding the getBytes(), I can confirm that it works with PostgreSQL 8.1.4 (with 8.1.407 JDBC3 driver).
Thanks for the help...
steve - 29 Nov 2007 23:33 GMT >> available() cannot be used to retrieve the number of bytes for an >> InputStream. [quoted text clipped - 15 lines] > > Thanks for the help... 1.don't try to read the whole blob into memory, process it as a buffered stream.
I.E
while (rset.next()) { blob = (oracle.sql.BLOB) rset.getObject(1); thebuffer = new BufferedInputStream(blob.getBinaryStream()); }
or write it to a temp file.
2. store the size of the blob , in an extra field in the record, it's way faster to recover
newmessageClob.setString(1, message); st.setString(1, ""); st.setString(2, ""); st.setString(3, theuser); st.setString(4, sendto); st.setString(5, subject); st.setString(6, "Packingfile.zip"); st.setLong(7, newmessageClob.length()); st.setClob(8, newmessageClob); st.setBlob(9, newfileBlob); st.setLong(10, newfileBlob.length()); st.setInt(11, 1); st.executeUpdate();
Ramon F Herrera - 29 Nov 2007 16:39 GMT > I've been googling for hours now and what I've found out is that there's > just no way to use the same code for handling BLOBs in Oracle and other [quoted text clipped - 13 lines] > classes and write database-specific code but I would like to write code that > would work OK if I ever switch to DB2 or PostgreSQL. I tried JDBC and gave up. It's too darn slow for any non-trivial task. All those layers and generality affect the performance. The same can be said about ODBC.
-Ramon (a former JDBC user)
Thomas Kellerer - 29 Nov 2007 16:53 GMT Ramon F Herrera, 29.11.2007 17:39:
> I tried JDBC and gave up. It's too darn slow for any non-trivial task. I cannot confirm that. Importing data using JDBC (and batched statements) is nearly as fast as using SQL*Loader to load the same data.
Thomas
Steve Wampler - 29 Nov 2007 16:59 GMT > I tried JDBC and gave up. It's too darn slow for any non-trivial task. > All those layers and generality affect the performance. The same can > be said about ODBC. I'm curious - what are you using instead? (Or did you move off of Java at the same time?)
 Signature Steve Wampler -- swampler@noao.edu The gods that smiled on your birth are now laughing out loud.
Ramon F Herrera - 29 Nov 2007 19:16 GMT > > I tried JDBC and gave up. It's too darn slow for any non-trivial task. > > All those layers and generality affect the performance. The same can [quoted text clipped - 4 lines] > > -- No, I didn't move away from Java. Never will. The thing is that along the years I have been learning the (not too many!) aspects in which a 100% Java solution is not appropriate.
My solution -which works quite well, the speed improved by orders of magnitude- is to write the server side in Oracle OCI (it is available for C and C++) and the Java client makes a remote Unix request through the iNetFactory library from these folks:
http://www.jscape.com
-Ramon
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|