我有一个JAX-WS应用程序,该应用程序返回从Hibernate数据库后端(Oracle 10g或Oracle 11g)获取的数据对象。我为此使用javax.persistence.criteria.CriteriaQuery。除非对象具有依赖项,否则它运行良好,除非该依赖项对于某些特定查询不应返回,例如:

@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) {...}

...
}


这是我的条件查询:

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


注意:我不获取SomeDependency属性。我不希望它退回。

这是UserServiceResponse类的定义:

@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;

...


然后,JAXB发现休眠会话已关闭。当它尝试整理响应时,出现以下异常:

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


当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; // 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;
    }
}


现在我又遇到一个例外:

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


据我了解,这会在尝试封送已初始化的休眠集合时发生,例如: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) 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;


现在,我需要在JAXB上下文中启用自定义AccessorFactory / Accessor支持。我尝试将自定义JAXBContextFactory添加到Web服务定义中,但是没有用...

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


这是我的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);
    }
}


我不知道为什么,但是从未调用过createJAXBContext方法。看起来@UsesJAXBContext注释不起作用...

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

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

最佳答案

我认为注释@XmlTransient是为此目的而设计的。将其添加到属性someDependency中,以使JAXB忽略此字段。

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


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


为实体用户扩展XmlAdapter创建新适配器
在适配器中,为每个属性调用编组器,并在调用编组器之前使用方法Hibernate.isInitialized(yourObject.getSomeDependency())测试关联是否已加载。
通过将具有适当属性的@XmlJavaTypeAdapter添加到您的实体用户来声明它


也许可以通过直接为属性someDependency创建适配器来完成,但是当JAXB尝试将属性传递给适配器时,您可能会期望出现LazyInitializationException。

07-24 19:13