Using Clobs with Oracle and Hibernate

Using Clobs with Oracle and Hibernate

Hibernate 1.2.1 comes with support for Clobs (and Blobs). Just use the clob type in your mapping file and java.sql.Clob in your persistent class.

However, due to problems with the Oracle JDBC driver, this support falls short when you try to store more than 4000 characters in a Clob. In order to properly store Clobs in Oracle 8 with Hibernate 1.2.x, you must do the following:

s = sf.openSession();
tx = s.beginTransaction();
foo = new Foo();
foo.setClob( Hibernate.createClob(" ") );
s.save(foo);
tx.commit();
s.close();

s = sf.openSession();
tx = s.beginTransaction();
foo = (Foo) s.load( Foo.class, foo.getId(), LockMode.UPGRADE );
oracle.sql.CLOB clob = (oracle.sql.CLOB) foo.getClob();
java.io.Writer pw = clob.getCharacterOutputStream();
pw.write(content);
pw.close();
tx.commit();
s.close();

You should be careful not to pass a zero-length string to Hibernate.createClob(), otherwise Oracle will set the column value to NULL and the subsequent getClob() call will return null.

In Hibernate2, the following (much more elegant) solution exists:

s = sf.openSession();
tx = s.beginTransaction();
foo = new Foo();
foo.setClob( Hibernate.createClob(" ") );
s.save(foo);
s.flush();
s.refresh(foo, LockMode.UPGRADE); //grabs an Oracle CLOB
oracle.sql.CLOB clob = (oracle.sql.CLOB) foo.getClob();
java.io.Writer pw = clob.getCharacterOutputStream();
pw.write(content);
pw.close();
tx.commit();
s.close();

If you need a solution that is more transparent and you can rely on having the Oracle 9.x JDBC drivers then you can try using the newly introduced oracle.sql.CLOB.createTemporary method. Here is an example user type that uses this idea while converting Clobs to strings. Note that it uses reflection to avoid a compile-time dependency on the Oracle driver, however the methods can be used directly if you wish. Also it should be straightforward to convert this UserType to one that just maps to a clob in the data object.

package foobar;

import java.io.Reader;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;

import net.sf.hibernate.Hibernate;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;

import org.apache.commons.lang.ObjectUtils;

/**
 * Based on community area design patterns on Hibernate site.
 * Maps java.sql.Clob to a String special casing for Oracle drivers.
 * @author Ali Ibrahim, Scott Miller
 */
public class StringClobType implements UserType
{

  /** Name of the oracle driver -- used to support Oracle clobs as a special case */
  private static final String ORACLE_DRIVER_NAME = "Oracle JDBC driver";
  
  /** Version of the oracle driver being supported with clob. */
  private static final int ORACLE_DRIVER_MAJOR_VERSION = 9;
  private static final int ORACLE_DRIVER_MINOR_VERSION = 0;
 
  public int[] sqlTypes()
  {
    return new int[] { Types.CLOB };
  }

  public Class returnedClass()
  {
    return String.class;
  }

  public boolean equals(Object x, Object y)
  {
    return ObjectUtils.equals(x, y);
  }

  public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
    throws HibernateException, SQLException
  {
    Reader clobReader = rs.getCharacterStream(names[0]);
    if (clobReader == null)
    {
      return null;
    }

    String str = new String();
    BufferedReader bufferedClobReader = new BufferedReader(clobReader);
    try
    {
      String line = null;
      while( (line = bufferedClobReader.readLine()) != null )
      {
    str += line;
      }
      bufferedClobReader.close();
    }
    catch (IOException e)
    {
      throw new SQLException( e.toString() );
    }

    return str;
  }

  public void nullSafeSet(PreparedStatement st, Object value, int index)
    throws HibernateException, SQLException
  {
    DatabaseMetaData dbMetaData = st.getConnection().getMetaData();
    if (value==null)
    {
      st.setNull(index, sqlTypes()[0]);
    }
    else if (ORACLE_DRIVER_NAME.equals( dbMetaData.getDriverName() ))
    {
      if ((dbMetaData.getDriverMajorVersion() >= ORACLE_DRIVER_MAJOR_VERSION) &&
      (dbMetaData.getDriverMinorVersion() >= ORACLE_DRIVER_MINOR_VERSION))
      {
    try
    {
      // Code compliments of Scott Miller
      // support oracle clobs without requiring oracle libraries
      // at compile time
      // Note this assumes that if you are using the Oracle Driver.
      // then you have access to the oracle.sql.CLOB class
                        
      // First get the oracle clob class
      Class oracleClobClass = Class.forName("oracle.sql.CLOB");

      // Get the oracle connection class for checking
      Class oracleConnectionClass = Class.forName("oracle.jdbc.OracleConnection");
                        
      // now get the static factory method
      Class partypes[] = new Class[3];
      partypes[0] = Connection.class;
      partypes[1] = Boolean.TYPE;
      partypes[2] = Integer.TYPE;                
      Method createTemporaryMethod = oracleClobClass.getDeclaredMethod( "createTemporary", partypes );                        
      // now get ready to call the factory method
      Field durationSessionField = oracleClobClass.getField( "DURATION_SESSION" );
      Object arglist[] = new Object[3];
      Connection conn = st.getConnection();

      // Make sure connection object is right type
      if (!oracleConnectionClass.isAssignableFrom(conn.getClass()))
      {
        throw new HibernateException("JDBC connection object must be a oracle.jdbc.OracleConnection. " +
                                     "Connection class is " + conn.getClass().getName());
      }

      arglist[0] = conn;
      arglist[1] = Boolean.TRUE;
      arglist[2] = durationSessionField.get(null); //null is valid because of static field
                        
      // Create our CLOB
      Object tempClob = createTemporaryMethod.invoke( null, arglist ); //null is valid because of static method
                        
      // get the open method
      partypes = new Class[1];
      partypes[0] = Integer.TYPE;
      Method openMethod = oracleClobClass.getDeclaredMethod( "open", partypes );
                                                
      // prepare to call the method
      Field modeReadWriteField = oracleClobClass.getField( "MODE_READWRITE" );
      arglist = new Object[1];
      arglist[0] = modeReadWriteField.get(null); //null is valid because of static field
                        
      // call open(CLOB.MODE_READWRITE);
      openMethod.invoke( tempClob, arglist );
                        
      // get the getCharacterOutputStream method
      Method getCharacterOutputStreamMethod = oracleClobClass.getDeclaredMethod( "getCharacterOutputStream", null );
                        
      // call the getCharacterOutpitStream method
      Writer tempClobWriter = (Writer) getCharacterOutputStreamMethod.invoke( tempClob, null );
                        
      // write the string to the clob
      tempClobWriter.write((String)value); 
      tempClobWriter.flush();
      tempClobWriter.close(); 
                        
      // get the close method
      Method closeMethod = oracleClobClass.getDeclaredMethod( "close", null );
                        
      // call the close method
      closeMethod.invoke( tempClob, null );
                        
      // add the clob to the statement
      st.setClob( index, (Clob)tempClob );
    }
    catch( ClassNotFoundException e )
    {
      // could not find the class with reflection
      throw new HibernateException("Unable to find a required class./n" + e.getMessage());
    }
    catch( NoSuchMethodException e )
    {
      // could not find the metho with reflection
      throw new HibernateException("Unable to find a required method./n" + e.getMessage());
    }
    catch( NoSuchFieldException e )
    {
      // could not find the field with reflection
      throw new HibernateException("Unable to find a required field./n" + e.getMessage());
    }
    catch( IllegalAccessException e )
    {
      throw new HibernateException("Unable to access a required method or field./n" + e.getMessage());
    }
    catch( InvocationTargetException e )
    {
      throw new HibernateException(e.getMessage());
    }
    catch( IOException e )
    {
      throw new HibernateException(e.getMessage()); 
    }
      }
      else
      {
    throw new HibernateException("No CLOBS support. Use driver version " + ORACLE_DRIVER_MAJOR_VERSION +
                                 ", minor " + ORACLE_DRIVER_MINOR_VERSION);
      }
    }    
    else
    {
      String str = (String)value;
      StringReader r = new StringReader(str);
      st.setCharacterStream(index, r, str.length());
    }      
  }

  public Object deepCopy(Object value)
  {
    if (value == null) return null;
    return new String((String) value);
  }
  
  public boolean isMutable()
  {
    return false;
  }
}

Notes:

1. This approach is very fragile when not used directly with oracle jdbc connections. Somwhere in the createTemporary method the connection is cast to an oracle.jdbc.OracleConnection. Of course this means that the connection you give it must be assignable to that class. The code here checks for that and tries to throw a meaningful exception. The practical implication is that connection pooling mechanisms such as in web application servers or jdbc wrappers such as p6spy can break the code. The workaround is to somehow extract the underlying connection to give to the createTemporary method (this is usually straightforward as I have done this for p6spy and oc4j in my custom code).

2. Related to the first point, even though OC4J/Orion data source pooling class for Oracle actually is assignable to oracle.jdbc.OracleConnection, there were NullPointerExceptions being thrown. When I extracted the underlying connection through the getPhysicalConnection method, it worked, so I assume there is some wierdness with the behavior of the wrapper class (OrclCMTConnection).

Enjoy!


Updated Clobs handling for Oracle and Hibernate

- uses interceptor and avoids compile-time dependency.

[Lukasz's compilation (added on 14th Oct 2004)]

Just copy/paste it.

User type:

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.StringReader;
import java.io.Writer;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;


/**
 * Implementation of Oracle's CLOB handling
 */
public class StringClobType implements UserType {
    protected static Log log = LogFactory.getLog(StringClobType.class);

    /** Name of the oracle driver -- used to support Oracle clobs as a special case */
    private static final String ORACLE_DRIVER_NAME = "Oracle JDBC driver";

    /** Version of the oracle driver being supported with clob. */
    private static final int ORACLE_DRIVER_MAJOR_VERSION = 9;
    private static final int ORACLE_DRIVER_MINOR_VERSION = 0;

    /**
     * @see net.sf.hibernate.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
     */
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
        throws SQLException {
        //Get the clob field we are interested in from the result set 
        Clob clob = rs.getClob(names[0]);

        return ((clob == null) ? null : clob.getSubString(1, (int) clob.length()));
    }

    /**
     * oracleClasses independent (at compile time); based on http://forum.hibernate.org/viewtopic.php?p=2173150,
     * changes: changed line:  Connection conn = ps.getConnection(); to: Connection conn = dbMetaData.getConnection();
     *
     * @see net.sf.hibernate.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
     */
    public void nullSafeSet(PreparedStatement ps, Object value, int index)
        throws SQLException, HibernateException {
        DatabaseMetaData dbMetaData = ps.getConnection().getMetaData();
        log.debug(dbMetaData.getDriverName());
        log.debug(dbMetaData.getDriverMajorVersion() + " " + dbMetaData.getDriverMinorVersion());
        log.debug(dbMetaData.getConnection().getClass().getName());

        if (value == null) {
            ps.setNull(index, sqlTypes()[0]);
        } else if (ORACLE_DRIVER_NAME.equals(dbMetaData.getDriverName())) {
            if ((dbMetaData.getDriverMajorVersion() >= ORACLE_DRIVER_MAJOR_VERSION) &&
                    (dbMetaData.getDriverMinorVersion() >= ORACLE_DRIVER_MINOR_VERSION)) {
                try {
                    // Code compliments of Scott Miller 
                    // support oracle clobs without requiring oracle libraries 
                    // at compile time 
                    // Note this assumes that if you are using the Oracle Driver. 
                    // then you have access to the oracle.sql.CLOB class 
                    // First get the oracle clob class 
                    Class oracleClobClass = Class.forName("oracle.sql.CLOB");

                    // Get the oracle connection class for checking 
                    Class oracleConnectionClass = Class.forName("oracle.jdbc.OracleConnection");

                    // now get the static factory method 
                    Class[] partypes = new Class[3];
                    partypes[0] = Connection.class;
                    partypes[1] = Boolean.TYPE;
                    partypes[2] = Integer.TYPE;

                    Method createTemporaryMethod = oracleClobClass.getDeclaredMethod("createTemporary", partypes);

                    // now get ready to call the factory method 
                    Field durationSessionField = oracleClobClass.getField("DURATION_SESSION");
                    Object[] arglist = new Object[3];

                    //changed from: Connection conn = ps.getConnection();
                    Connection conn = dbMetaData.getConnection();

                    // Make sure connection object is right type 
                    if (!oracleConnectionClass.isAssignableFrom(conn.getClass())) {
                        throw new HibernateException("JDBC connection object must be a oracle.jdbc.OracleConnection. " +
                            "Connection class is " + conn.getClass().getName());
                    }

                    arglist[0] = conn;
                    arglist[1] = Boolean.TRUE;
                    arglist[2] = durationSessionField.get(null); //null is valid because of static field 

                    // Create our CLOB 
                    Object tempClob = createTemporaryMethod.invoke(null, arglist); //null is valid because of static method 

                    // get the open method 
                    partypes = new Class[1];
                    partypes[0] = Integer.TYPE;

                    Method openMethod = oracleClobClass.getDeclaredMethod("open", partypes);

                    // prepare to call the method 
                    Field modeReadWriteField = oracleClobClass.getField("MODE_READWRITE");
                    arglist = new Object[1];
                    arglist[0] = modeReadWriteField.get(null); //null is valid because of static field 

                    // call open(CLOB.MODE_READWRITE); 
                    openMethod.invoke(tempClob, arglist);

                    // get the getCharacterOutputStream method 
                    Method getCharacterOutputStreamMethod = oracleClobClass.getDeclaredMethod("getCharacterOutputStream",
                            null);

                    // call the getCharacterOutpitStream method 
                    Writer tempClobWriter = (Writer) getCharacterOutputStreamMethod.invoke(tempClob, null);

                    // write the string to the clob 
                    tempClobWriter.write((String) value);
                    tempClobWriter.flush();
                    tempClobWriter.close();

                    // get the close method 
                    Method closeMethod = oracleClobClass.getDeclaredMethod("close", null);

                    // call the close method 
                    closeMethod.invoke(tempClob, null);

                    // add the clob to the statement 
                    ps.setClob(index, (Clob) tempClob);

                    LobCleanUpInterceptor.registerTempLobs(tempClob);
                } catch (ClassNotFoundException e) {
                    // could not find the class with reflection 
                    throw new HibernateException("Unable to find a required class./n" + e.getMessage());
                } catch (NoSuchMethodException e) {
                    // could not find the metho with reflection 
                    throw new HibernateException("Unable to find a required method./n" + e.getMessage());
                } catch (NoSuchFieldException e) {
                    // could not find the field with reflection 
                    throw new HibernateException("Unable to find a required field./n" + e.getMessage());
                } catch (IllegalAccessException e) {
                    throw new HibernateException("Unable to access a required method or field./n" + e.getMessage());
                } catch (InvocationTargetException e) {
                    throw new HibernateException(e.getMessage());
                } catch (IOException e) {
                    throw new HibernateException(e.getMessage());
                }
            } else {
                throw new HibernateException("No CLOBS support. Use driver version " + ORACLE_DRIVER_MAJOR_VERSION +
                    ", minor " + ORACLE_DRIVER_MINOR_VERSION);
            }
        } else {
            String str = (String) value;
            StringReader r = new StringReader(str);
            ps.setCharacterStream(index, r, str.length());
        }
    }

    public Object deepCopy(Object value) {
        if (value == null) {
            return null;
        }

        return new String((String) value);
    }
    public boolean isMutable() {
        return false;
    }
    public int[] sqlTypes() {
        return new int[] { Types.CLOB };
    }
    public Class returnedClass() {
        return String.class;
    }
    public boolean equals(Object x, Object y) {
        return ObjectUtils.equals(x, y);
    }
}

Interceptor:

import net.sf.hibernate.Interceptor;
import net.sf.hibernate.type.Type;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;


public class LobCleanUpInterceptor implements Interceptor {
    protected static Log log = LogFactory.getLog(LOBEntityInterceptor.class);

    // a thread local set to store temperary LOBs
    private static final ThreadLocal threadTempLobs = new ThreadLocal();

    public boolean onLoad(Object arg0, Serializable arg1, Object[] arg2, String[] arg3, Type[] arg4) {
        return false;
    }
    public boolean onFlushDirty(Object arg0, Serializable arg1, Object[] arg2, Object[] arg3, String[] arg4, Type[] arg5) {
        return false;
    }
    public boolean onSave(Object arg0, Serializable arg1, Object[] arg2, String[] arg3, Type[] arg4) {
        return false;
    }
    public void onDelete(Object arg0, Serializable arg1, Object[] arg2, String[] arg3, Type[] arg4) {}
    public void preFlush(Iterator arg0) {}
    public Boolean isUnsaved(Object arg0) {
        return null;
    }
    public int[] findDirty(Object arg0, Serializable arg1, Object[] arg2, Object[] arg3, String[] arg4, Type[] arg5) {
        return null;
    }
    public Object instantiate(Class arg0, Serializable arg1) {
        return null;
    }

    public void postFlush(Iterator arg0) {
        Set tempLobs = (Set) threadTempLobs.get();

        if (tempLobs == null) {
            return;
        }

        try {
            for (Iterator iter = tempLobs.iterator(); iter.hasNext();) {
                Object lob = iter.next();
                Method freeTemporary = lob.getClass().getMethod("freeTemporary", new Class[0]);
                freeTemporary.invoke(lob, new Object[0]);

                log.info("lob cleaned");
            }
        } catch (SecurityException e) {
            log.error("clean LOB failed: " + e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            log.error("clean LOB failed: " + e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            log.error("clean LOB failed: " + e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            log.error("clean LOB failed: " + e.getMessage(), e);
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            log.error("clean LOB failed: " + e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            threadTempLobs.set(null);
            tempLobs.clear();
        }
    }

    // register oracle temperary BLOB/CLOB into 
    // a thread-local set, this should be called at
    // the end of nullSafeSet(...) in BinaryBlobType
    // or StringClobType
    public static void registerTempLobs(Object lob) {
        getTempLobs().add(lob);
    }

    // lazy create temperary lob storage
    public static Set getTempLobs() {
        Set tempLobs = (Set) threadTempLobs.get();

        if (tempLobs == null) {
            tempLobs = new HashSet();
            threadTempLobs.set(tempLobs);
        }

        return tempLobs;
    }
}

things that you need to do (beside copy/paste):

  1. if using Oracle - use Oracle's 9 (or grater) drivers
  1. obtain session with interceptor

sessionFactory.openSession(new LobCleanUpInterceptor());

  1. use it:

<property name="lobField" column="whatever" type="StringClobType"/>

Tested on Oracle 8i and 9i (Oracle 9 drivers; ojdbc14 9.2.0.5), HSQLDB 1.7.2, MySql 4.0 (Connector 3.0.15-ga).

Note for MySql users: - CLOB becomes TEXT and it can hold only up to 65k, if you need more add length="16777215" to your column mapping for MEDIUMTEXT or add more for LONGTEXT.


Another solution for Oracle 8i

- does not need an Interceptor, works with 8i JDBC drivers

By Eli Levine [elilevine _AT_ yahoo] October 18, 2004

This implementation requires two additional DB objects, a sequence and a temporary table, created as such:

-- create table
CREATE GLOBAL TEMPORARY TABLE TEMP_CLOB_TABLE ( 
  ID         NUMBER, 
  TEMP_CLOB  CLOB) ON COMMIT DELETE ROWS; 

-- create sequence
CREATE SEQUENCE SEQ_TEMP_CLOB_ID INCREMENT BY 1 START WITH 0 MINVALUE 0 MAXVALUE 99999999 CYCLE NOCACHE NOORDER;

UserType implementation:

import java.io.IOException;
import java.io.Writer;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;
import oracle.sql.CLOB;

public class StringClobTypeUsingTempTable implements UserType {

    /**
     * @return java.sql.Types.CLOB
     */
    public int[] sqlTypes() {
        return new int[] { Types.CLOB };
    }

    /**
     * @return java.lang.String.class
     */
    public Class returnedClass() {
        return String.class;
    }
    
    public boolean equals(Object x, Object y) throws HibernateException {
        return (x == y) || (x != null && y != null && (x.equals(y)));
    }

    
    /**
     * @see net.sf.hibernate.UserType#nullSafeGet(java.sql.ResultSet, java.lang.String[], java.lang.Object)
     */
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
        Clob clob = rs.getClob(names[0]);
        return clob.getSubString(1, (int) clob.length());
    }

    /**
     * @see net.sf.hibernate.UserType#nullSafeSet(java.sql.PreparedStatement, java.lang.Object, int)
     */
    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        
        int tempClobId;
        
        Connection con = st.getConnection();
        Statement sta;
        ResultSet rs;
        
        String sql = "select seq_temp_clob_id.nextval from dual";
        
        sta = con.createStatement();
        rs = sta.executeQuery(sql);
        rs.next();
        tempClobId = rs.getInt(1);
        
        rs.close();
        sta.close();
        
        sta = con.createStatement();
        sql = "insert into temp_clob_table (id, temp_clob) values(" + tempClobId + ", empty_clob())";
        sta.executeUpdate(sql);
        
        sta.close();
        
        sta = con.createStatement();
        sql = "select temp_clob from temp_clob_table where id=" + tempClobId+ " for update";
        sta = con.createStatement();
        rs = sta.executeQuery(sql);
        
        rs.next();
            
        CLOB tempClob = (CLOB)rs.getClob(1);
            
        Writer tempClobWriter = tempClob.getCharacterOutputStream();
        try {
            tempClobWriter.write((String)value);
            tempClobWriter.flush();
            tempClobWriter.close();
        } catch (IOException ioe) {
            throw new HibernateException(ioe);
        }
        
        rs.close();
        sta.close();
        
        st.setClob(index, tempClob);
    }

    /**
     * @see net.sf.hibernate.UserType#deepCopy(java.lang.Object)
     */
    public Object deepCopy(Object value) throws HibernateException {
        if (value == null) return null;
        return new String((String) value);
    }

    /**
     * @return false
     * @see net.sf.hibernate.UserType#isMutable()
     */
    public boolean isMutable() {
        return false;
    }

}

No Interceptor is needed to clean up the temporary CLOB because it is created in the global temporary table and is cleared by the database on commit.


  NEW COMMENT

in first example, need to call pw.flush()
06 Oct 2003, 18:51
djbigmac
Hi, in the code in the first two gray boxes above, we found we had to
call pw.flush() after pw.write(content), otherwise, worked great. cheers.
 
Temporary Clob Resource Leak
10 Oct 2003, 16:05
rgodfrey
The createTemporary() method of oracle.sql.CLOB allocates resources 
which should be cleaned up with a call to freeTemporary() once the 
PreparedStatement has been executed. As the StringClobType class 
stands at the moment it doesn't cleanup the temporary clob and will 
slowly leak memory resources.

I'm just evaluating Hibernate at the moment and so don't know if there 
is an appropriate hook that can be used to ensure that freeTemporary() 
gets called post statement execution.
 
Re: Temporary Clob Resource Leak
14 Oct 2003, 13:07
rgodfrey
The best solution I've come up with to clean up the temporary clobs is 
to add a transaction event listener that calls freeTemporary after the 
transaction commits or rolls back. Hibernate 2.2 with its event API 
may allow for the clean up to be performed immediately after the 
prepared statement executes, will wait and see.
 
implementing a UserType with Oracle 8i
24 Oct 2003, 12:14
smaring
That's a great example, but it does not help us folks strapped to 
Oracle 8i.  If I could somehow get a handle to the transaction, I 
could register a Synchronization to be called after a commit puts an 
empty_lob() in the DB as a locator.  I could then stream the content 
to the locator from my Synchronization.  This, of coarse, assumes that 
I am able to find the locator again, which does not seem hopeful given 
the limited context at this point.

Does anyone have any ideas here?
 
Re: Temporary Clob Resource Leak
07 Nov 2003, 11:36
bd
On 14 Oct 2003 13:07, rgodfrey wrote:

On WebLogic I had to put the clean-up code in beforeCompletion() method,
as WebLogic somehow logs out the JDBC connection before
afterCompletion() is called.
 
See also this forum discussion...
08 Nov 2003, 23:22
christian
Implementing a UserType for multiple VARCHAR(4000) columns instead of CLOBS:

 
Getting underlying connection in JBoss
15 Nov 2003, 00:52
MjH
As indicated in the Notes (#1), when using the StringClobType with an
application server requires some additional code to get to the actual
database connection.  To do this in JBoss (3.2) requires the following:

- Add $JBOSS_HOME/server/default/lib/jboss-common-jdbc-wrapper.jar to
the compiler classpath.
- Add the following import statement:

  import org.jboss.resource.adapter.jdbc.WrappedConnection;

- Change the code that checks the validity of the connection object to
something like:

// Make sure connection object is right type
if (!oracleConnectionClass.isAssignableFrom(conn.getClass()))
{
  if (conn.getClass().getName().startsWith("org.jboss"))
  {
    conn = ((WrappedConnection)conn).getUnderlyingConnection();
  }
            
  if (!oracleConnectionClass.isAssignableFrom(conn.getClass()))
  {
    throw new HibernateException("JDBC connection object must be a
oracle.jdbc.OracleConnection. " +
                                 "Connection class is " +
conn.getClass().getName());
  }
}

It's not rocket science, but hopefully this note will save somebody some
time.


Maury....
 
Re: Temporary Clob Resource Leak
05 Dec 2003, 16:21
yvanhess
On 07 Nov 2003 11:36, bd wrote:




method,



We also use the Oracle CLOB custom type solution in our application.
Can you explain more in detail the solution/the pattern to solve the 
resource leak problem.
 
Re: Temporary Clob Resource Leak
13 Dec 2003, 16:17
bd
On 05 Dec 2003 16:21, yvanhess wrote:





It's simple, but making it work in your environment might not be. You
need to execute this statement after you're done with the CLOB: 
---
oracle.sql.CLOB.freeTemporary(temporaryClob);
---

I wanted to make this non-intrusive, so that user of the CLOB mapper
would not need to manually code the release (which would render mapper
useless). So I hooked cleanup code to a transaction listener and hooked
the listener to the UserTransaction. If you're not using JTA, you will
have to find some other hook. It will be all much easier when Hibernate
gets proper event handling.

Code illustration:
---
Context ctx = new InitialContext();
Transaction t =
((TransactionManager)ctx.lookup("java:comp/UserTransaction")).getTransaction()
t.registerSynchronization(new Synchronization()
{
  public void beforeCompletion()
  {
// temporaryClob is the temp. CLOB created by the mapper. Variable must 
// of course be final for this to work and in scope.
    oracle.sql.CLOB.freeTemporary(temporaryClob);
  }
  public void afterCompletion(int i)
  {
    // nothing to do here
  }
});
---
 
Re: Temporary Clob Resource Leak
17 Dec 2003, 10:49
bd
On 13 Dec 2003 16:17, bd wrote:










If you are going to implement this (on any appserver), try to do some
load-tests to check if there is a memory leak. I just found out that
with my approach WebLogic leaks prepared statements. If I find a way to
correct this, I'll try to post complete mapper on the main page.
 
Re: Temporary Clob Resource Leak
07 Jan 2004, 11:03
neil_g_avery
This still feels like a messy hack to get around oracle shortcomings
that means hibernate or the developer code has to jump through hoops.
From a point of cleanliness it would be prefereble to not have to resort
to a JTA callback and then be able to hide freeTemp call through an
upcoming hibernate callback api (as previously mentioned).

The problem comes down to the issues of obtaining a clob/blob pointer
into the DB which the oracle jdbc driver provides 2 mechanisms for - 
1) either through a ResultSet or Statement (non-prep) executing and
interrogating the resultset.
2) or using a temporary Clob - which is simpler and less intruisive
however hits you with the overhead of resorting to callbacks to free the
temp storage.

(apologies for stating the obvious)

Would it be possible to code scenario 1) as implied below ? - and hide
that code in the user defined type? This means we need a reference to
the session and current object we are trying to persist.

<<snip>>
foo = new Foo();
foo.setClob( Hibernate.createClob(" ") );
s.save(foo);
s.flush();
s.refresh(foo, LockMode.UPGRADE); //grabs an Oracle CLOB
oracle.sql.CLOB clob = (oracle.sql.CLOB) foo.getClob();
java.io.Writer pw = clob.getCharacterOutputStream();
<<snip>>
 
Temporary Clob solution - nullSafeGet method eats newlines
14 Jan 2004, 06:34
ericmason
If you're going to throw that class into your project and use it
verbatim, you might want to change nullSafeGet as follows:
<snip>
	String lineSeparator = System.getProperty("line.separator");
	StringBuffer sb = new StringBuffer();
    BufferedReader bufferedClobReader = new BufferedReader(clobReader);
    try
    {
      String line = null;
      while( (line = bufferedClobReader.readLine()) != null )
      {
      	sb.append(line + lineSeparator);
      }
      bufferedClobReader.close();
    }
    catch (IOException e)
    {
      throw new SQLException( e.toString() );
    }

    return sb.toString();
<snip>

Changes: 
1. Adds the line separator back in, as the BufferedReader removes it.  
2. Uses a string buffer, which I'm told is faster than instantiating
tons of strings.

There may be a better way to do this, but I tested this one and I know
it works.  :)
 
Re: Temporary Clob solution - nullSafeGet method eats newlines
15 Jan 2004, 16:55
rgodfrey
Reading into a buffer using read() rather than readline() is a little 
more efficient and a bit cleaner IMO. E.g.


	char[] charbuf = new char[BUF_SIZE];

	for (int i = clobReader.read(charbuf); i > 0; i = 
clobReader.read(charbuf)) {
		sb.append(charbuf, 0, i);
	}
 
Re: Temporary Clob Resource Leak
30 Jan 2004, 14:43
bd
On 07 Jan 2004 11:03, neil_g_avery wrote:









[deleted]


Unfortunately, you cannot call save(), update(), flush() and so on in
the type mapper.
 
Re: Temporary Clob Resource Leak
02 Mar 2004, 17:21
Kai
I am using the JDBC driver 9.2.0.3.0 and found within the 
documentation the siganture:
  createTemporary(java.sql.Connection conn, boolean cache, int 
duration) 
Duration can be DURATION_CALL and DURATION_SESSION. 
Is DURATION_CALL the solution?
 
Re: Temporary Clob Resource Leak
02 Mar 2004, 17:22
Kai
I am using the JDBC driver 9.2.0.3.0 and found within the 
documentation the siganture:
  createTemporary(java.sql.Connection conn, boolean cache, int 
duration) 
Duration can be DURATION_CALL and DURATION_SESSION. 
Is DURATION_CALL the solution?

Sorry for posting twice, but it was not in the right thread.
 
Re: Temporary Clob Resource Leak
11 Mar 2004, 22:34
hypernate
On 02 Mar 2004 17:22, Kai wrote:










DURATION_CALL should only be used in PL/SQL code.  Stick to
DURATION_SESSION for JDBC clients.
 
linebreaks disapears in nullSafeGet
12 Mar 2004, 09:19
Kai
Hi,

if you have stored "First line/nSecond line" nullSafeGet will 
return "First lineSecond line". 
The simple solution would be to add a "/n" after each line expected 
the last one.
However what you are thinking about this solution:
  public Object nullSafeGet(ResultSet rs, String[] names, Object 
owner) throws HibernateException, SQLException {
    Clob clob = rs.getClob(names[0]);
    if(clob == null) {
      return null;
    }
    return clob.getSubString(1, (int) clob.length());
  }

This also works fine for more than 4000 characters and null values.

Bye
Kai
 
ClassCastException using Hibernate2 Blobs
25 Mar 2004, 01:15
dannwebster
The following could be regarded as a trivial question, but it seems to
be a common problem, since variations of it show up in the forums
several times. So that the forums have a central reference for this
problem, I think that this page should explain why this problem occurs
and how to overcome it. The Problem:

I am using the following code to access blobs (instead of clobs. It is
the trivial modification of the code included on this page, but
presumably this problem exists on both blob and clob types...if not, the
distinction should be noted on this page):

<code>
s = sf.openSession();
tx = s.beginTransaction();
foo = new Foo();
foo.setBlob( Hibernate.createBlob(new byte[1]) );
s.save(foo);
s.flush();
s.refresh(foo, LockMode.UPGRADE); //grabs an Oracle BLOB
oracle.sql.BLOB blob = (oracle.sql.BLOB) foo.getBlob();
OutputStream os = blob.getBinaryOutputStream();
os.write(content);
os.flush();
os.close();
tx.commit();
s.close();
</code>

This code always throws a ClassCastException on this line:

<code>
oracle.sql.BLOB blob = (oracle.sql.BLOB) foo.getBlob();
</code>

When hibernate gets the Blob, it is of type
net.sf.hibernate.lob.BlobImpl, which is not castable to oracle.sql.BLOB.
Since BlobImpl is what Hibernate uses to implement any type which is
generated using a "blob" type in the .hbm.xml file, and it makes sense
that it is not a subclass of oracle.sql.BLOB. 

The alternative would seem to be to define your "blob" in the .hbm.xml
file as "oracle.sql.BLOB," but then this line will fail to compile:

<code>
foo.setBlob( Hibernate.createBlob(new byte[1]) );
</code>

because Hibernate.createBlob() creates a java.sql.Blob, which is not
castable to foo's blob property, since it is now set to a specific
implementation (oracle.sql.BLOB) instead of the generalized
java.sql.Blob interface.

Is there some setting that needs to be used in the .hbm.xml file to make
the Hibernate BlobImpl castable to oracle.sql.BLOB, or is there
something specific to Blobs as opposed to Clobs that makes this occur?
Or am I fundamentally misunderstanding something else that I should get
from reading this page?
 
Re: ClassCastException using Hibernate2 Blobs
26 Mar 2004, 19:23
dannwebster
From above:

<snip>
This code always throws a ClassCastException on this line:

<code>
oracle.sql.BLOB blob = (oracle.sql.BLOB) foo.getBlob();
</code>

When hibernate gets the Blob, it is of type
net.sf.hibernate.lob.BlobImpl, which is not castable to oracle.sql.BLOB.
Since BlobImpl is what Hibernate uses to implement any type which is
generated using a "blob" type in the .hbm.xml file, and it makes sense
that it is not a subclass of oracle.sql.BLOB. 

</snip>

This problem is actually simply because the offending foo was part of a
lazy-loaded object fetched as part of a collection. It goes away if the
object itself is refreshed, or if Hibernate.initialize() is called on
the collection that foo is part of.
 
Re: ClassCastException using Hibernate2 Blobs
30 Mar 2004, 11:21
leenamba
On 26 Mar 2004 19:23, dannwebster wrote:









a

the



I'm using Weblogic 8.1 and I continued to get a ClassCastException at 
that line even after refreshing the object.  The solution was to use 
the Weblogic 8.1 Oracle extensions found in weblogic.jar

<code>
import weblogic.jdbc.vendor.oracle.OracleThinBlob;

...
OracleThinBlob blob = (OracleThinBlob)foo.getBlob();
...
</code>
 
Getting the "actual" Oracle connection problem
30 Apr 2004, 18:59
branscomp
Yes the approach is fragile and I have experienced a problem using 
C3P0 connection pooling because the java.sql.Connection implementation 
that it supplies is a generated proxy and can never be cast to an 
oracle.jdbc.driver.OracleConnection nor can the native connection be 
obtained because the Connection interface, quite rightly, supports no 
such method. However I have found that the DatabaseMetaData instance 
returned from a call to getMetaData on this Connection proxy does 
return the native/original connection when you call its getConnection
() method. How about that. Have not tried this on any other connection 
pooling implementations though but it might be worth a go.

eg
DatabaseMetaData dbMetaData = st.getConnection().getMetaData
().getConnection()

seems bizarre I know but it works.

Paul Branscombe
 
Re: ClassCastException using Hibernate2 Blobs
08 Jun 2004, 09:20
alu1344
for Oracle9i and optional weblogic:
<code>
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.UserType;
import oracle.sql.CLOB;

import org.apache.commons.lang.ObjectUtils;
import org.apache.log4j.Logger;

import weblogic.jdbc.extensions.WLConnection;

/**
 */
public class StringClobType implements UserType {
	
	private Logger log = Logger.getLogger(getClass());

	/**
	 * Return the SQL type codes for the columns mapped by this type. 
	 */
    public int[] sqlTypes() {
        return new int[] { Types.CLOB};
    }

	/**
	 * The class returned by <tt>nullSafeGet()</tt>.
	 */
    public Class returnedClass() {
        return String.class;
    }

    public boolean equals(Object x, Object y) {
        return ObjectUtils.equals(x, y);
    }

	/**
	 * Retrieve an instance of the mapped class from a JDBC resultset.
Implementors
	 * should handle possibility of null values.
	 */
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
        throws HibernateException, SQLException {
        Reader clobReader = rs.getCharacterStream(names[0]);
        if (clobReader == null) 
            return null;

        StringBuffer str = new StringBuffer();
        BufferedReader bufferedClobReader = new BufferedReader(clobReader);
        try {
            String line = null;
            while ((line = bufferedClobReader.readLine()) != null) 
                str.append(line);
        } catch (IOException e) {
            throw new SQLException(e.toString());
        } finally {
            try {
				bufferedClobReader.close();
			} catch (IOException e) {
			}
        }

        return str.toString();
    }

	/**
	 * Write an instance of the mapped class to a prepared statement.
Implementors
	 * should handle possibility of null values. A multi-column type should
be written
	 * to parameters starting from <tt>index</tt>.
	 * 
	 */
    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
    	
        if (value == null) {
            st.setNull(index, sqlTypes()[0]);
            return;
        }
        
        try {
            Connection conn =
st.getConnection().getMetaData().getConnection();
            
            if (conn instanceof WLConnection)
            	conn = ((WLConnection)conn).getVendorConnection();
            log.info(conn.getClass().getName());
            
            Writer tempClobWriter = null;
            CLOB tempClob = CLOB.createTemporary(conn, true,
CLOB.DURATION_SESSION);
            try {
	            tempClob.open(CLOB.MODE_READWRITE);
	            tempClobWriter = tempClob.getCharacterOutputStream();
	            tempClobWriter.write((String) value);
	            tempClobWriter.flush();
            } finally {
            	if (tempClobWriter != null)
            		tempClobWriter.close();
	            tempClob.close();
            }
            
            st.setClob(index, (Clob) tempClob);
        } catch (IOException e) {
            throw new HibernateException(e);
        }
    
    }

	/**
	 * Return a deep copy of the persistent state, stopping at entities and at
	 * collections.
	 */
    public Object deepCopy(Object value) {
    	return (String)value;
    }

	/**
	 * Are objects of this type mutable?
	 */
    public boolean isMutable() {
        return false;
    }
    

}
</code>
 
ALso read this thread
07 Jul 2004, 19:04
christian
 
OC4J datasource connection - NullPointerException
21 Jul 2004, 16:58
niranjan
What is the best way to handle pooled (wrapper) connections? Could you 
please post the OC4J workaround that you have mentioned in notes?
 
license on code in this page
22 Jul 2004, 16:35
JimDaues
what license applies to all the code authored by Ibrahim and Miller on 
this page? is there a statement somewhere that everything posted in 
the community area is public domain?
 
Re: license on code in this page
22 Jul 2004, 16:38
JimDaues
On 22 Jul 2004 16:35, JimDaues wrote:


on 



and by "this page" I mean  
"Using Clobs with Oracle and Hibernate "
 
Re: license on code in this page
23 Jul 2004, 00:13
christian
On 22 Jul 2004 16:35, JimDaues wrote:





I'd say so, as long as no other license is mentioned. The copyright is 
of course still valid, so I'd take it and keep the @author line as a 
reference. Basically, putting code on a public forum means "free for 
distribution and re-use in whatever way you like". Any other 
interpretation wouldn't be sensible.
 
Free temperary clob/blob with interceptor (1)
12 Sep 2004, 10:36
ingramchen
We are using a specialized interceptor to free temperary clob/blob
resource, it seems work fine now:

<code>
public class LobCleanUpInterceptor implements Interceptor {
    private static final Logger logger = Logger
            .getLogger(LobCleanUpInterceptor.class);

    // leave these method unchanged
    public boolean onSave(...)
    public boolean onLoad(...)
    public boolean onFlushDirty(...)
    public void onDelete(...)
    public void preFlush(...)
    public Boolean isUnsaved(...)
    public int[] findDirty(...)
    public Object instantiate(...)

    // a thread local set to store temperary LOBs
    private static final ThreadLocal threadTempLobs 
                                     = new ThreadLocal();

    // after flush(), clean all registered LOBs
    public void postFlush(Iterator entities) 
                           throws CallbackException {
        Set tempLobs = (Set) threadTempLobs.get();
        if (tempLobs == null) {
            return;
        }
        try {
            for (Iterator iter = tempLobs.iterator(); 
                                      iter.hasNext();) {
                Object lob = iter.next();
                cleanIfBLOB(lob);
                cleanIfCLOB(lob);
            }
        } catch (SQLException e) {
            logger.fatal("clean LOB failed"+e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            threadTempLobs.set(null);
            tempLobs.clear();
        }
    }

    // free temperary clob resource
    private static void cleanIfCLOB(Object lob) 
                                throws SQLException {
        if (lob instanceof oracle.sql.CLOB) {
            oracle.sql.CLOB clob = (oracle.sql.CLOB) lob;
            if (clob.isTemporary()) {
                oracle.sql.CLOB.freeTemporary(clob);
                logger.info("clob cleaned");
            }
        }
    }

    // free temperary blob resource
    private static void cleanIfBLOB(Object lob) 
                                 throws SQLException {
        if (lob instanceof oracle.sql.BLOB) {
            oracle.sql.BLOB blob = (oracle.sql.BLOB) lob;
            if (blob.isTemporary()) {
                oracle.sql.BLOB.freeTemporary(blob);
                logger.info("blob cleaned");
            }
        }
    }

    // register oracle temperary BLOB/CLOB into 
    // a thread-local set, this should be called at
    // the end of nullSafeSet(...) in BinaryBlobType
    // or StringClobType
    public static void registerTempLobs(Object lob) {
        getTempLobs().add(lob);
    }

    // lazy create temperary lob storage
    public static Set getTempLobs() {
        Set tempLobs = (Set) threadTempLobs.get();
        if (tempLobs == null) {
            tempLobs = new HashSet();
            threadTempLobs.set(tempLobs);
        }
        return tempLobs;
    }
}
</code>
 
Re: Free temperary clob/blob with interceptor (2) (continued)
12 Sep 2004, 10:37
ingramchen
And in BinaryBlobType/StringClobType, add register at the 
end of nullSafeSet(...)
<code>
public void nullSafeSet(PreparedStatement st, 
                        Object value, int index)
        throws HibernateException, SQLException {

    if (value == null) {
        st.setClob(index, null);
        return;
    }

    if (isOraclePreparedStatement(st)) {
        // skip ...
        // open temperary clob and write....
        // skip ...
        temperaryClob.close();
        LobCleanUpInterceptor.registerTempLobs(temperaryClob);
    } else {
        st.setClob(index, 
               Hibernate.createClob((String) value));
    }
}
</code>

finally, obtain session with interceptor

   sessionFactory.openSession(new LobCleanUpInterceptor());

After flush() or commit(), nullSafeSet(...) will be called
and register temperary LOBs into a thread local set.
when flush() is complete. postFlush(...) will be invoked and
all registered temperary LOBs will be cleaned.

This interceptor may only work when applying 
thread-local-session pattern.

regards.
 
summary...
29 Sep 2004, 16:04
Lukasz (Qr)
After doing some research and trying to use it, the code for 
StringClobType requires some changes. I hope that this will save 
someone some time and digging through all the different threads.

1) readline issue:
Just do not use readline() - (check: rgodfrey's post)
<code>
	        StringBuffer sb = new StringBuffer(); 
	        try { 
	            char[] charbuf = new char[4096]; 
	            for (int i = clobReader.read(charbuf); i > 0; i = 
clobReader.read(charbuf)) { 
	                sb.append(charbuf, 0, i); 
	            } 
	        } catch (IOException e) { 
	            throw new SQLException(e.getMessage()); 
	        } 
	        return sb.toString(); 
</code>
on the other hand why shouldn't we just as something simple as:
<code>
    public Object nullSafeGet(ResultSet rs, String[] names, Object 
owner)
            throws SQLException {
        Clob clob = rs.getClob(names[0]);
        return (clob==null? null :clob.getSubString(1, (int) 
clob.length()));
    }
</code>
it works fine for me (Oracle 8i with Oracle 9i drivers)

2) obtain base connection, not the dbcp wrapper:
Connection conn = st.getConnection();
should be changed to:
Connection conn = ps.getConnection().getMetaData().getConnection();
(check: Paul Branscombe's post)

3) freeTemporary lob 
check ingramchen's both posts - "Free temperary clob/blob with 
interceptor (1)"
Great job ingramchen!

I would use reflection to avoid a compile-time dependency on the 
Oracle driver. To do so change:
<code>
                cleanIfBLOB(lob);
                cleanIfCLOB(lob);
</code>
to:
<code>
    			Method freeTemporary = lob.getClass
().getMethod("freeTemporary", new Class[0]);
    			freeTemporary.invoke(lob, new Object[0]);
</code>

and finally tip 'how to set interceptor' for those who would like to 
use springframework.orm.hibernate&#8217;s 
org.springframework.orm.hibernate.LocalSessionFactoryBean
just
lsfb.setEntityInterceptor(new LobCleanUpInterceptor());
or even better user xml equivalent.

Thanks all.

Lukasz
 
All together
30 Oct 2004, 12:51
kalgon
Hi, I've been also struggling with blobs and hibernate for some weeks 
so I took all good ideas (interceptors for example) that have been 
posted here before and put them in a set of classes + introduced the 
concept of BlobFactory. My code is available on 
 if you have comments or suggestions, 
they're all welcome. Thanks.
 
Re: All together
30 Oct 2004, 12:55
kalgon
On 30 Oct 2004 12:51, kalgon wrote:








 (sorry, the comma corrupted the link =)
 
Re: All together
18 Nov 2004, 14:21
Woyzeck
On 30 Oct 2004 12:55, kalgon wrote:



Thx a lot, but there's just the JavaDoc available. Where can we see 
the code ?
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章