I am running Oracle 11.2.0.3 and am attempting to create a workable UserType that maps XMLType or SQLXML columns.
Existing solutions found online all have two problems:
XMLType values are LOB values, so they must be free() prior to Connection.close() or they will leak both database resources and heap memory in Java.
XML values fetched from these columns are connected objects; unless they are copied via a deep copy, they are gone after the connection closes.
So, I wrote this classes at the bottom to store XMLType objects.
My question is this - since these are LOB values, they must be freed after the transaction commits, but before the connection closes. Is there a way to get a Hibernate UserType to do this? Ignore the fact that this is an SQLXML object for a moment - if it were a BLOB or a CLOB, and I had the same requirement (that someone has to call free() after commit but before close()), how would I do it?
Thank you for reading through all this...
package com.mycomp.types;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import oracle.xdb.XMLType;
import oracle.xml.binxml.BinXMLDecoder;
import oracle.xml.binxml.BinXMLException;
import oracle.xml.binxml.BinXMLStream;
import oracle.xml.jaxp.JXSAXTransformerFactory;
import oracle.xml.jaxp.JXTransformer;
import oracle.xml.parser.v2.XMLDOMImplementation;
import oracle.xml.parser.v2.XMLDocument;
import oracle.xml.scalable.InfosetReader;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import org.w3c.dom.DOMException;
/**
* This class encapsulates the XMLDocument class into a database XMLType.
* It is used to allow Hibernate entities to use XMLDocument transparently
* for persistence as XMLTypes in an Oracle database.
*
* @author bmarke
*
*/
public class HibernateXMLType implements UserType
{
private static final String CAST_EXCEPTION_TEXT = " cannot be cast to a oracle.xml.parser.v2.XMLDocument.";
@Override
public int[] sqlTypes()
{
return new int[] { Types.SQLXML };
}
@Override
public Class<?> returnedClass()
{
return XMLDocument.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException
{
if (x == y)
{
return true;
}
if (!(x instanceof XMLDocument && y instanceof XMLDocument))
{
throw new HibernateException(x.getClass().toString()
+ CAST_EXCEPTION_TEXT);
}
return ObjectUtils.equals(x, y);
}
@Override
public int hashCode(Object x) throws HibernateException
{
if (!(x instanceof XMLDocument))
{
throw new HibernateException(x.getClass().toString()
+ CAST_EXCEPTION_TEXT);
}
return x.hashCode();
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException
{
XMLType xmlData = (XMLType) rs.getSQLXML(names[0]);
XMLDocument doc = null;
XMLDocument toReturn = null;
BinXMLStream stream = null;
InfosetReader reader = null;
if (xmlData == null)
{
doc = null;
toReturn = null;
}
else
{
try
{
stream = xmlData.getBinXMLStream();
BinXMLDecoder decoder = stream.getDecoder();
reader = decoder.getReader();
XMLDOMImplementation domImpl = new XMLDOMImplementation();
domImpl.setAttribute(XMLDocument.SCALABLE_DOM, true);
domImpl.setAttribute(XMLDocument.ACCESS_MODE,
XMLDocument.UPDATEABLE);
doc = (XMLDocument) domImpl.createDocument(reader);
toReturn = (XMLDocument)deepCopy(doc);
}
catch (IllegalArgumentException e)
{
throw new HibernateException(e);
}
catch (DOMException e)
{
throw new HibernateException(e);
}
catch (BinXMLException e)
{
throw new HibernateException(e);
}
finally
{
if(doc != null)
{
doc.freeNode();
}
if(reader != null)
{
reader.close();
}
if(stream != null)
{
stream.close();
}
if(xmlData != null)
{
xmlData.close();
}
}
}
return toReturn;
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException
{
if( value == null )
{
st.setNull(index, Types.SQLXML);
}
else if( !(value instanceof XMLDocument) )
{
throw new HibernateException(value.getClass().toString()
+ CAST_EXCEPTION_TEXT);
}
else
{
XMLDocument xml = (XMLDocument) value;
XMLType xmlData = null;
try
{
xmlData = new XMLType(st.getConnection().getMetaData().getConnection(), xml);
st.setSQLXML(index, xmlData);
}
finally
{
if(xmlData != null)
{
xmlData.close();
}
}
}
}
@Override
public Object deepCopy(Object value) throws HibernateException
{
XMLDocument orig = (XMLDocument)value;
DOMResult result;
try
{
JXSAXTransformerFactory tfactory = new oracle.xml.jaxp.JXSAXTransformerFactory();
JXTransformer tx = (JXTransformer)tfactory.newTransformer();
DOMSource source = new DOMSource(orig);
result = new DOMResult();
tx.transform(source,result);
return (XMLDocument)result.getNode();
}
catch (Exception e)
{
throw new HibernateException(e);
}
}
@Override
public boolean isMutable()
{
return true;
}
@Override
public Serializable disassemble(Object value) throws HibernateException
{
XMLDocument doc = (XMLDocument) deepCopy(value);
return doc;
}
@Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException
{
XMLDocument doc = (XMLDocument) deepCopy(cached);
return doc;
}
@Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException
{
return deepCopy(original);
}
}
(Yes, the above is Oracle-specific... for those of you looking for a DBMS-agnostic class, it looks like this, but note the warning, and I haven't tested it):
package com.mycomp.types;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLXML;
import java.sql.Types;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
/**
* This class encapsulates the XMLDocument class into a database XMLType.
* It is used to allow Hibernate entities to use XMLDocument transparently
* for persistence as XMLTypes in an Oracle database.
*
* @author bmarke
*
*/
public class HibernateSQLXML implements UserType
{
private static final String CAST_EXCEPTION_TEXT = " cannot be cast to a oracle.xml.parser.v2.XMLDocument.";
@Override
public int[] sqlTypes()
{
return new int[] { Types.SQLXML };
}
@Override
public Class<?> returnedClass()
{
return SQLXML.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException
{
if (x == y)
{
return true;
}
if (!(x instanceof SQLXML && y instanceof SQLXML))
{
throw new HibernateException(x.getClass().toString()
+ CAST_EXCEPTION_TEXT);
}
return ObjectUtils.equals(x, y);
}
@Override
public int hashCode(Object x) throws HibernateException
{
if (!(x instanceof SQLXML))
{
throw new HibernateException(x.getClass().toString()
+ CAST_EXCEPTION_TEXT);
}
return x.hashCode();
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException
{
SQLXML xmlData = rs.getSQLXML(names[0]);
Document toReturn = null;
if (xmlData == null)
{
toReturn = null;
}
else
{
try
{
DOMSource source = xmlData.getSource(DOMSource.class);
toReturn = (Document)deepCopy(source);
}
catch (IllegalArgumentException e)
{
throw new HibernateException(e);
}
catch (DOMException e)
{
throw new HibernateException(e);
}
finally
{
if(xmlData != null)
{
xmlData.free();
}
}
}
return toReturn;
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException
{
if( value == null )
{
st.setNull(index, Types.SQLXML);
}
else if( !(value instanceof Document) )
{
throw new HibernateException(value.getClass().toString()
+ CAST_EXCEPTION_TEXT);
}
else
{
Document xml = (Document) value;
SQLXML xmlData = null;
try
{
xmlData = st.getConnection().createSQLXML();
DOMResult res = xmlData.setResult(DOMResult.class);
res.setNode(xml);
st.setSQLXML(index, xmlData);
}
finally
{
if(xmlData != null)
{
xmlData.free();
}
}
}
}
public Object deepCopy(DOMSource orig) throws HibernateException
{
DOMResult result;
try
{
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer tx = tfactory.newTransformer();
result = new DOMResult();
tx.transform(orig,result);
return (Document)result.getNode();
}
catch (Exception e)
{
throw new HibernateException(e);
}
}
@Override
public Object deepCopy(Object value) throws HibernateException
{
Document orig = (Document)value;
DOMResult result;
try
{
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer tx = tfactory.newTransformer();
DOMSource source = new DOMSource(orig);
result = new DOMResult();
tx.transform(source,result);
return (Document)result.getNode();
}
catch (Exception e)
{
throw new HibernateException(e);
}
}
@Override
public boolean isMutable()
{
return true;
}
@Override
public Serializable disassemble(Object value) throws HibernateException
{
//NOTE: We're making a really ugly assumption here, that the particular parser
//impelementation creates a Document object that is Serializable. In the case
//of the Oracle XDK parser, it is, but it may not be for the default Xerces
//implementation - you have been warned.
Serializable doc = (Serializable) deepCopy(value);
return doc;
}
@Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException
{
Document doc = (Document) deepCopy(cached);
return doc;
}
@Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException
{
return deepCopy(original);
}
}