簡體   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