尝试解决JPA懒加载异常问题(wildfly)

来源:互联网 发布:掌炙世家淘宝网有买吗 编辑:程序博客网 时间:2024/04/30 11:29

之前在JPA/Hibernate+JSF+CDI的项目中老是遇到懒加载异常问题,解决办法要不就是修改查询语句,要不就是改为EAGER。

最近百度了很久之后终于想到了一个还不错的办法,使用JPA的拦截器,不过这个解决方案需要使用Hibernate实现的时候才可用。


先看一下org.hibernate.collection.internal.AbstractPersistentCollection的代码,抛出懒加载异常的原因在这里:

               if ( session == null ) {if ( allowLoadOutsideTransaction ) {session = openTemporarySessionForLoading();isTempSession = true;}else {throwLazyInitializationException( "could not initialize proxy - no Session" );}}else if ( !session.isOpen() ) {if ( allowLoadOutsideTransaction ) {originalSession = session;session = openTemporarySessionForLoading();isTempSession = true;}else {throwLazyInitializationException( "could not initialize proxy - the owning Session was closed" );}}else if ( !session.isConnected() ) {if ( allowLoadOutsideTransaction ) {originalSession = session;session = openTemporarySessionForLoading();isTempSession = true;}else {throwLazyInitializationException( "could not initialize proxy - the owning Session is disconnected" );}}


所以,只需要在集合代理对象初始化之前,将Session对象传递给集合代理对象即可,所以,写一个JPA拦截器,使用@PostLoad注解拦截方法

public class LazyInitilazeCollectionListener {private static final Map<Class<?>,Set<Field>> PROXY_MAP= new HashMap<>();/** * 1. 检查PROXY_MAP的键中是否包含o的Class对象,若包含,则取出相应的Set集合,即需要懒加载的成员 * 2. 若未包含o的Class对象,则检查o的Class对象的成员变量,看是否有成员变量加上了@OnetoMany,@ManyToMany注解,并且fetch值为FetchType.LAZY *    若找到符合条件的变量(即懒加载对象),则将它们放入到Set中,然后放入PROXY_MAP中。 * 3. 若set为空,则表示没有需要进行懒加载的对象 * 4. 若set不为空,则遍历set,取出o的相应的变量的值(fieldValue),若该值(fieldValue)类型属于AbstractPersistentCollection,则说明此集合是懒加载的集合,需要对其进行再此代理处理。 * 5. 使用值(fieldValue)生成代理对象,将代理对象设置为o的相应的field的值,问题搞定。 * 在此后若是需要使用该懒加载集合的值时,调用该集合的方法即会调用代理对象的相应方法;代理方法会先检查该集合是否已经完全初始化,若是,则直接调用,若不是,则给其创造加载所需的环境(Session) * @param o */@PostLoadpublic void proxyCollection(Object o){Class<?> clazz = o.getClass();Set<Field> set = null;if(PROXY_MAP.containsKey(clazz)){set = PROXY_MAP.get(clazz);}else{//检查所有成员变量,是否有onetomany和manytomany注解并且申明为fetchType.LAZYfor(Field field : clazz.getDeclaredFields()){if(field.isAnnotationPresent(OneToMany.class)){if(field.getAnnotation(OneToMany.class).fetch().equals(FetchType.LAZY)){if(set == null){set = new HashSet<>();}set.add(field);}}else if(field.isAnnotationPresent(ManyToMany.class)){if(field.getAnnotation(ManyToMany.class).fetch().equals(FetchType.LAZY)){if(set == null){set = new HashSet<>();}set.add(field);}}}synchronized (PROXY_MAP) {PROXY_MAP.put(clazz, set);}}//若是o中有需要代理的对象,此时set已经不为空了if(set == null){return;}for(Field field : set){field.setAccessible(true);try {Object fieldValue= field.get(o);if(fieldValue != null){if(fieldValue instanceof AbstractPersistentCollection){//生成代理对象Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{field.getType()},new Handler(fieldValue));field.set(o, obj);}}} catch (IllegalArgumentException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}/** * 到服务器上查找EntityManager对象,需要在persistence.xml文件中先配置<br/> * <!-- 将EntityManagerFactory和EntityManager绑定到JNDI上 --><br/>     *    <property name="jboss.entity.manager.factory.jndi.name" value="java:jboss/myEntityManagerFactory" ><br/>     *   <br/>     *   <property name="jboss.entity.manager.jndi.name" value="java:/myEntityManager"><br/> * @return * @throws NamingException */private static EntityManager lookupEntityManager() throws NamingException{Context ctx = new InitialContext();return (EntityManager)ctx.lookup("java:/myEntityManager");}class Handler implements InvocationHandler{private Object obj;public Handler(Object obj) {this.obj = obj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {AbstractPersistentCollection collection = (AbstractPersistentCollection)obj;if(!collection.wasInitialized()){EntityManager em = lookupEntityManager();SessionImplementor session = em.unwrap(SessionImplementor.class);try {collection.setCurrentSession(session);} catch (Exception e) {//若是已关联了Session则会抛出异常,这里的异常只会发生在对象所处的Session还未关闭的情况下,这时是可以加载数据的,所以不需要任何处理}}return  method.invoke(obj, args);}}}


我自己测试的时候是通过的,环境是Wildfly 10,服务器自带的Hibernate实现。

0 0
原创粉丝点击