对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设计方式,合理区分子资源和属性.
    大大减少数据库压力!

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