Как очистить значения LOB в Hibernate UserTypes?

Я запускаю Oracle 11.2.0.3 и пытаюсь создать работоспособный пользовательский тип, который отображает столбцы XMLType или SQLXML.

Существующий решения Найдено онлайн у всех две проблемы:

  1. Значения XMLType являются значениями LOB, поэтому они должны быть free() до Connection.close(), иначе они приведут к утечке как ресурсов базы данных, так и памяти кучи в Java.

  2. Значения XML, полученные из этих столбцов, являются связанными объектами; если они не скопированы с помощью глубокой копии, они исчезнут после закрытия соединения.

Итак, я написал эти классы внизу для хранения объектов XMLType.
Мой вопрос заключается в следующем: поскольку это значения LOB, они должны быть освобождены после фиксации транзакции, но до< /strong> соединение закрывается. Есть ли способ заставить Hibernate UserType сделать это? Не обращайте внимания на тот факт, что это объект SQLXML на мгновение - если бы это был BLOB или CLOB, и у меня было такое же требование (кто-то должен вызвать 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... для тех из вас, кто ищет класс, не зависящий от СУБД, он выглядит так, но обратите внимание на предупреждение, и я его не тестировал):

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

person Brad    schedule 29.05.2013    source источник


Ответы (1)


Я подумал, что у Hibernate есть какой-то способ добавить что-то вроде ActionListener, который может выполнять некоторую работу после завершения коммита. Кто-то из комнаты #hibernate на freenode предложил попробовать использовать AfterTransactionCompletionProcess, чтобы сделать то, что нам нужно.

Итак, следующий очевидный вопрос: где пример, который я могу использовать? Я открыл вопрос SOF и сам ответил на него: Как использовать 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;
    }

}
person Nicholas DiPiazza    schedule 05.06.2013
comment
Спасибо - попробую. Приведенный выше код UserType преобразует объект в XMLDocument, поэтому вы теряете ссылку на SQLXML. Возможно, это можно было бы сохранить как свойство в анонимном объекте... - person Brad; 05.06.2013
comment
Без понятия. Мне не удалось связаться с кем-либо из команды Redhat Hibernate, чтобы поговорить со мной сегодня. Надеюсь скоро. - person Nicholas DiPiazza; 06.06.2013
comment
Можете ли вы воспроизвести утечку памяти на некоторое время, а затем сгенерировать heapdump? Если вы это сделаете, кто все еще держит ссылку на XML-данные? Это даст нам необходимую информацию. - person Nicholas DiPiazza; 06.06.2013
comment
Извините, я не получаю электронные письма автоматически, когда есть комментарии или ответы. Я определенно могу дать вам дамп кучи... 16 ГБ. Ты уверен, что хочешь этого? - person Brad; 09.06.2013
comment
Конечно. Напишите мне по адресу nicholas.dipiazza на gmail с информацией о FTP. Убедитесь, что вы сжимаете его! - person Nicholas DiPiazza; 09.06.2013