杰克逊2:通过JPA协会解析大量课程,是否有可能限制深度?

时间:2013-08-09 08:52:36

标签: performance jackson limit json

我一直在尝试使用Jackson 2.2.2作为Json(de)序列化程序在一个可用的Spring MVC 3.1.2应用程序之上开发一个RESTful层。问题是,它过于深入到引用中,并且对于一个过去最多需要1秒加载的页面,现在只需要服务器端22秒。

问题是杰克逊正在经历每一个关联,并且需要永远加载一切并解析它。

我知道@JsonIgnore但是,我宁愿有深度限制,因为如果我们让我们说:

My amazing explanation

如果我将@JsonIgnore放在B和C之间的链接上,那么在序列化A时我会很好但是如果我需要序列化B并希望C序列化呢?我能想到的最好的方法是给序列化器一个深度级别的限制。比方说,深度限制= 1然后在序列化A时它不会序列化C但在序列化时仍会序列化B.有没有办法做这样的事情?

我见过@JsonView注释,但它的目的是包含属性,而不是排除它们。它可以用来排除某些属性,但它只与一个级别相关。

我是否需要编写自己的序列化程序?如果我编写自己的序列化程序,有没有办法实现这样的事情?

我不认为这是不可解决的,但我找不到任何可以帮助我的案件......

谢谢!

3 个答案:

答案 0 :(得分:1)

如果您使用Hibernate,Jackson Hibernate模块(https://github.com/FasterXML/jackson-datatype-hibernate)支持禁用“延迟”属性(延迟提取的集合)的加载。这将允许您限制对象图的大部分被访问和序列化。

除此之外杰克逊没有基于深度的限制;和核心包对特定的域/数据模块一无所知。可以编写扩展模块来改变特定域的行为;这就是Hibernate模块的工作原理。也许这可用于通用的JPA特定功能。

答案 1 :(得分:1)

前一段时间我找到了一个解决方案,并没有真正的时间和精力来发布它,但是我们走了:

注意:这个解决方案并不是每个人都梦寐以求的完美的双线解决方案,但是,它可以按照我的意愿工作。 NB2:此解决方案需要XOM library才能读取XML文件。

首先,它是如何工作的: 我创建了一组XML文件,每个文件代表一个由jackson序列化的实体(或需要自定义序列化)。

以下是此类文件的示例 - Assignment.xml

<?xml version="1.0" encoding="UTF-8"?>
<Assignment>
    <endDate/>
    <id/>
    <missionType>
        <id/>
        <name/>
    </missionType>
    <numberOfDaysPerWeek/>
    <project>
        <id/>
        <name/>
    </project>
    <resource>
        <id/>
        <firstName/>
        <lastName/>
        <fte/>
    </resource>
    <role>
        <id/>
        <name/>
    </role>
    <startDate/>
    <workLocation/>
</Assignment>

这里我们有Assignment类,每个属性表示为XML元素。请注意,任何未表示的元素都不会使用我稍后将要显示的转换器进行序列化 具有子元素的元素是Assignment实例引用的对象。这些子元素将与其余元素一起序列化 例如,Assignment实例具有名为“role”的属性,其类型为Role,我们希望role的{​​{1}}和id序列化。

这主要是你如何选择什么和什么不序列化,从而限制序列化的深度。

现在到name班级:

ObjectConverter

如您所见,serialize,serializeList和serializeMap方法需要一个import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import nu.xom.Builder; import nu.xom.Document; import nu.xom.Element; import nu.xom.Elements; import nu.xom.ParsingException; import nu.xom.ValidityException; import model.ModelObject; /** * This helper class will convert DOM objects (those implementing ModelObject) into a data structure built on the fly. * Typically, a simple object will be converted into a Map<String, Object> where keys will be the object's field names and values be corresponding field values. * The convertion uses an XML configuration file that is located in webservices/jackson/converters. * * @author mdekeys * */ public class ObjectConverter { private static Logger logger = Logger.getLogger(ObjectConverter.class); private static final String CONFIGURATION_DIR = "../standalone/deployments/resources-management.war/WEB-INF/classes/com/steria/rm/webservices/jackson/converters/"; /** * * @param obj The object to convert * @param element An XML element (based on XOM library) which represents the object structure. * @return Returns the object converted in a corresponding data structure */ @SuppressWarnings("unchecked") private static Object serialize(ModelObject obj, Element element) { //initialize return value Map<String, Object> map = new HashMap<String, Object>(); //find all child elements Elements children = element.getChildElements(); //loop through children elements for (int i = 0; i < children.size(); i++) { //get the current child Element child = children.get(i); //child's qualifiedName shoud be the name of an attribute String fieldName = child.getQualifiedName(); //find get method for this attribute Method getMethod = null; try { getMethod = obj.getConvertedClass().getMethod("get" + firstLetterToUpperCase(fieldName)); } catch (NoSuchMethodException e) { logger.error("Cannot find getter for "+fieldName, e); return null; } catch (SecurityException e) { logger.error("Cannot access getter for "+fieldName, e); return null; } //invoke get method Object value = null; try { value = getMethod.invoke(obj, (Object[]) null); } catch (IllegalAccessException e) { logger.error("Cannot invoke getter for "+fieldName, e); return null; } catch (IllegalArgumentException e) { logger.error("Bad arguments passed to getter for "+fieldName, e); return null; } catch (InvocationTargetException e) { logger.error("Cannot invoke getter for "+fieldName, e); return null; } //if value is null, return null if (value == null || (value instanceof List && ((List<?>) value).size() == 0)) { map.put(fieldName, null); } else if (value instanceof List<?>) { //if value is a list, recursive call map.put(fieldName, serializeList((List<ModelObject>) value, child)); } else if (value instanceof ModelObject) { //if value is another convertable object, recursive call map.put(fieldName, serialize((ModelObject) value, child)); } else { //simple value, put it in map.put(fieldName, value); } } return map; } /** * Intermediary method that is called from outside of this class. * @param list List of objects to be converted. * @param confFileName Name of the configuration file to be used. * @return The list of converted objects */ public static List<Object> serializeList(List<ModelObject> list, String confFileName) { return serializeList(list, findRootElement(confFileName)); } /** * Method that is called inside this class with an XML element (based on XOM library) * @param list List of objects to be converted. * @param element XML element (XOM) representing the object's structure * @return List of converted objects. */ public static List<Object> serializeList(List<ModelObject> list, Element element) { ArrayList<Object> res = new ArrayList<Object>(); for (ModelObject obj : list) { res.add(serialize(obj, element)); } return res; } /** * Method that is called from outside of this class. * @param object Object to be converted. * @param confFileName Name of the XML file to use for the convertion. * @return Converted object. */ public static Object serialize(ModelObject object, String confFileName) { return serialize(object, findRootElement(confFileName)); } /** * Helper method that is used to set the first letter of a String to upper case. * @param str The string to be modified. * @return Returns the new String with its first letter in upper case. */ private static String firstLetterToUpperCase(String str) { return str.substring(0, 1).toUpperCase() + str.substring(1); } /** * Helper method that is taking an XML configuration file name and returns its the root element (based on XOM library). * @param confFileName Name of the XML configuration file * @return Returns the root element (XOM based) */ private static Element findRootElement(String confFileName) { Builder parser = new Builder(); Document doc = null; String confFile = confFileName + ".xml"; try { doc = parser.build(CONFIGURATION_DIR + confFile); } catch (ValidityException e) { doc = e.getDocument(); logger.warn("XML configuration file for "+confFileName+" isn't valid", e); } catch (ParsingException e) { logger.error("XML configuration file for "+confFileName+" isn't parseable", e); } catch (IOException e) { logger.error("IOException on XML configuration file for "+confFileName, e); } return doc.getRootElement(); } } 参数,该参数是您所有可序列化对象必须实现的接口(在下面提供)。
如果您已经有一个用于在一个给定类型下将所有域对象重新组合在一起的接口,那么也可以使用此接口(您只需要添加一个方法,请参见下文)。

接口ModelObject

ModelObject

您使用此ObjectConverter如下:

/**
 * Interface that identifies an object as a DOM object and is used by class {@ObjectConverter} to retrieve the class of the object to convert.
 * @author mdekeys
 *
 */
public interface ModelObject {

    /**
     * This method returns the implementer's class
     * @return The implementer Class
     */
    Class<?> getConvertedClass();

}

PS:忽略奇怪的演员,当你知道XX可以被转换为YY时,这是一个将XX列表转换为YY列表的Java技巧。

你可以看到,除了杰克逊之外还使用了这个: 您从数据库中检索列表或单个对象,然后使用ObjectConverter的专用方法(serializeList等)转换它们,并提供XML配置文件的密钥(例如Assignment.xml)。然后你将它们添加到由杰克逊自己序列化的地图上,然后你去。

此ObjectConverter读取XML文件的目标是构建一个可以使用这些XML文件自定义的数据结构。这样可以避免为需要序列化的每个对象创建转换器类。

ObjectConverter类将循环遍历XML文件的所有元素,然后使用java.lang.reflect包在要转换的对象中查找这些属性。
请注意,显然拼写在XML文件中非常重要,但顺序不是。

我自己使用该解决方案并使用我自己的一个小应用程序,我能够生成所有XML文件,然后根据需要自定义它们。这可能看起来很重,但这对我帮助很大,而且我没有看到任何性能损失。

希望这有帮助!

答案 2 :(得分:1)

这是一个非常常见的问题,已经解决了,请查看Jackson的@JsonBackReference注释。样品:

@Entity
@Table(name = 'EMPLOYERS')
public class Employer implements Serializable {
    @JsonManagedReference('employer-employee')
    @OneToMany(mappedBy = 'employer', cascade = CascadeType.PERSIST)
    public List getEmployees() {
        return employees;
    }
}

@Entity
@Table(name = 'EMPLOYEES')
public class Employee implements Serializable {
    @JsonManagedReference('employee-benefit')
    @OneToMany(mappedBy = 'employee', cascade = CascadeType.PERSIST)
    public List getBenefits() {
        return benefits;
    }

    @JsonBackReference('employer-employee')
    @ManyToOne(optional = false)
    @JoinColumn(name = 'EMPLOYER_ID')
    public Employer getEmployer() {
        return employer;
    }
}

@Entity
@Table(name = 'BENEFITS')
public class Benefit implements Serializable {
    @JsonBackReference('employee-benefit')
    @ManyToOne(optional = false)
    @JoinColumn(name = 'EMPLOYEE_ID')
    public Employee getEmployee() {
        return employee;
    }
}

Full example