如何在Hibernate UserTypes中清理LOB值?

时间:2013-05-29 21:33:02

标签: oracle hibernate jdbc sqlxml xmltype

我正在运行Oracle 11.2.0.3并且正在尝试创建一个可行的UserType来映射XMLType或SQLXML列。

Existing solutions found在线都存在两个问题:

  1. XMLType值是LOB值,因此它们必须在Connection.close()之前是free(),否则它们将泄漏Java中的数据库资源和堆内存。

  2. 从这些列获取的XML值是连接对象;除非通过深层复制复制它们,否则它们会在连接关闭后消失。

  3. 因此,我在底部编写了这些类来存储XMLType对象 我的问题是 - 由于这些是LOB值,因此必须在事务提交之后释放,但在连接关闭之前 。有没有办法让Hibernate UserType执行此操作?暂时忽略这是一个SQLXML对象的事实 - 如果它是BLOB或CLOB,并且我有相同的要求(有人必须在提交后但在close()之前调用free()),我该怎么做它?

    感谢您阅读所有这些......

    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);
        }
    }
    

    (是的,以上是特定于Oracle的...对于那些寻找与DBMS无关的类的人来说,它看起来像这样,但请注意警告,我还没有测试过它):

    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);
        }
    }
    

1 个答案:

答案 0 :(得分:1)

我认为Hibernate可以通过某种方式添加一个ActionListener,它可以在提交完成后完成一些工作。来自freenode上#hibernate房间的人建议我们尝试使用AfterTransactionCompletionProcess来完成我们需要的工作。

所以下一个显而易见的问题是......我可以使用哪个例子?我打开了一个SOF问题并自己回答:How to use org.hibernate.action.spi.AfterTransactionCompletionProcess?

因此,使用此示例加上您提供的HibernateXMLType类,我们现在可以注册AfterTransactionCompletionProcess进程,以便调用它以满足您的要求:“必须在事务提交后调用,但在连接关闭之前。“

以下是源代码。

请查看我遇到困难的评论。我不确切地知道从实体调用什么来手动清除内存。我想知道如何从free()方法调用实体中java.sql.SQLXML对象的doAfterTransactionCompletion方法...从而消除内存泄漏。

我会在早上恢复原状,看看能不能搞清楚。也许这就是获得解决方案所需的全部内容?如果是这样,太好了!

<强> HibernateTest.java

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;

public class HibernateTest {
    public static void main(String [] args) {
        PostInsertTransactionBoundaryListener listener = new PostInsertTransactionBoundaryListener();
        Configuration configuration = new Configuration();
        configuration.configure();
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
        EventListenerRegistry registry = serviceRegistry.getService(EventListenerRegistry.class);
        registry.getEventListenerGroup(EventType.POST_COMMIT_INSERT).appendListener(listener);
        SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);        
        Session session = sessionFactory.openSession();
        session.getTransaction().begin();

        TestEntity entity = new TestEntity();
        session.save(entity);
        session.getTransaction().commit();
        session.close();

    }
    private static class PostInsertTransactionBoundaryListener implements PostInsertEventListener {
        private static final long serialVersionUID = 1L;
        public void onPostInsert(final PostInsertEvent event) {
            event.getSession().getActionQueue().registerProcess(new AfterTransactionCompletionProcess() {
                public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
                    TestEntity testEntity = (TestEntity)event.getEntity();
                    if (testEntity != null) {
                        // How can I free the memory here to avoid the memory leak???
                    }
                }
            });
        }

    }
}

<强> TestEntity.java

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "TEST")
public class TestEntity {
    @Id
    @GeneratedValue
    private Integer id;

    private HibernateXMLType xml;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public HibernateXMLType getXml() {
        return xml;
    }

    public void setXml(HibernateXMLType xml) {
        this.xml = xml;
    }

}