[英]JAX-WS + Hibernate + JAXB: How to avoid LazyInitializationException during marshalling
I have a JAX-WS application, which returns data objects, that were fetched from a Hibernate Database backend (Oracle 10g or Oracle 11g). 我有一个JAX-WS应用程序,该应用程序返回从Hibernate数据库后端(Oracle 10g或Oracle 11g)获取的数据对象。 I use javax.persistence.criteria.CriteriaQuery for that. 我为此使用javax.persistence.criteria.CriteriaQuery。 It works fine, unless the object has dependencies, which should not be returned for some specific queries, eg: 除非对象具有依赖关系,否则它运行良好,除非该依赖关系对于某些特定查询不应返回,例如:
@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. 注意:我不获取SomeDependency属性。 I don't want it to be returned. 我不希望它退回。
And here is the definition of the UserServiceResponse class: 这是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;
...
Then JAXB finds that the Hibernate Session has been closed. 然后,JAXB发现休眠会话已关闭。 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. 当marshaller试图获取SomeDependency类的“ code”属性的值时,就会发生这种情况,该类是HibernateProxy实例。
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. 我现在看到的解决方案是添加某种“过滤器”,以在编组期间检查对象是否为HibernateProxy实例。 If it is a HibernateProxy instance the filter handle it, if not, just leaves its default behavior. 如果它是一个HibernateProxy实例,则过滤器将处理它,否则,将保留其默认行为。
How can I do that? 我怎样才能做到这一点? Using a XmlJavaTypeAdapter class? 使用XmlJavaTypeAdapter类? Or using com.sun.xml.internal.bind.v2.runtime.reflect.Accessor? 还是使用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. 注意:我正在其他Web服务的JAX-WS内部和应用程序其他模块的JAX-WS内部重用相同的Hibernate代码和POJO,在这种情况下,延迟加载是一个优势。
UPDATE: 更新:
I've tried using XmlJavaTypeAdapter but it didn't work for me. 我尝试使用XmlJavaTypeAdapter,但对我而言不起作用。 I created a new adapter - HibernateProxyAdapter which extends XmlJavaTypeAdapter. 我创建了一个新的适配器-HibernateProxyAdapter,它扩展了XmlJavaTypeAdapter。 User entity is not the only POJO that I have, in fact, there are a lot of them. 用户实体不是我拥有的唯一POJO,实际上,它们很多。 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, eg: org.hibernate.collection.internal.PersistentSet. 据我了解,这发生在尝试封送已初始化的休眠集合时,例如:org.hibernate.collection.internal.PersistentSet。 I don't get the reason... PersistentSet implements Set interface. 我不知道原因... PersistentSet实现Set接口。 I thought JAXB should know how to handle it. 我以为JAXB应该知道如何处理它。 Any ideas? 有任何想法吗?
UPDATE 2: I have also tried the second solution using Accessor class. 更新2:我也尝试使用Accessor类的第二个解决方案。 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... 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 ... package-info.java ...
@XmlAccessorFactory(JAXBHibernateAccessorFactory.class)
package com.myproject.model.entity;
Now I need to enable custom AccessorFactory/Accessor support on the JAXB context. 现在,我需要在JAXB上下文中启用自定义AccessorFactory / Accessor支持。 I tried adding custom JAXBContextFactory to the web service definition but it didn't work... 我尝试将自定义JAXBContextFactory添加到Web服务定义中,但是没有用...
@WebService
@UsesJAXBContext(JAXBHibernateContextFactory.class)
public interface UserService {
...
}
and here is my contextFactory 这是我的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. 我不知道为什么,但是从未调用过createJAXBContext方法。 Looks like @UsesJAXBContext annotation does nothing... 看起来@UsesJAXBContext注释不起作用...
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? 或者如何在JAX-WS中将“ com.sun.xml.bind.XmlAccessorFactory” JAXBContext属性设置为true?
BTW, I forgot to mention, I deploy it to JBoss EAP 6.2. 顺便说一句,我忘了提,我将其部署到JBoss EAP 6.2。
I think annotation @XmlTransient
is meant for this. 我认为注释@XmlTransient
就是为此目的而设计的。 Add it to your property someDependency
to make JAXB ignore this field. 将其添加到属性someDependency
以使JAXB忽略此字段。
@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 : 更新以下评论:如果您必须使用适配器选项,我想您必须:
Hibernate.isInitialized(yourObject.getSomeDependency())
to test if the association has been loaded before calling the marshaller or not. 在适配器中,为每个属性调用编组器,并使用方法Hibernate.isInitialized(yourObject.getSomeDependency())
来测试是否在调用编组器之前加载了关联。 @XmlJavaTypeAdapter
with the proper attribute to your entity User 通过将具有适当属性的@XmlJavaTypeAdapter
添加到您的实体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. 也许可以通过直接为属性someDependency
创建适配器来完成,但是当JAXB尝试将属性传递给适配器时,您可能会期望出现LazyInitializationException。
There are 2 solutions to that, which can be combined in order to have finest control of marshalling. 有2种解决方案,可以将它们组合在一起,以对编组进行最好的控制。
public abstract ValueT get(BeanT bean) throws AccessorException;
and if the pojo is not initialized, return null : 如果未初始化pojo,则返回null:
if (!Hibernate.isInitialized(valueT)) { return null; }
Note there is an annoying optimize method : 注意有一个烦人的优化方法:
public Accessor<BeanT,ValueT> optimize(@Nullable JAXBContextImpl context) { return this; }
which can replace your custom Accessor, depending on the Accessor you override (see FieldReflection). 可以替换您的自定义Accessor,具体取决于您覆盖的Accessor(请参见FieldReflection)。
That is one of the solution described in question, but I think you forgot the following initialization of JAXBContext : 那是有问题的解决方案之一,但是我认为您忘记了JAXBContext的以下初始化:
HashMap<String, Object> props = new HashMap<String, Object>(); props.put(JAXBRIContext.XMLACCESSORFACTORY_SUPPORT, true); JAXBContext jaxbContext = JAXBContext.newInstance(new Class[] { clazz }, props);
HashMap<String, Object> props = new HashMap<String, Object>(); props.put(JAXBRIContext.ANNOTATION_READER, new CustomAnnotationReader()); JAXBContext jaxbContext = JAXBContext.newInstance(new Class[] { clazz }, props);
RuntimeInlineAnnotationReader
is final and cannot be overriden... so you will have to copy the code. RuntimeInlineAnnotationReader
是最终的,无法覆盖...因此您必须复制代码。
I personnaly combined those two approches in order to modify marshalling depending on the context and content of the objects. 我个人将这两个方法组合在一起,以便根据对象的上下文和内容来修改编组。
Simply replaced the implementation of Accessor$FieldReflection using Javaassist to come arround this one: 只需使用Javaassist替换Accessor $ FieldReflection的实现来解决这个问题:
static {
ClassPool pool = ClassPool.getDefault();
try {
CtClass cc = pool.get("com.sun.xml.bind.v2.runtime.reflect.Accessor$FieldReflection");
CtMethod method = cc.getMethod("get", "(Ljava/lang/Object;)Ljava/lang/Object;");
method.insertBefore("if (bean instanceof org.hibernate.proxy.HibernateProxy) {\n"
+ "bean = ((org.hibernate.proxy.HibernateProxy)bean).getHibernateLazyInitializer().getImplementation();\n"
+ "}");
cc.toClass();
}
catch (Throwable t) {
t.printStackTrace();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.