nutzam / nutz

Nutz -- Web Framework(Mvc/Ioc/Aop/Dao/Json) for ALL Java developer
https://nutzam.com
Apache License 2.0
2.53k stars 942 forks source link

关于json 和 mapl的问题 #834

Closed sphsyv closed 9 years ago

sphsyv commented 9 years ago

在我的项目中使用了hibernate。目前发现的问题是: 在action中,代码如下 List l = service.find(filter);//查询所有车辆对象(车辆对象里面有设备类型对象) Object j = Mapl.toMaplist(l);//转成mapl格式 Object json = Mapl.excludeFilter(j, Arrays.asList("deviceType"));//过滤设备类型,不进行json解析

现在的情况是,第2句话,转成mapl格式就出错了,下面的过滤根本就没走到 错误描述: java.lang.RuntimeException: Fail to invoke getter org.hibernate.proxy.AbstractLazyInitializer.'isReadOnly()' because [org.hibernate.TransientObjectException: Proxy is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session.]: Proxy is detached (i.e, session is null). The read-only/modifiable setting is only accessible when the proxy is associated with an open session. at org.nutz.lang.Lang.makeThrow(Lang.java:99) at org.nutz.lang.eject.EjectByGetter.eject(EjectByGetter.java:26) at org.nutz.json.entity.JsonEntityField.getValue(JsonEntityField.java:141) at org.nutz.mapl.impl.compile.ObjCompileImpl.pojo2Json(ObjCompileImpl.java:123) at org.nutz.mapl.impl.compile.ObjCompileImpl.parse(ObjCompileImpl.java:82) at org.nutz.mapl.impl.compile.ObjCompileImpl.pojo2Json(ObjCompileImpl.java:128) at org.nutz.mapl.impl.compile.ObjCompileImpl.parse(ObjCompileImpl.java:82) at org.nutz.mapl.impl.compile.ObjCompileImpl.pojo2Json(ObjCompileImpl.java:128) at org.nutz.mapl.impl.compile.ObjCompileImpl.parse(ObjCompileImpl.java:82) at org.nutz.mapl.impl.compile.ObjCompileImpl.pojo2Json(ObjCompileImpl.java:128) at org.nutz.mapl.impl.compile.ObjCompileImpl.parse(ObjCompileImpl.java:82) at org.nutz.mapl.impl.compile.ObjCompileImpl.coll2Json(ObjCompileImpl.java:157) at org.nutz.mapl.impl.compile.ObjCompileImpl.parse(ObjCompileImpl.java:70) at org.nutz.mapl.Mapl.toMaplist(Mapl.java:141) at com.autolink.controller.business.IovBizCarInfoController.findJoinRtLocation(IovBizCarInfoController.java:163)

我的建议是:在转换mapl格式的时候,就将要排除的属性,或者要解析的属性已参数的方式传递进去,避免hibernate懒加载出错。 例如 Object j = Mapl.toMaplist(l,includeFilter); Object j = Mapl.toMaplist(l,excludeFilter); 就相当于将 Object j = Mapl.toMaplist(l); Object json = Mapl.excludeFilter(j, Arrays.asList("deviceType")); 合并成 Object j = Mapl.toMaplistByExcludeFilter(l,Arrays.asList("deviceType")); 在转换mapl的时候,就告诉mapl要过滤那些属性,或者包含那些属性,避免出错??

wendal commented 9 years ago

@juqkai

juqkai commented 9 years ago

这与JSON将对象转换成MAPL一样的。

你可以考虑使用 JsonField 注解

sphsyv commented 9 years ago

@juqkai 不能考虑JsonField注释,如果加了注释就永远都不会解析了, 我的hibernate查询有时候会预先抓取车辆信息里面的deviceType类的信息,这时候我是要解析这个属性的;有的查询页面就不需要这个信息,那么我就没有使用join fetch关键字,这个deviceType信息就没有查询,这个时候解析json就会出错。 根本解决问题的方式,就是在转换mapl的时候,直接传递需要忽略的属性,或者需要解析的属性,程序是不知道我什么时候需要什么属性,但是开发人员知道。所以某些方法需要转换这个属性,某些方法又不需要,不能写jsonField注释的,太死了。 我看了一下源码,有一句会将属性put到一个变量中,在这之前判断一下,是不是忽略属性,如果是就不放到变量中,这样就不会解析了估计。 还是不想直接修改源码,请大神增加一个方法吧??

sphsyv commented 9 years ago

@juqkai 教教我怎么动态JsonField呗,或者mapl在构造的时候怎样添加排除属性呢?

wendal commented 9 years ago

有个变通的方法,先转json, 因为toJson的时候可以通过JsonFormat忽略一些属性,然后再转回对象

sphsyv commented 9 years ago

我自己写了个类解决了。 类内容如下:

package sy.util.base;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.nutz.json.Json;
import org.nutz.lang.Mirror;
import org.springframework.beans.BeanUtils;

/**
 * 为了解决hibernate懒加载序列化json出错,再次封装一层
 * 
 * @author 孙宇
 *
 */
public class JsonUtil {

    /**
     * 对象转json字符串
     * 
     * @param obj
     * @param excludePath
     *            要排除的属性路由(要符合Mapl的path规则)
     * @return
     */
    public static String toJsonByExclude(Object obj, String[] excludePath) {
        return Json.toJson(excludeAttributes(obj, excludePath));
    }

    /**
     * 排除不需要的属性
     * 
     * @param obj
     *            原对象
     * @param excludePath
     *            不需要的属性路径
     * @return
     */
    public static Object excludeAttributes(Object obj, String[] excludePath) {
        Object o = null;
        if (obj instanceof Collection) {
            List<Object> oList = new ArrayList<Object>();
            for (Object t : (List<Object>) obj) {
                Object nt = Mirror.me(t).born();
                BeanUtils.copyProperties(t, nt);
                oList.add(nt);
            }
            o = oList;
        } else {
            try {
                o = obj.getClass().newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            BeanUtils.copyProperties(obj, o);
        }
        for (String path : excludePath) {
            setAttributeNull(o, path);
        }
        return o;
    }

    private static void setAttributeNull(Object o, String path) {
        Mirror<?> mirror = Mirror.me(o);
        excludeHibernateProxy(o, mirror);
        if (path.indexOf(".") > 0) {
            String[] paths = path.split("\\.");
            String newPath = "";
            for (int i = 1; i < paths.length; i++) {
                if (i > 1) {
                    newPath += ".";
                }
                newPath += paths[i];
            }
            if (paths[0].indexOf("[") > -1) {// 第一个需要过滤的路由是集合属性
                Collection<?> collection;
                if (paths[0].indexOf("[") == 0) {// [].attribute
                    collection = (Collection<?>) o;
                } else {// attribute[].attribute
                    collection = (Collection<?>) mirror.getValue(o, paths[0].substring(0, paths[0].indexOf("[")));
                }
                if (collection != null && collection.size() > 0) {
                    Iterator<?> iterator = collection.iterator();
                    while (iterator != null && iterator.hasNext()) {
                        setAttributeNull(iterator.next(), newPath);
                    }
                }
            } else {// attribute.attribute
                setAttributeNull(mirror.getValue(o, paths[0]), newPath);
            }
        } else {// attribute
            mirror.setValue(o, path, null);
        }
    }

    private static void excludeHibernateProxy(Object o, Mirror<?> mirror) {
        for (Field field : mirror.getFields()) {
            Object value = mirror.getValue(o, field);
            if (value instanceof HibernateProxy) {// hibernate代理对象
                LazyInitializer initializer = ((HibernateProxy) value).getHibernateLazyInitializer();
                if (initializer.isUninitialized()) {
                    mirror.setValue(o, field, null);
                }
            } else if (value instanceof PersistentCollection) {// 实体关联集合
                PersistentCollection collection = (PersistentCollection) value;
                if (!collection.wasInitialized()) {
                    mirror.setValue(o, field, null);
                } else if (collection.getValue() == null) {
                    mirror.setValue(o, field, null);
                }
            }
        }
    }
}

调用方式:

IovBizCarInfo car = service.get(filter);
JsonResult j = new JsonResult();
j.setObj(JsonUtil.excludeAttributes(car, new String[] { "iovBizDevices[].iovBizCarInfo" }));
j.setSuccess(true);
return j;

这样就排除了car对象里的iovBizDevices集合里的iovBizCarinfo属性,避免了循环引用的问题。

wendal commented 9 years ago