-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 
Author Message
 Post subject: Getting around LazyInitializationException in web services
PostPosted: Tue Aug 11, 2009 6:39 pm 
Newbie

Joined: Wed Jul 29, 2009 11:01 pm
Posts: 7
We use Glassfish 2.1 for our application development and have recently switched to use Hibernate as our JPA provider.

Surprisingly, it seemed not easily possible to expose entity beans managed by Hibernate JPA through web services implemented as EJB session beans. I kept getting a LazyInitializationException thrown every time an entity bean with a lazily loaded relationship was returned from one of the web services due to the Hibernate session being terminated before JAXB was trying to marshal the entity beans.

I am aware of the Open Session in View Pattern that is commonly being portrayed on the Hibernate forums as the appropriate solution to this kind of problem when Hibernate entities are to be passed around in a multi-tier environment. However, I think the pattern is neither elegant nor appropriate for SOA applications built around EJBs (correct if I got that wrong).

Disabling all lazy loading and suppressing related entities with hibernate.max_fetch_depth is also not an acceptable solution either in pretty much any larger deployment.

The resolution of JAXB Issue 280 offers a fairly elegant solution to this problem. However, unfortunately, JAXB ignores any @XmlAccessorFactory annotations unless the property com.sun.xml.bind.XmlAccessorFactory is set to true on the JAXBContext responsible for marshalling the entities.

The design decision to default this property of the JAXBContext to false and not allow it to be controlled via a system property does not present a problem when manually marshalling JAXB annotated objects, but hinders customization in a managed environments. Fortunately, the fix to JAX-WS Issue 282 adds the @UsesJAXBContext annotation. This annotation allows specifying the JAXBContext implementation to be used by a particular web service endpoint.

Together these two fixes provide a good workaround to the Hibernate lazy loading problem in EJB web services. So, here is what I did to fix the problem in our setup.

  1. Upgrade to Metro 1.5 in order to be able to take advantages of the described improvements in JAXB and JAX-WS. Instructions on how to apply this upgrade to Glassfish can be found here.
  2. Create an implementation of com.sun.xml.ws.developer.JAXBContextFactory that enables support for @XmlAccessorFactory annotations in the JAXBContext objects it returns.

    Code:
    package util.ws;

    import com.sun.xml.bind.api.JAXBRIContext;
    import com.sun.xml.bind.api.TypeReference;
    import com.sun.xml.bind.v2.ContextFactory;
    import com.sun.xml.ws.api.model.SEIModel;
    import com.sun.xml.ws.developer.JAXBContextFactory;
    import java.util.ArrayList;
    import java.util.List;
    import javax.xml.bind.JAXBException;

    public class JAXBContextFactoryImpl implements JAXBContextFactory {

        public JAXBRIContext createJAXBContext(SEIModel sei, List<Class> classesToBind, List<TypeReference> typeReferences) throws JAXBException {
            return ContextFactory.createContext(classesToBind.toArray(new Class[classesToBind.size()]),
                                                new ArrayList<TypeReference>(typeReferences),
                                                null,
                                                sei.getTargetNamespace(),
                                                false,
                                                null,
                                                true,
                                                false,
                                                false);
        }
    }
  3. Annotate your web service bean with the @UsesJAXBContext[/item] annotation. Like so,

    Code:
    --- SNIP ---

    @Remote({ExampleFacadeBeanRemote.class})
    @WebService(serviceName = "ExampleBeanService")
    @UsesJAXBContext(JAXBContextFactoryImpl.class)
    @Stateless(name = "ExampleFacade")
    @TransactionManagement(TransactionManagementType.CONTAINER)
    public class ExampleFacadeBean implements ExampleFacadeBeanRemote {

    --- SNIP ---
  4. Create an implementation of com.sun.xml.bind.v2.runtime.reflect.Accessor that checks if a field of an entity bean is being lazy loaded and if so returns null instead. Then create a matching implementation of com.sun.xml.bind.AccessorFactory that returns your custom Accessor. There is an example provided in the thread of JAXB Issue 280. I have adapted it as follows in order to be able to share the entity class library between the service layer and its client without requiring the client to have Hibernate on the classpath.

    Code:
    package util.hibernate;


    import com.sun.xml.bind.AccessorFactory;
    import com.sun.xml.bind.AccessorFactoryImpl;
    import com.sun.xml.bind.v2.runtime.reflect.Accessor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import javax.xml.bind.JAXBException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class HibernateJAXBAccessorFactory implements AccessorFactory {

        private final AccessorFactory delegate;
        private Method hibernateInitializationCheck;

        public HibernateJAXBAccessorFactory() {
            this(AccessorFactoryImpl.getInstance());
        }

        public HibernateJAXBAccessorFactory(AccessorFactory delegate) {
            this.delegate = delegate;
            try {
                Class hibernate = Class.forName("org.hibernate.Hibernate");
                hibernateInitializationCheck = hibernate.getMethod("isInitialized", Object.class);
                Logger logger = LoggerFactory.getLogger(HibernateJAXBAccessorFactory.class);
                logger.info("Detected Hibernate: Enabled hiding of uninitialized lazy objects and collections during XML marshalling.");
            } catch(ClassNotFoundException e) {
                hibernateInitializationCheck = null;
                Logger logger = LoggerFactory.getLogger(HibernateJAXBAccessorFactory.class);
                logger.info("Hibernate was not detected: Disabled hiding of uninitialized lazy objects and collections during XML marshalling.");
            } catch(Exception e) {
                hibernateInitializationCheck = null;
                Logger logger = LoggerFactory.getLogger(HibernateJAXBAccessorFactory.class);
                logger.warn("Detected Hibernate, but failed to enable hiding of uninitialized lazy objects and collections during XML marshalling.", e);
            }
        }

        @SuppressWarnings("unchecked")
        public Accessor createFieldAccessor(Class bean, Field field, boolean readOnly) throws JAXBException {
            Accessor accessor = delegate.createFieldAccessor(bean, field, readOnly);

            if(hibernateInitializationCheck == null) {
                return accessor;
            } else {
                return new HibernateJAXBAccessor(accessor, hibernateInitializationCheck);
            }       
        }

        @SuppressWarnings("unchecked")
        public Accessor createPropertyAccessor(Class bean, Method getter, Method setter) throws JAXBException {
            Accessor accessor = delegate.createPropertyAccessor(bean, getter, setter);

            if(hibernateInitializationCheck == null) {
                return accessor;
            } else {
                return new HibernateJAXBAccessor(accessor, hibernateInitializationCheck);
            }
        }
    }


    Code:
    package util.hibernate;

    import com.sun.xml.bind.api.AccessorException;
    import com.sun.xml.bind.v2.runtime.JAXBContextImpl;
    import com.sun.xml.bind.v2.runtime.reflect.Accessor;
    import java.lang.reflect.Method;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class HibernateJAXBAccessor<B, V> extends Accessor<B, V> {

       private Accessor<B, V> delegate;
            private final Method hibernateInitializationCheck;

            @SuppressWarnings("unchecked")
             protected HibernateJAXBAccessor(Accessor<B, V> delegate, Method hibernateInitializationCheck) {
                super(delegate.getValueType());
                if(delegate == null) {
                    throw new IllegalArgumentException("delegate must not be null");
                } else if(hibernateInitializationCheck == null) {
                    throw new IllegalArgumentException("hibernateInitializationCheck must not be null");
                }

                this.delegate = delegate;
                this.hibernateInitializationCheck = hibernateInitializationCheck;
       }

       @Override
       public Accessor<B, V> optimize(JAXBContextImpl context) {
          delegate = delegate.optimize(context);
          return this;
       }

       @Override
       public V get(B bean) throws AccessorException {
          return hideLazy(delegate.get(bean));
       }

       @Override
       public void set(B bean, V value) throws AccessorException {
          delegate.set(bean, value);
       }

              protected V hideLazy(V value) {
                try {
                    boolean isInitialized = (Boolean) hibernateInitializationCheck.invoke(null, new Object[]{value});
                    if(isInitialized) {
                        return value;
                    } else {
                        return null;
                    }
                } catch (Exception e) {
                    Logger logger = LoggerFactory.getLogger(HibernateJAXBAccessor.class);
                    logger.error("Failed to determine state of Hibernate object or collection, assuming " + value + " is initialized", e);
                    return null;
                }
       }
    }
  5. Now all that remains is creating an appropriate package-info.java in the package containing your XML annotated entity beans and you are set to go.
    Code:
    @XmlAccessorFactory(HibernateJAXBAccessorFactory.class)
    package example.beans.entities;

    import util.hibernate.HibernateJAXBAccessorFactory;
    import com.sun.xml.bind.XmlAccessorFactory;
  6. Say goodbye to the LazyInitializationException.

This solution is provided in the hope that it may be useful to others who have encountered the same problem. Constructive comments and improvements to this solution are of course much appreciated :).


Last edited by clemenb on Wed Aug 12, 2009 10:07 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Tue Aug 11, 2009 11:23 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
NULL does not mean "not loaded", it means "not there". You might want to use a different custom marker for "not loaded".

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Wed Aug 12, 2009 2:24 am 
Newbie

Joined: Wed Jul 29, 2009 11:01 pm
Posts: 7
I agree, using a custom marker to indicate a truncated element would be more elegant.
It should be relatively simple to make the appropriate changes.


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Wed Aug 12, 2009 10:06 pm 
Newbie

Joined: Wed Jul 29, 2009 11:01 pm
Posts: 7
Thinking about it a bit more, it should be fine to use NULL to represent a truncated lazily loaded collection as an empty collection would suffice to determine that a relationship has been loaded but only contains no entities. A similar argument can be applied to collections mapped through different mechanisms (e.g. serialization).

It would be great if there was a convention for Hibernate to handle this scenario because it is rather common in multi-tier environments. I am not aware of anything in this direction in the Hibernate documentation, but I am new to Hibernate, so I would appreciate any pointers.


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Fri Sep 25, 2009 10:44 am 
Newbie

Joined: Fri Sep 25, 2009 10:24 am
Posts: 2
Do you have any idea how to apply this hack on Jboss 5.0.0 GA !!!

I'm dealing with this problem during marshalling

org.hibernate.LazyInitializationException: failed to lazily initialize a collection
Code:
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:325)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:254)
at org.jboss.ws.core.jaxws.JAXBSerializer.serialize(JAXBSerializer.java:80)


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Sun Oct 04, 2009 8:45 pm 
Newbie

Joined: Wed Jul 29, 2009 11:01 pm
Posts: 7
The solution depends primarily on the newer JAXB and JAXWS stacks that are part of the latest stable Metro release. Therefore the solution should (in theory) work for you if you are using Metro 1.5 or later.

Unfortunately, I have no experience using the JBoss application server, but from what I understand the JBoss container can be configured to use different stacks including CFX and Metro. Though I do not know how one would switch and/or update the stack used by JBoss, I think that you might get some answers from the JBossWS Community.

Of course it would be great if the Hibernate community could come up with an elegant solution to this problem. I have not had this problem with either OpenJPA or EclipseLink.


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Fri Oct 09, 2009 11:48 am 
Newbie

Joined: Fri Sep 25, 2009 10:24 am
Posts: 2
Finally, i make it work with help of Jboss-WS project Manager.
It's somewhat simpler than your solution.
It's necessary only to put a file in META-INF/services/org.jboss.ws.core.jaxws.JAXBContextFactory containing the class that implement CustomizableJAXBContextFactory in my services layer.
It's same to define a HibernateAccessor & HibernateAccessorFactory in the Entity layer.

However, i prefer to serialize the id of my lazy object but i don't know how to do that because hideLazy is execute on the parent bean !
If anyone can help me to tune my HibernateAccessor,
Thanks for this cool thread
jo


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Tue Feb 09, 2010 12:58 am 
Newbie

Joined: Fri Feb 05, 2010 1:05 am
Posts: 12
Hi lespoches, I am interested to know more about the details to achieve it, could you provide more information may be?


Top
 Profile  
 
 Post subject: Re: Getting around LazyInitializationException in web services
PostPosted: Mon Sep 24, 2012 3:07 pm 
Newbie

Joined: Thu Sep 14, 2006 12:08 pm
Posts: 19
Thanks clemenb for what seems to be a nice solution to a long standing problem. Your post build nicely on Becky Searl's blog on this at:

https://blogs.oracle.com/searls/entry/jaxb_custom_accessor_for_marshalling

I am in the process of implementing this solution and wondering if there is a way to use a single JAXBContext that can be dynamically configured to do either of the following:

  • In HibernateJAXBAccessor do hiding of lazy collections or not, or
  • In the JAXBContext use the custom HibernateJAXBAccessorFactory or not


Alternatively I could create two separate JAXBContext instances one with HibernateJAXBAccessorFactory and one without but then I pay an overhead of creating the JAXBContext twice which is expensive.

Any suggestions?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 9 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.