2

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

网上找到的现有解决方案 都有两个问题:

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

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

因此,我在底部编写了这些类来存储 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);
    }
}
4

1 回答 1

1

我认为 Hibernate 有办法添加一个 ActionListener 之类的东西,它可以在提交完成后做一些工作。freenode 上#hibernate room 的某个人建议我们尝试使用 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???
                    }
                }
            });
        }

    }
}

测试实体.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;
    }

}
于 2013-06-05T03:17:49.917 回答