OraLobEditor - Edit Oracle LOB (BLOB, CLOB) data »

Download OraLobEditor Free Trial »

Form http://www.dba-oracle.com/t_java_graphics_blob_retrieval.htm

Creating a Simple Application Using Oracle, BLOBs and JAVA


This simple application  will provide a list of graphics stored in the database to the user on request, allow the user to enter an index value which is then used to query the database and pull up the requested graphic for display. 

From the description we can see that this application is actually a combination of two applets, a canned query return and a BLOB retrieval display. This application combines the two into a single applet and adds the capability to specify the graphic returned. Listing 9 shows the complete application.

 

/* 
This applet demonstrates advanced Lob support in the oci8 driver
 */
import java.awt.Graphics.*;
import java.awt.Image.*;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.sql.*;
import java.io.*;
import java.util.*;
// Import the Oracle Jdbc driver package to make the code more readable
import oracle.jdbc.driver.*;
// Define the class as an extension of the Applet calss
public class DisplayImage2 extends java.applet.Applet
implements ActionListener
{
// Set up global variables
MediaTracker theTracker = null;
String blob_id;
Image theImage;
Graphics theGraphic;
// The buttons to push for showing the graphic, cancel
Button show_button;
Button cancel_button;
Button execute_button;
// The text fields for status and blob id input
Canvas blobCanvas = new drawCanvas();
TextField  theStatus  = new TextField(64);
TextField  getblobid  = new TextField(4);
TextArea   output     = new TextArea();
Font outputFont = new  Font("Courier", Font.PLAIN, 12);
// The sequence value for the image file
static String inc_value; 
// The global panels and frame definitions
Frame imageFrame = new Frame("Blob Retrieval Window");
Panel queryPanel = new Panel ();
Panel statusPanel = new Panel ();
Panel buttonPanel = new Panel ();
Panel blobPanel = new Panel();
// The Applets main section init() to set up screen
public void init( )
 {
// Create the User Interface
// Setup Query Panel
queryPanel.setLayout (new BorderLayout ( ));
execute_button = new Button ("Get BLOBs");
execute_button.addActionListener(this);
queryPanel.add ("North", execute_button);
output = new TextArea (22, 40);
output.setFont(outputFont);
queryPanel.add ("Center", output);
// Setup buttonPanel
buttonPanel.setLayout (new GridLayout (1,3));
show_button = new Button ("Show");
show_button.addActionListener(this);
buttonPanel.add("North", show_button);
cancel_button = new Button ("Cancel");
cancel_button.addActionListener(this);
buttonPanel.add("North", cancel_button);
buttonPanel.add("West", new Label("Enter Blob ID:", Label.RIGHT));
buttonPanel.add("Center", getblobid);
// Setup Blob Panel
blobPanel.setLayout (new BorderLayout());
blobPanel.add("East", blobCanvas);
blobPanel.add("North", buttonPanel);
this.validate();
// Setup Status Panel
statusPanel.setLayout (new BorderLayout ( ));
statusPanel.add("South", theStatus);
// Set text editable status
theStatus.setEditable(false);
getblobid.setEditable(true);
//Setup Master Layout
imageFrame.setLayout (new BorderLayout( ));
imageFrame.add("West", queryPanel);
imageFrame.add("South", statusPanel);
imageFrame.add("East", blobPanel);
imageFrame.pack();
imageFrame.setVisible(true);
theTracker = new MediaTracker(this);
 }
// What to do when buttons pushed
public void actionPerformed(ActionEvent event)
 {
// Get event
Object source = event.getSource();
// Determine type of event and take action
if (source == execute_button)
   {
try
      {
// Clear the output area
output.setText (null);
// open the connection to the database
Class.forName ("oracle.jdbc.driver.OracleDriver");
// Connect to the database
Connection conn =
DriverManager.getConnection ("jdbc:oracle:oci8:TEST", "graphics_dba", "graphics1");
// Create a Statement
Statement stmt = conn.createStatement ();
// Create a statement
// Execute the statement and query
output.append("Blobs Available to View" + "\n");
ResultSet rset = stmt.executeQuery ("SELECT rpad(to_char(graphic_id),5)||': '||rpad(graphic_desc,30)||' : '||rpad(graphic_type,10) FROM internal_graphics order by graphic_id");
// Dump the result
while (rset.next ()) output.append(rset.getString (1) + "\n");
      }
catch (Exception e)
      {
handleError(e);
      }
getblobid.requestFocus();
   }
if (source == show_button)
   { 
blob_id = getblobid.getText();
theStatus.setText("Looking For BLOB :" + blob_id);
// Load the Oracle JDBC driver
try
       {
theStatus.setText("Logging On");
Class.forName ("oracle.jdbc.driver.OracleDriver");
// Connect to the database
Connection conn =
DriverManager.getConnection ("jdbc:oracle:oci8:TEST", "graphics_dba", "graphics1");
theStatus.setText("Connection Established");
// Create a Statement
Statement stmt = conn.createStatement ();
// Get increment value for temp file
ResultSet inc = stmt.executeQuery ("select to_char(image_seq.nextval) from dual");
if (inc.next ())
inc_value = inc.getString (1);        
// Select the lobs
theStatus.setText("Fetching BLOB :" + blob_id);
ResultSet rset = stmt.executeQuery ("select graphic_blob from internal_graphics where graphic_id=" + blob_id);
        //
if (rset.next ())
         {
OracleBlob blob = ((OracleResultSet)rset).getBlob (1);
theStatus.setText("Placing Blob in Temp File");
dumpBlob(conn,blob);
theStatus.setText("Showing Image");
getblobid.setText("");
theImage = getImage(getCodeBase(),"../images/theImage" + inc_value + ".gif");
theTracker = new MediaTracker(this);
theTracker.addImage(theImage, 0);
try { 
theTracker.waitForID(0);
              }
catch (InterruptedException e) { }
blobPanel.setVisible(false);
repaint();
blobPanel.setVisible(true);
getblobid.requestFocus();
         }
       }
catch (ClassNotFoundException c)
       {
handleError?;
       } 
catch (SQLException sql)
       {
handleError(sql);
       }
catch (Exception s)
       {
handleError(s);
       }
   }   
if (source == cancel_button) System.exit(0);   
 }
// Override of paint() with canvas to do exactly what we want
class drawCanvas extends Canvas 
 {
int iWidth = 480;
int iHeight = 500;
public void paint(Graphics theGraphic) 
   {
Dimension minSize = getPreferredSize();
if (theTracker != null)
     {
if (theTracker.isErrorAny()) 
       {
theGraphic.setColor(Color.red);
theGraphic.drawString("Image Error",60,10);
return;
       }
if (theTracker.checkAll(true))
       {
iWidth = theImage.getWidth(this);
iHeight = theImage.getHeight(this);
if (iHeight>480)
         {
iWidth=iWidth/2;
iHeight=iHeight/2;
         }
if (iWidth>500)
         {
iWidth=iWidth/2;
iHeight=iHeight/2;
         }
int xpos = 60;
minSize = getPreferredSize();
theGraphic.drawImage(theImage, xpos, 20, iWidth, iHeight, this);
       }
else
       {
theGraphic.setColor(Color.green);
theGraphic.drawString("Image loading ....", 60,10);
repaint(100);
       }
     }
   }
public Dimension getPreferredSize() 
  {
/*System.out.println(iWidth + " x " + iHeight);*/
return new Dimension (iWidth,iHeight);
  }
 }
// Since JAVA doesn't clean up graphics context, do it in an over ride
// of destroy()
public void destroy()
 {
theGraphic.dispose();
System.gc();
 }
// Utility to handle exceptions cleanly
public void handleError(Throwable t)
 {
theStatus.setText("Error: " + t.getMessage( ));
t.printStackTrace( );
 }
// Utility function to dump Blob contents
static void dumpBlob (Connection conn, OracleBlob blob)
throws Exception
 {
OracleCallableStatement cstmt1 =
(OracleCallableStatement)
conn.prepareCall ("begin ? := dbms_lob.getLength (?); end;");
OracleCallableStatement cstmt2 =
(OracleCallableStatement)
conn.prepareCall ("begin dbms_lob.read (?, ?, ?, ?); end;");
cstmt1.registerOutParameter (1, Types.NUMERIC);
cstmt1.setBlob (2, blob);
cstmt1.execute ();
long length = cstmt1.getLong (1);
long i=0;
int chunk=100;
FileOutputStream file = new FileOutputStream ("../images/theImage" + inc_value+ ".gif");
while (i < length)
    {
cstmt2.setBlob (1, blob);
cstmt2.setLong (2, chunk);
cstmt2.registerOutParameter (2, Types.NUMERIC);
cstmt2.setLong (3, i+1);
cstmt2.registerOutParameter (4, Types.VARBINARY);
cstmt2.execute ();
long read_this_time = cstmt2.getLong (2);
byte [] bytes_this_time = cstmt2.getBytes (4);
      {
// Put into file
file.write (bytes_this_time);
i += read_this_time;
/*      System.out.print ("Read " + i + " bytes of " + length + "\n");*/
      if ((i + read_this_time)>length) chunk = (int) (length-i);
      }
    }
file.close();
cstmt1.close ();
cstmt2.close ();
  }
}
Listing 9: Full Code for Graphics Retrieval Applet

 

To give you an idea what the different sections do, look at Figure 5. Figure 5 shows the Blob Retrieval application as it looks when it is first started. Notice we have five main panels, these being the two top button panels, the central query and BLOB display panels and the bottom status display panel. All of these panels are attached to a main frame component.

Figure 6 shows the results of a mouse click of the ¨¬Get BLOBs? button. The click of the button institutes a query of the internal_graphics table showing the index value, description and type of each BLOB currently stored in the database. Notice how the awt panel component automatically adds scroll bars to text regions as needed. Also look up in the ¨¬Enter BLOB ID:? text box, we have entered a BLOB Id from the display on the right but have not clicked on the ?Show? button, yet.

Figure 7 shows the results from clicking on the ¨¬Show? button. Clicking on the ¨¬Show? button initiates the query to get the BLOB locator from the database, use the BLOB locator to retrieve the actual image data from the database using the DBMS_LOB package and place the result in one of a rotating pool of temporary files. Finally, the image is displayed on the graphic canvas, a part of the right panel.

So, how is all of this accomplished? We first initialize the classes we need iin the start of the applet, then in the init() method we set global variables and components. A look at the global variables shows we are initializing a new class called MediaTracker, but more about this in a minute. We are also initializing buttons and panels. Another new item I mentioned in the description of the applets actions is the Canvas, a canvas is used to draw on from within an applet or application. A canvas is usually attached to another object, or container if you prefer, such as panels, frames, windows or even buttons. Lets look at the globals:

 

// Set up global variables
MediaTracker theTracker = null;  ( Note new Class, MediaTracker
String blob_id;
Image theImage;
Graphics theGraphic;
// The buttons to push for showing the graphic, cancel
Button show_button;
Button cancel_button;
Button execute_button;
// The text fields for status and blob id input
Canvas blobCanvas = new drawCanvas();  ( Note new object, a Canvas
TextField  theStatus  = new TextField(64);
TextField  getblobid  = new TextField(4);
TextArea   output     = new TextArea();
Font outputFont = new  Font("Courier", Font.PLAIN, 12);
// The sequence value for the image file
static String inc_value; 
// The global panels and frame definitions
Frame imageFrame = new Frame("Blob Retrieval Window");
Panel queryPanel = new Panel ();
Panel statusPanel = new Panel ();
Panel buttonPanel = new Panel ();
Panel blobPanel = new Panel();
 
These initial definitions are not all of the components we will be using, these are only the ones which are used in multiple methods within DisplayImage2.java. The init() method defines the rest of the user interface components.
 
public void init( )
 {
// Create the User Interface
// Setup Query Panel
queryPanel.setLayout (new BorderLayout ( ));
execute_button = new Button ("Get BLOBs");
execute_button.addActionListener(this);
queryPanel.add ("North", execute_button);
output = new TextArea (22, 40);
output.setFont(outputFont);
queryPanel.add ("Center", output);
// Setup buttonPanel
buttonPanel.setLayout (new GridLayout (1,3));
show_button = new Button ("Show");
show_button.addActionListener(this);
buttonPanel.add("North", show_button);
cancel_button = new Button ("Cancel");
cancel_button.addActionListener(this);
buttonPanel.add("North", cancel_button);
buttonPanel.add("West", new Label("Enter Blob ID:", Label.RIGHT));
buttonPanel.add("Center", getblobid);
// Setup Blob Panel
blobPanel.setLayout (new BorderLayout());
blobPanel.add("East", blobCanvas);
blobPanel.add("North", buttonPanel);
this.validate();
// Setup Status Panel
statusPanel.setLayout (new BorderLayout ( ));
statusPanel.add("South", theStatus);
// Set text editable status
theStatus.setEditable(false);
getblobid.setEditable(true);
//Setup Master Layout
imageFrame.setLayout (new BorderLayout( ));
imageFrame.add("West", queryPanel);
imageFrame.add("South", statusPanel);
imageFrame.add("East", blobPanel);
imageFrame.pack();
imageFrame.setVisible(true);
theTracker = new MediaTracker(this);
 }
 

Notice the use of the media tracker with the theTracker = new MediaTracker(this); line of code. This line attaches the media listener to the imageFrame components blobPanel. The blobPanel contains the blobCanvas drawing component. Another method we have used before but not commented on is the pack() method. The pack() method makes a component just big enough to hold its components. The pack() method can cause problems with graphic elements if the graphic element isn¨ªt initialized, this is especially true of the Canvas class of objects, more on this later.

 

The next section of code determines what actions are taken when the buttons are clicked using the mouse buttons.

 

public void actionPerformed(ActionEvent event)
 {
// Get event
Object source = event.getSource();
// Determine type of event and take action
if (source == execute_button)
   {
try
      {
// Clear the output area
output.setText (null);
// open the connection to the database
Class.forName ("oracle.jdbc.driver.OracleDriver");
// Connect to the database
Connection conn =
DriverManager.getConnection ("jdbc:oracle:oci8:TEST", "graphics_dba", "graphics1");
// Create a Statement
Statement stmt = conn.createStatement ();
// Create a statement
// Execute the statement and query
output.append("Blobs Available to View" + "\n");
ResultSet rset = stmt.executeQuery ("SELECT rpad(to_char(graphic_id),5)||': '||rpad(graphic_desc,30)||' : '||rpad(graphic_type,10) FROM internal_graphics order by graphic_id");
// Dump the result
while (rset.next ()) output.append(rset.getString (1) + "\n");
      }
catch (Exception e)
      {
handleError(e);
      }
getblobid.requestFocus();
   }
if (source == show_button)
   { 
blob_id = getblobid.getText();
theStatus.setText("Looking For BLOB :" + blob_id);
// Load the Oracle JDBC driver
try
       {
theStatus.setText("Logging On");
Class.forName ("oracle.jdbc.driver.OracleDriver");
// Connect to the database
Connection conn =
DriverManager.getConnection ("jdbc:oracle:oci8:TEST", "graphics_dba", "graphics1");
theStatus.setText("Connection Established");
// Create a Statement
Statement stmt = conn.createStatement ();
// Get increment value for temp file
ResultSet inc = stmt.executeQuery ("select to_char(image_seq.nextval) from dual");
if (inc.next ())
inc_value = inc.getString (1);        
// Select the lobs
theStatus.setText("Fetching BLOB :" + blob_id);
ResultSet rset = stmt.executeQuery ("select graphic_blob from internal_graphics where graphic_id=" + blob_id);
        //
if (rset.next ())
         {
OracleBlob blob = ((OracleResultSet)rset).getBlob (1);
theStatus.setText("Placing Blob in Temp File");
dumpBlob(conn,blob);
theStatus.setText("Showing Image");
getblobid.setText("");
 
theImage = getImage(getCodeBase(),"../images/theImage" + inc_value + ".gif");
theTracker = new MediaTracker(this);
theTracker.addImage(theImage, 0);
try { 
theTracker.waitForID(0);
              }
catch (InterruptedException e) { }
blobPanel.setVisible(false);
repaint();
blobPanel.setVisible(true);
getblobid.requestFocus();
         }
       }
catch (ClassNotFoundException c)
       {
handleError?;
       } 
catch (SQLException sql)
       {
handleError(sql);
       }
catch (Exception s)
       {
handleError(s);
       }
   }   
if (source == cancel_button) System.exit(0);   
 }
 

Rather than use a more modular approach (which by the way I tried but it ended up more complex than I liked) I decided to integrate the operations into the actionPerformed(ActionEvent event) module. 

In this case if the execute_button (Get BLOBs) is clicked, the code to retrieve the contents of the internal_graphics table is activated. Notice that at the end of the process of retrieving the BLOB data I place the focus of input into the getblobid text area so the user doesn¨ªt have to manually select the area using a mouse action.

The next action item is the show_button (Show). If the show_button is clicked, the value from the getblobid region is scraped from the screen using a getText() method call. This getblobid value will be used in a query to get the BLOB of choice. First, we reconnect (if needed) to the database and get the next sequence value from a database maintained sequence called image_seq. This sequence is set to forever cycle between 1 and 32 and allows us to randomly associate our graphic with one of the 32 temporary image files. 

I found that if I attempted to use a single temporary image file the graphic context would not remap (would not display). I assume that since JAVA saw I was using the same file name it meant I wanted the same graphic. This behavior even persisted if I used a dispose() method and a System.gc() call (the dispose is supposed to destroy the graphic context and the System call gc() initiates a garbage cleanup). Use of multiple temp files corrected this rather annoying feature of JAVA. Once we have the temporary file we can proceed to SELECT the BLOB id from the internal_graphics table and pass this information to the dumpBlob() method. Also notice that you must create a new instance of tracker at the start of the run or any error returned is carried into the next cycle of operation adfinitum.

Once control returns from the dumpBlob() method the image is retrieved from the temporary file and the MediaTracker object is attached to the process retrieving the image from the file. The MediaTracker object follows the progress of the image load into memory and if there is a problem, reports it back to the process. The MediaTracker is useful in situations where ../images are being transferred from one net location to another to prevent draw and paint routines from attempting to draw incomplete graphics. Once a graphics context is assigned, the control is returned immediately, without the call to the MediaTracker the image might not be complete before operations using it begin.

The next set of commands kept me up several nights figuring out. When I first wrote the application I could get the graphics to display only by collapsing and reinitializing the window using the frame interface with mouse clicks, hardly what I was wanting. 

blobPanel.setVisible(false);
repaint();
blobPanel.setVisible(true);
getblobid.requestFocus();
The act of making the blobPanel invisible, then doing the call to  repaint() and making the panel visible fixed the problem. As a final step in the process I return focus to the getblobid text area, again for user convenience. Once a graphic context is initialized, it will remain in memory cluttering things up until a dispose() is issued against it. Using appletviewer in a MS-DOS window on the NT4.0 I was only able to display about 2-3 ../images before the process locked and required a destructive drop using the task manager. With addition of the dispose method call in the overridden destroy() method call this increased to 12-13 and with a final call to garbage collection (System.gc())  I was finally able to alternatively retrieve and display an unlimited number of ../images.

public void destroy()
 {
theGraphic.dispose();
System.gc();
 }
In this applet the paint() routine is encapsulated in an overridden Canvas class definition. The paint() routine has been expanded to include call to the getPreferredSize() overridden method to get an initialization set of dimensions set by globally declaring the iWidth  and iHeight integer values and using these as a return value set whenever the getPreferredSize() routine is called. By tying the values returned to iWidth  and iHeight whenwe reset these to the actual and adjusted width and height of the graphic any calls to getPreferredSize() returns the proper value. Notice we use a new class, the Dimension class, to set up our iWidth and iHeight values into the minSize() calls. Notice how we use the value of theTracker (the MediaTracker object) to verify if the image is properly loaded.

 

class drawCanvas extends Canvas 
 {
int iWidth = 480;
int iHeight = 500;
public void paint(Graphics theGraphic) 
   {
 
Dimension minSize = getPreferredSize();
if (theTracker != null)
     {
if (theTracker.isErrorAny()) 
       {
theGraphic.setColor(Color.red);
theGraphic.drawString("Image Error",60,10);
return;
       }
if (theTracker.checkAll(true))
       {
iWidth = theImage.getWidth(this);
iHeight = theImage.getHeight(this);
if (iHeight>480)
         {
iWidth=iWidth/2;
iHeight=iHeight/2;
         }
if (iWidth>500)
         {
iWidth=iWidth/2;
iHeight=iHeight/2;
         }
int xpos = 60;
minSize = getPreferredSize();
theGraphic.drawImage(theImage, xpos, 20, iWidth, iHeight, this);
       }
else
       {
theGraphic.setColor(Color.green);
theGraphic.drawString("Image loading ....", 60,10);
repaint(100);
       }
     }
   }
public Dimension getPreferredSize() 
  {
/*System.out.println(iWidth + " x " + iHeight);*/
return new Dimension (iWidth,iHeight);
  }
 
The last section we will discuss is the getBlob() method. The getBlob() method takes the blob identifier and a connection value and uses the Oracle8 stored procedure from the DBMS_LOB package to actually read the BLOB and place the image data into a holding file:
 
// Utility function to dump Blob contents
static void dumpBlob (Connection conn, OracleBlob blob)
throws Exception
 {
OracleCallableStatement cstmt1 =
(OracleCallableStatement)
conn.prepareCall ("begin ? := dbms_lob.getLength (?); end;");
OracleCallableStatement cstmt2 =
(OracleCallableStatement)
conn.prepareCall ("begin dbms_lob.read (?, ?, ?, ?); end;");
cstmt1.registerOutParameter (1, Types.NUMERIC);
cstmt1.setBlob (2, blob);
cstmt1.execute ();
long length = cstmt1.getLong (1);
long i=0;
int chunk=100;
FileOutputStream file = new FileOutputStream ("../images/theImage" + inc_value+ ".gif");
while (i < length)
    {
cstmt2.setBlob (1, blob);
cstmt2.setLong (2, chunk);
cstmt2.registerOutParameter (2, Types.NUMERIC);
cstmt2.setLong (3, i+1);
cstmt2.registerOutParameter (4, Types.VARBINARY);
cstmt2.execute ();
long read_this_time = cstmt2.getLong (2);
byte [] bytes_this_time = cstmt2.getBytes (4);
      {
// Put into file
file.write (bytes_this_time);
i += read_this_time;
/*      System.out.print ("Read " + i + " bytes of " + length + "\n");*/
      if ((i + read_this_time)>length) chunk = (int) (length-i);
      }
    }
file.close();
cstmt1.close ();
cstmt2.close ();
  }
}
 

The dumpBlob() method uses the JDBC Oracle implementation to prepare and execute the calls to the database. The following code snippet shows the Oracle specific calls:

OracleCallableStatement cstmt1 =
      (OracleCallableStatement)
        conn.prepareCall ("begin ? := dbms_lob.getLength (?); end;");
    OracleCallableStatement cstmt2 =
      (OracleCallableStatement)
        conn.prepareCall ("begin dbms_lob.read (?, ?, ?, ?); end;");
 

The question marks in the calls to prepareCall() method mark locations where variables are written. These variables are written using code similar to the following code lines:

    cstmt1.registerOutParameter (1, Types.NUMERIC);
    cstmt1.setBlob (2, blob);
    cstmt1.execute ();
 

The first call to the registerOutParameter for cstmt1 registers the first question mark in the line: ("begin ? := dbms_lob.getLength (?); end;"); as a value that is returned (dbms_lob.getlength() is a function and must return a value) and sets its type as NUMERIC. The call to setBlob() registers the second (as shown by the 2) question mark as an input to the function in cstmt1, setting it to the actual BLOB identifier. The last statement in the snippet executes the statement cstmt1 after making the substitutions. This call to dbms_lob.getlength() returns the length of the BLOB. This length must be known before we can retrieve the BLOB using dbms_lob.read() as over runs of the BLOB bounderies are not permitted and will result in an error if they happen.

The next section in dumpBlob() uses repeated calls to the dbms_lob.read() Oracle8 procedure to read the BLOB values piecewise. Once the pieces are placed in memory using the getBytes() method, calls to the write() method of the JAVA File class place the bytes into the designated file. To create a file rather than use an existing temporary file,  more advanced streaming methods as demonstrated in "Graphic JAVA 1.1 - Mastering the AWT, 2nd Edition", David M. Geary, The SunSoft Press, 1997, should be used. 

 

    long length = cstmt1.getLong (1);
    long i=0;
    int chunk=100;
    FileOutputStream file = new FileOutputStream ("../images/theImage" + inc_value+ ".gif");
    while (i < length)
    {
      cstmt2.setBlob (1, blob);
      cstmt2.setLong (2, chunk);
      cstmt2.registerOutParameter (2, Types.NUMERIC);
      cstmt2.setLong (3, i+1);
      cstmt2.registerOutParameter (4, Types.VARBINARY);
      cstmt2.execute ();
      long read_this_time = cstmt2.getLong (2);
      byte [] bytes_this_time = cstmt2.getBytes (4);
      {
      // Put into file
      file.write (bytes_this_time);
      i += read_this_time;
/*      System.out.print ("Read " + i + " bytes of " + length + "\n");*/
      if ((i + read_this_time)>length) chunk = (int) (length-i);
      }
    }
 

 One technique to take note of in this code snippet is the calculation: 

 

if ((i + read_this_time)>length) chunk = (int) (length-i);

 

This calculation prevents the loop from reading past the end of the BLOB and generating an exception which would cause the transaction to be backed out resulting in only a partial (if any) image transferred into our file.

Once the image file is created and the BLOB has been completely transferred, the file and the Oracle statements are closed:

 

    file.close();
    cstmt1.close ();
    cstmt2.close ();
  }