繁体   English   中英

gson.toJson() 在 Servlet 中抛出 StackOverflowError

[英]gson.toJson() throws StackOverflowError in Servlet

我有对象客户列表

List<Client> clientsList=new ArrayList<Client>();
clientsList=clientDao.GetAllClients();

实体客户端将其他列表作为属性:

@ManyToOne(optional=false)
private User createdBy;


@ManyToMany(mappedBy = "Clients")
private Set<ClientType> Types=new HashSet();


@ManyToOne(optional=false)
private LeadSource id_LeadSource;
@ManyToOne(optional=false)
private Agencie id_Agencie;

@OneToMany(cascade=CascadeType.ALL,mappedBy="Owner")
private Set<Propertie> properties=new HashSet();

@OneToMany(cascade=CascadeType.ALL,mappedBy="buyer")
private Set<Sale> sales=new HashSet();

@OneToMany(cascade=CascadeType.ALL,mappedBy = "client")
private Set<Rent> Rents=new HashSet();

@OneToMany(cascade=CascadeType.ALL,mappedBy = "clientDoc")
private Set<Document> Docuements=new HashSet();

当我尝试将客户端列表转换为 json 格式时

out.write(new Gson().toJson(clientsList));

我收到此错误:

java.lang.StackOverflowError
at com.google.gson.stream.JsonWriter.beforeName(JsonWriter.java:603)
at com.google.gson.stream.JsonWriter.writeDeferredName(JsonWriter.java:401)
at com.google.gson.stream.JsonWriter.value(JsonWriter.java:512)
at com.google.gson.internal.bind.TypeAdapters$8.write(TypeAdapters.java:270)
at com.google.gson.internal.bind.TypeAdapters$8.write(TypeAdapters.java:255)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:113)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:240)

那是因为您的实体具有双向连接。 因此,例如Client有一组Rent ,每个租金都有对Client的引用。 当您尝试序列化Client您序列化其Rents ,然后您必须序列化Rent每个Client ,依此类推。 这就是导致StackOverflowError原因。

要解决此问题,您必须将某些属性标记为transient (或使用一些类似的注释),例如在Rent使用transient Client然后任何编组库都将忽略此属性。

在GSON的情况下,你可以做标记周围想包括在使用JSON这些领域的其他方式@Expose和创建的GSON对象:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

PS 另外,我想提一下,将您的 JPA 实体转换为 json 并将其发送到某个地方通常不是一个好主意。 我建议创建一个 DTO(数据传输对象)类,您可以在其中仅包含您需要的信息,并且最好只使用简单的类型,如intDateString等。 如果您对此方法有疑问,您可以谷歌搜索DTOData Transfer Object或点击此链接: https : //www.tutorialspoint.com/design_pattern/transfer_object_pattern.htm

经过一段时间与这个问题的斗争,我相信我有一个解决方案。 正如@Nestor Sokil 所解释的,问题在于未解决的双向连接,以及如何在序列化时表示连接。 修复该行为的方法是“告诉” gson如何序列化对象。 为此,我们使用Adapters

通过使用Adapters我们可以告诉gson如何序列化Entity类中的每个属性以及序列化哪些属性。

FooBar是两个实体,其中Foo具有OneToMany的关系,以BarBarManyToOne关系Foo 我们定义了Bar适配器,所以当gson序列化Bar ,通过定义如何从Bar循环引用的角度序列化Foo将是不可能的。

public class BarAdapter implements JsonSerializer<Bar> {
    @Override
    public JsonElement serialize(Bar bar, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("id", bar.getId());
        jsonObject.addProperty("name", bar.getName());
        jsonObject.addProperty("foo_id", bar.getFoo().getId());
        return jsonObject;
    }
}

这里foo_id用于表示将被序列化并会导致我们的循环引用问题的Foo实体。 现在当我们使用适配器Foo将不会从Bar再次序列化,只会获取它的 id 并将其放入JSON 现在我们有了Bar适配器,我们可以用它来序列化Foo 这是想法:

public String getSomething() {
    //getRelevantFoos() is some method that fetches foos from database, and puts them in list
    List<Foo> fooList = getRelevantFoos();

    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(Bar.class, new BarAdapter());
    Gson gson = gsonBuilder.create();

    String jsonResponse = gson.toJson(fooList);
    return jsonResponse;
}

还有一件事要澄清, foo_id不是强制性的,可以跳过。 在这个例子中适配器的目的是序列化Bar并且通过放置foo_id我们展示了Bar可以触发ManyToOne而不会导致Foo再次触发OneToMany ...

答案是基于个人经验,因此请随时发表评论,证明我错了,纠正错误,或扩大答案。 无论如何,我希望有人会发现这个答案很有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM