JAX-WS + Hibernate + JAXB:如何在编组期间避免LazyInitializationException [英] JAX-WS + Hibernate + JAXB: How to avoid LazyInitializationException during marshalling

查看:219
本文介绍了JAX-WS + Hibernate + JAXB:如何在编组期间避免LazyInitializationException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个JAX-WS应用程序,它返回从Hibernate数据库后端(Oracle 10g或Oracle 11g)中获取的数据对象。我为此使用了javax.persistence.criteria.CriteriaQuery。它工作正常,除非对象具有依赖性,不应该为某些特定查询返回,例如:

  @Immutable 
@Entity
@Table(schema =some_schema,name =USER_VW)
public class User实现Serializable {

...

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name =PRFL_ID)
public Profile getProfile(){...}
$ b $ public void setProfile个人资料配置文件){...}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name =SM_OTH_TP_ID)
public SomeOtherType getSomeOtherType(){.. 。}

public void setSomeOtherType(SomeOtherType otherType){...}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name =SM_DPND_ID )
public SomeDependency getSomeDependency(){...}

public void setSomeDependency(SomeDependency dependency){...}

...
}

以下是我的标准查询:

  CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
CriteriaQuery< User> criteria = cb.createQuery(User.class);
criteria.distinct(true);
Root< User> user = criteria.from(User.class);
加入<用户,个人资料> profileJoin = user.join(profile,JoinType.INNER);
user.fetch(someOtherType,JoinType.LEFT);
criteria.select(user);
Predicate inPredicate = profileJoin.get(profileType)。in(types);
criteria.where(inPredicate);

注意:我不提取SomeDependency属性。我不希望它被返回。



这里是UserServiceResponse类的定义:



<$ p
@XmlRootElement(name =UserServiceResponse,namespace =...)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name =UserServiceResponse, namespace =...)
public class UserServiceResponse {

@XmlElementWrapper(name =users)
@XmlElement(name =user)
私人最终集合<用户>用户;

...

然后JAXB发现Hibernate Session已关闭。当它试图编组响应时,我得到以下异常:

 原因:org.hibernate.LazyInitializationException:无法初始化代理 - 没有会话
在org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164)[hibernate-core-4.2.7.SP1-redhat-3.jar:4.2.7.SP1-redhat-3 ]
在org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)[hibernate-core-4.2.7.SP1-redhat-3.jar:4.2.7.SP1-redhat-3]
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)[hibernate-core-4.2.7.SP1-redhat-3.jar:4.2.7.SP1-redhat-3 ]

at com.myproject.model.user.entity.SomeDependency _ $$ _ jvsteec_98.getCode(SomeDependency _ $$ _ jvsteec_98.java)
...
at com.sun。 xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:74)[jboss -jaxb-api_2.2_spec-1.0.4.Final-redhat-2.jar:1.0.4.Final-redhat-2]
at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:
at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:612)[cxf-rt-databinding-jaxb-2.7.7.redhat-1.jar:2.7.7.redhat-1] 240)[cxf-rt-databinding-jaxb-2.7.7.redhat-1.jar:2.7.7.redhat-1]
... 32 more

当marshaller尝试为SomeDependency类的code属性(它是HibernateProxy实例)获取值时,会发生这种情况。



现在我看到的解决方案是添加某种过滤器,它在编组期间检查是否该对象是HibernateProxy的实例。如果它是一个HibernateProxy实例,则过滤器处理它,如果不是,则保留其默认行为。



我该怎么做?使用XmlJavaTypeAdapter类?或者使用com.sun.xml.internal.bind.v2.runtime.reflect.Accessor?



如果有人能告诉我任何其他方式来解决我的问题,我会赞赏。注意:我在其他Web服务中的JAX-WS内部以及应用程序的其他模块中的JAX-WS之外重复使用相同的Hibernate代码和POJO,其中延迟加载是一个优势。



更新:

XmlJavaTypeAdapter,但它不适合我。我创建了一个新的适配器--HibernateProxyAdapter,它扩展了XmlJavaTypeAdapter。用户实体并不是我拥有的唯一POJO,事实上,它们有很多。为了确保适配器适用于所有这些适配器,我将它添加到了包级别。

  @XmlJavaTypeAdapters(
@XmlJavaTypeAdapter(value = HibernateProxyAdapter.class,type = HibernateProxy.class)

package com.myproject.model.entity;

以下是适配器:

  public class HibernateProxyAdapter extends XmlJavaTypeAdapter< Object,Object> {

public Object unmarshal(Object v)throws Exception {
return null; //不需要解组HibernateProxy实例

$ b $ public Object marshal(Object v)抛出Exception {
if(v!= null){
if (v HibernateProxy的实例){
LazyInitializer lazyInitializer =((HibernateProxy)v).getHibernateLazyInitializer();
if(lazyInitializer.isUninitialized()){
return null;
} else {
//现在不做任何事
}
} else if(v instanceof PersistentCollection){
if(((PersistentCollection)v).wasInitialized )){
//获得了一个初始化集合
} else {
return null;
}
}
}
return v;


$ / code $ / pre

现在我又遇到了另一个异常:

 导致:javax.xml.bind.JAXBException:类org.hibernate.collection.internal.PersistentSet或其任何超类是已知的在这方面。 
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:588)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer。 java:648)
... 57 more

据我所知,尝试编组初始化的hibernate集合,例如:org.hibernate.collection.internal.PersistentSet。我没有理由...... PersistentSet实现Set接口。我认为JAXB应该知道如何处理它。任何想法?

更新2:
我也尝试了使用Accessor类的第二个解决方案。这是我的访问器:

  public class JAXBHibernateAccessor extends Accessor {

private final Accessor accessor;

protected JAXBHibernateAccessor(Accessor accessor){
super(accessor.getValueType());
this.accessor = accessor;
}

@Override
public Object get(Object bean)抛出AccessorException {
return Hibernate.isInitialized(bean)? accessor.get(bean):null;
}

@Override
public void set(Object bean,Object value)throws AccessorException {
accessor.set(bean,value);


AccessorFactory ...

  public class JAXBHibernateAccessorFactory implements AccessorFactory {

private final AccessorFactory accessorFactory = AccessorFactoryImpl.getInstance();
$ b $ @Override
public Accessor createFieldAccessor(Class bean,Field field,boolean readOnly)throws JAXBException {
return new JAXBHibernateAccessor(accessorFactory.createFieldAccessor(bean,field,readOnly));

$ b @Override
public Accessor createPropertyAccessor(Class bean,Method getter,Method setter)throws JAXBException {
return new JAXBHibernateAccessor(accessorFactory.createPropertyAccessor(bean,getter ,setter));


package-info.java ...

  @XmlAccessorFactory(JAXBHibernateAccessorFactory.class)
package com.myproject.model.entity;

现在我需要在JAXB上下文中启用自定义的AccessorFactory / Accessor支持。我尝试将自定义的JAXBContextFactory添加到Web服务定义中,但它不起作用...
$ b $ pre $ $ $ c $ @WebService
@ UsesJAXBContext(JAXBHibernateContextFactory.class)
public interface UserService {
...
}

这里是我的contextFactory

  public class JAXBHibernateContextFactory implements JAXBContextFactory {

@Override
public JAXBRIContext createJAXBContext(@NotNull SEIModel seiModel,@NotNull List< Class>类,
@NotNull List< TypeReference> typeReferences)throws JAXBException {
return ContextFactory.createContext(classes.toArray(new Class [classes.size()]),typeReferences,
null,null,false,new RuntimeInlineAnnotationReader(),true,false,false);
}
}

我不知道为什么,但是创建JAXBContext方法从不调用。看起来像@UsesJAXBContext注释什么都不做......



有谁知道如何使它工作?
或者如何在JAX-WS中将com.sun.xml.bind.XmlAccessorFactoryJAXBContext属性设置为true?


顺便说一句,我忘了提及它,我将它部署到JBoss EAP 6.2中。

解决方案 div>

我认为注释 @XmlTransient 是为了这个。将它添加到你的属性 someDependency 以使JAXB忽略这个字段。

  @XmlTransient 
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name =SM_DPND_ID)
Public SomeDependency getSomeDependency(){...}

更新以下评论:如果您必须使用适配器选项,我的猜测是您必须:


  1. 为实体用户创建一个新适配器,用于扩展XmlAdapter

  2. 每个属性的编组,并使用方法 Hibernate.isInitialized(yourObject.getSomeDependency())来测试关联是否在调用编组之前被加载过。

  3. 用适当的属性添加 @XmlJavaTypeAdapter 来声明它实体用户

也许可以通过直接为属性 someDependency 创建一个适配器来完成,但您可能会希望在JAXB尝试将该属性传递给适配器时发生LazyInitializationException。


I have a JAX-WS application, which returns data objects, that were fetched from a Hibernate Database backend (Oracle 10g or Oracle 11g). I use javax.persistence.criteria.CriteriaQuery for that. It works fine, unless the object has dependencies, which should not be returned for some specific queries, e.g.:

@Immutable
@Entity
@Table(schema = "some_schema", name = "USER_VW")
public class User implements Serializable {

  ...

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "PRFL_ID")
  public Profile getProfile() {...}

  public void setProfile(Profile profile) {...}

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "SM_OTH_TP_ID")
  public SomeOtherType getSomeOtherType() {...}

  public void setSomeOtherType(SomeOtherType otherType) {...}

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "SM_DPND_ID)
  public SomeDependency getSomeDependency() {...}

  public void setSomeDependency(SomeDependency dependency) {...}

...
}

Here is my criteria query:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteria = cb.createQuery(User.class);
criteria.distinct(true);
Root<User> user = criteria.from(User.class);
Join<User, Profile> profileJoin = user.join("profile", JoinType.INNER);
user.fetch("someOtherType", JoinType.LEFT);
criteria.select(user);
Predicate inPredicate = profileJoin.get("profileType").in(types);
criteria.where(inPredicate);

NOTE: I don't fetch SomeDependency property. I don't want it to be returned.

And here is the definition of the UserServiceResponse class:

@XmlRootElement(name = "UserServiceResponse", namespace = "...")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "UserServiceResponse", namespace = "...")
public class UserServiceResponse {

@XmlElementWrapper(name = "users")
@XmlElement(name = "user")
private final Collection<User> users;

...

Then JAXB finds that the Hibernate Session has been closed. When it tries to marshall the response I get the following exception:

Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164) [hibernate-core-4.2.7.SP1-redhat-3.jar:4.2.7.SP1-redhat-3]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285) [hibernate-core-4.2.7.SP1-redhat-3.jar:4.2.7.SP1-redhat-3]
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185) [hibernate-core-4.2.7.SP1-redhat-3.jar:4.2.7.SP1-redhat-3]

    at com.myproject.model.user.entity.SomeDependency_$$_jvsteec_98.getCode(SomeDependency_$$_jvsteec_98.java)
...
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:74) [jboss-jaxb-api_2.2_spec-1.0.4.Final-redhat-2.jar:1.0.4.Final-redhat-2]
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:612) [cxf-rt-databinding-jaxb-2.7.7.redhat-1.jar:2.7.7.redhat-1]
    at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:240) [cxf-rt-databinding-jaxb-2.7.7.redhat-1.jar:2.7.7.redhat-1]
    ... 32 more

It happens when marshaller tries to get value for the "code" property of SomeDependency class which is a HibernateProxy instance.

The solution that I see for now is to add some kind of "filter" which checks during marshalling if the object is instance of HibernateProxy or not. If it is a HibernateProxy instance the filter handle it, if not, just leaves its default behavior.

How can I do that? Using a XmlJavaTypeAdapter class? Or using com.sun.xml.internal.bind.v2.runtime.reflect.Accessor?

If anyone can tell me any other way to solve my problem I would be appreciated.

NOTE: I'm reusing the same Hibernate code and POJOs, inside JAX-WS in other web-services and outside JAX-WS in other modules of the application, where lazy loading is an advantage.

UPDATE:

I've tried using XmlJavaTypeAdapter but it didn't work for me. I created a new adapter - HibernateProxyAdapter which extends XmlJavaTypeAdapter. User entity is not the only POJO that I have, in fact, there are a lot of them. To ensure that the adapter is applied to all of them I added it on the package level.

@XmlJavaTypeAdapters(
    @XmlJavaTypeAdapter(value=HibernateProxyAdapter.class, type=HibernateProxy.class)
)
package com.myproject.model.entity;

Here is the adapter:

public class HibernateProxyAdapter extends XmlJavaTypeAdapter<Object, Object> {

    public Object unmarshal(Object v) throws Exception {
        return null; // there is no need to unmarshall HibernateProxy instances
    }

    public Object marshal(Object v) throws Exception {
        if (v != null) {
            if ( v instanceof HibernateProxy ) {
                LazyInitializer lazyInitializer = ((HibernateProxy) v ).getHibernateLazyInitializer();
                if (lazyInitializer.isUninitialized()) {
                    return null;
                } else {
                    // do nothing for now
                }
            } else if ( v instanceof PersistentCollection ) {
                if(((PersistentCollection) v).wasInitialized()) {
                    // got an initialized collection
                } else {
                    return null;
                }
            }
        }
        return v;
    }
}

Now i'm getting another exception:

Caused by: javax.xml.bind.JAXBException: class org.hibernate.collection.internal.PersistentSet nor any of its super class is known to this context.
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:588)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:648)
    ... 57 more 

As I understand, this happens when it tries to marshal an initialized hibernate collection, e.g.: org.hibernate.collection.internal.PersistentSet. I don't get the reason... PersistentSet implements Set interface. I thought JAXB should know how to handle it. Any ideas?

UPDATE 2: I have also tried the second solution using Accessor class. Here is my accessor:

public class JAXBHibernateAccessor extends Accessor {

    private final Accessor accessor;

    protected JAXBHibernateAccessor(Accessor accessor) {
        super(accessor.getValueType());
        this.accessor = accessor;
    }

    @Override
    public Object get(Object bean) throws AccessorException {
        return Hibernate.isInitialized(bean) ? accessor.get(bean) : null;
    }

    @Override
    public void set(Object bean, Object value) throws AccessorException {
        accessor.set(bean, value);
    }
}

AccessorFactory...

public class JAXBHibernateAccessorFactory implements AccessorFactory {

    private final AccessorFactory accessorFactory = AccessorFactoryImpl.getInstance();

    @Override
    public Accessor createFieldAccessor(Class bean, Field field, boolean readOnly) throws JAXBException {
        return new JAXBHibernateAccessor(accessorFactory.createFieldAccessor(bean, field, readOnly));
    }

   @Override
   public Accessor createPropertyAccessor(Class bean, Method getter, Method setter) throws JAXBException {
        return new JAXBHibernateAccessor(accessorFactory.createPropertyAccessor(bean, getter, setter));
   }
}

package-info.java ...

@XmlAccessorFactory(JAXBHibernateAccessorFactory.class)
package com.myproject.model.entity;

Now I need to enable custom AccessorFactory/Accessor support on the JAXB context. I tried adding custom JAXBContextFactory to the web service definition but it didn't work...

@WebService
@UsesJAXBContext(JAXBHibernateContextFactory.class)
public interface UserService {
...
}

and here is my contextFactory

public class JAXBHibernateContextFactory implements JAXBContextFactory {

    @Override
    public JAXBRIContext createJAXBContext(@NotNull SEIModel seiModel, @NotNull List<Class> classes,
                                       @NotNull List<TypeReference> typeReferences) throws JAXBException {
        return ContextFactory.createContext(classes.toArray(new Class[classes.size()]), typeReferences,
            null, null, false, new RuntimeInlineAnnotationReader(), true, false, false);
    }
}

I don't know why but createJAXBContext method is never invoked. Looks like @UsesJAXBContext annotation does nothing...

Does anyone know how to make it work? Or how can I set the "com.sun.xml.bind.XmlAccessorFactory" JAXBContext property to true inside JAX-WS?

BTW, I forgot to mention, I deploy it to JBoss EAP 6.2.

解决方案

I think annotation @XmlTransient is meant for this. Add it to your property someDependency to make JAXB ignore this field.

@XmlTransient
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "SM_DPND_ID)
public SomeDependency getSomeDependency() {...}

Update following comments: If you must go for the adapter option, my guess is you have to :

  1. Create a new adapter for entity User extending XmlAdapter
  2. In your adapter call the marshaller for each of your property and use method Hibernate.isInitialized(yourObject.getSomeDependency()) to test if the association has been loaded before calling the marshaller or not.
  3. declare it by adding @XmlJavaTypeAdapter with the proper attribute to your entity User

Maybe it can be done by creating directly an adapter for the property someDependency but you might expect a LazyInitializationException when JAXB will try to pass the property to the adapter.

这篇关于JAX-WS + Hibernate + JAXB:如何在编组期间避免LazyInitializationException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆