對JPA實體關係管理雙向關聯的一些思考

  • 現象
    在使用JPA進行實體關係管理的時候,會產生無限循環的情況,如果使用fastjson來進行序列化,則表現形式如下:

    {
        "address":{
            "id":63,
            "name":"1address name",
            "person":{"$ref":".."},
            "zipCode":"ZipCode01"
        },
        "firstName":"0firstName",
        "id":69,
        "lastName":"0lastName"
    },
    {
        "address":{
            "id":64,
            "name":"2address name",
            "person":{"$ref":".."},
            "zipCode":"ZipCode11"
        },
        "firstName":"1firstName",
        "id":70,
        "lastName":"1lastName"
    }

    重點是address.person的值:{"$ref":".."}
    如果你用的不是fastjson(它默認會檢查該對象是否已經存在在json文本中)而是其他一些json類庫,比如jackson,則會拋出java.lang.StackOverflowError異常(無限循環產生的棧溢出所導致).
    但是,哪怕你用的是fastjson,你也無法用js來解析{"$ref":".."}.

  • 解決思路

    • 使用fastjson自帶的JSON.toJSONString(page,SerializerFeature.DisableCircularReferenceDetect)
      • 優點:解決快速
      • 缺點:
        • 序列化後的json文本包含太多不需要的信息,冗雜程度太高
        • 方式太死板,沒有相應的註解來實現(jackson有一個),接口只能返回String類型了.
    • 重新設計實體關係,儘量避免雙向關聯,使用RESTful進行接口的暴露.(舉個例子來說)

      • 優點:邏輯清晰,結構更合理
      • 缺點:
        • 對老代碼改動較大.
        • 實現較複雜,要對整體業務邏輯有清晰的認識.

      實體類Person

      public class Person {
          private String name;
      
          @Id
          @GeneratedValue
          private Long id;
      
          @ManyToMany
          @JoinColumn(name = "address_id")
          private List<Address> addresses;
      
          // ...... getter and setter
      }

      實體類Address

      public class Address {
          @Id
          @GeneratedValue
          private Long id;
      
          private String name;
      
          private String zipCode;
      
          // ...... getter and setter
      }

      兩個實體類之間的關係爲Many Person To Many Address,只在Person實體類中進行關係的配置,避免雙向關聯.

      下面舉例說明使用RESTful來對資源進行訪問的情況.

      對於Person:

      • 1 查詢所有Person: /persons
      • 2 查詢某一個Person: /persons/{person_id}
      • 3 查詢某一個Person的所有Address: /persons/{person_id}/addresses
      • 4 查詢某一個Person的某一個Address: /persons/{person_id}/addresses/{address_id}

        如果要查詢一個Address有幾個Person: /persons?address.id=xxx (帶分頁,自己設置pageSize)

      對於Address:

      • 1 查詢所有Address: /addresses
      • 2 查詢某一個Address: /addresses/{address_id}

      以上是Person和Address的一些簡單接口.其中粗體部分爲關聯查詢.
      設計的思路就是要儘量避免雙向關聯,然後把Person作爲一個資源,把Address作爲Person的一個子資源或者屬性.
      上述Person中的1 2 將Address作爲了屬性,查詢時可以通過參數傳遞進去.而上述Person中的3 4 兩個接口則將Address作爲一個子資源進行管理.

      如果要用Address來作爲一個資源反查Person怎麼辦?
      在一個Address管理頁面,需求要求列出某一個住址下的Person:

      • 點擊某一項:
        Address發起/persons?address.id=xxx請求,取得List<Person>.
      • 默認顯示:
        在controller層對/persons?address.id_in=xxx1,xxx2,xxx3接口的返回值進行處理,取得List<Address>和其對應的List<Person>
  • 總結
    儘量避免雙向關聯,使用更合理的API設計方式,合理區分子資源和屬性.
    大大減少數據庫壓力!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章