JPA中因双向依赖而造成的json怪相 相互访问造成溢出

简单说一下Jackson

如果想要详细了解一下Jackson,可以去其github上的项目主页查看其版本情况以及各项功能。除此以外,需要格外提一下Jackson的版本问题。Jackson目前主流版本有两种,1.x和2.x,而这二者的核心包是不同的。在2.0以后的Jackson版本中,groupId从原来的org.codehaus.jackson.core转而变成了com.fasterxml.jackson.core。所以如果希望用到新性能的Jackson,请将原来的maven依赖改为以下的依赖。

下文中的jackson-2-version是指特定的版本,当前的稳定版本为2.8.9。想要了解最新的版本情况还请去项目主页或是maven官网上查看。但是如果是要和spring mvc配合使用的话,还要注意一下他们之间的兼容问题。目前我采用的是4.2.6版本的springmvc和2.6.6版本的jackson

 
  1. <!-- the core, which includes Streaming API, shared low-level abstractions (but NOT data-binding) -->

  2. <dependency>

  3. <groupId>com.fasterxml.jackson.core</groupId>

  4. <artifactId>jackson-core</artifactId>

  5. <version>${jackson-2-version}</version>

  6. </dependency>

  7.  
  8. <!-- Just the annotations; use this dependency if you want to

  9. attach annotations to classes without connecting them to the code. -->

  10. <dependency>

  11. <groupId>com.fasterxml.jackson.core</groupId>

  12. <artifactId>jackson-annotations</artifactId>

  13. <version>${jackson-2-version}</version>

  14. </dependency>

  15.  
  16. <!-- databinding; ObjectMapper, JsonNode and related classes are here -->

  17. <dependency>

  18. <groupId>com.fasterxml.jackson.core</groupId>

  19. <artifactId>jackson-databind</artifactId>

  20. <version>${jackson-2-version}</version>

  21. </dependency>

  22.  
  23. <!-- smile (binary JSON). Other artifacts in this group do other formats. -->

  24. <dependency>

  25. <groupId>com.fasterxml.jackson.dataformat</groupId>

  26. <artifactId>jackson-dataformat-smile</artifactId>

  27. <version>${jackson-2-version}</version>

  28. </dependency>

  29. <!-- JAX-RS provider -->

  30. <dependency>

  31. <groupId>com.fasterxml.jackson.jaxrs</groupId>

  32. <artifactId>jackson-jaxrs-json-provider</artifactId>

  33. <version>${jackson-2-version}</version>

  34. </dependency>

  35. <!-- Support for JAX-B annotations as additional configuration -->

  36. <dependency>

  37. <groupId>com.fasterxml.jackson.module</groupId>

  38. <artifactId>jackson-module-jaxb-annotations</artifactId>

  39. <version>${jackson-2-version}</version>

  40. </dependency>

如果是在springmvc中配置maven依赖,则需要的依赖包为core,annotation和databind

从一个bug说起

刚开始上手springmvc的时候并没有详细去了解更多的JSON操作,只是简单的了解了一下如何将对象转化为JSON数据传送回前端。但是在这时出现了一个问题,就是当两个entity之间存在双向依赖时,传回的JSON数据会出现无限的读取情况。也就是说,因为两个实体中都存在着指向对方的指针,导致序列化的时候会出现二者之间不断相互访问的情况。hibernate这种实体设计方式一直让我有些困惑,毕竟在一般代码的设计模式中是应当尽量避免出现双向依赖的。


这里举一个具体的例子说明这个情况。
假设我有一个订单,订单中有多个商品。也就是订单和商品之间是一对多的关系。订单和商品的实体类如下:

订单实体类

 
  1.  
  2. import org.hibernate.annotations.Fetch;

  3. import org.hibernate.annotations.FetchMode;

  4.  
  5. import javax.persistence.*;

  6. import java.util.ArrayList;

  7. import java.util.Date;

  8. import java.util.List;

  9.  
  10. /**

  11. * Created by rale on 7/15/17.

  12. * 销售单

  13. */

  14. @Entity

  15. @Table(name="sales_order")

  16. public class SalesOrder {

  17.  
  18. @Id

  19. @Column(name = "sales_order_id")

  20. private Long salesOrderId;

  21.  
  22. /**订单创建人员**/

  23. @Column(name = "salesman_id", nullable = false)

  24. private Long userId;

  25.  
  26. @Temporal(TemporalType.TIMESTAMP)

  27. @Column(name = "sales_order_created_at")

  28. private Date createAt;

  29.  
  30.  
  31. /**订单中商品列表清单**/

  32. @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

  33. @OrderBy(value = "order_item_id")

  34. @Fetch(FetchMode.JOIN)

  35. private List<SalesOrderItem> salesOrderItems;

  36.  
  37.  
  38. public Long getSalesOrderId() {

  39. return salesOrderId;

  40. }

  41.  
  42. public void setSalesSource(SalesSource salesSource) {

  43. this.salesSource = salesSource;

  44. }

  45.  
  46. public Long getUserId() {

  47. return userId;

  48. }

  49.  
  50. public void setUserId(Long userId) {

  51. this.userId = userId;

  52. }

  53.  
  54. public Date getCreateAt() {

  55. return createAt;

  56. }

  57.  
  58. public void setCreateAt(Date createAt) {

  59. this.createAt = createAt;

  60. }

  61.  
  62.  
  63. public List<SalesOrderItem> getSalesOrderItems() {

  64. return salesOrderItems==null ? new ArrayList<SalesOrderItem>() : salesOrderItems;

  65. }

  66.  
  67. public void setSalesOrderItems(List<SalesOrderItem> salesOrderItems) {

  68. this.salesOrderItems = salesOrderItems;

  69. }

  70.  
  71. public void addSalesOrderItem(SalesOrderItem salesOrderItem){

  72. if (this.salesOrderItems == null) this.salesOrderItems = new ArrayList<SalesOrderItem>();

  73. salesOrderItem.setSalesOrder(this);

  74. this.salesOrderItems.add(salesOrderItem);

  75. }

  76.  
  77.  
  78. }

订单商品实体类

 
  1. import javax.persistence.*;

  2.  
  3. /**

  4. * Created by rale on 7/15/17.

  5. * 销售清单中的单品和数量

  6. */

  7. @Entity

  8. @Table(name = "sales_order_item")

  9. public class SalesOrderItem {

  10. @Id

  11. @GeneratedValue

  12. @Column(name = "order_item_id")

  13. private Long salesOrderItemId;

  14.  
  15. @Column(name = "order_item_quantity")

  16. private int quantity;

  17.  
  18. @Column(name = "order_item_price")

  19. private double salesPrice;

  20.  
  21. //对应的销售单实体类

  22. @ManyToOne

  23. @JoinColumn(name = "order_id")

  24. private SalesOrder salesOrder;

  25.  
  26. public SalesOrderItem() {

  27. }

  28.  
  29.  
  30. public SalesOrderItem(Long salesOrderItemId){

  31. this.salesOrderItemId = salesOrderItemId;

  32. }

  33.  
  34. public Long getSalesOrderItemId() {

  35. return salesOrderItemId;

  36. }

  37.  
  38. public void setSalesOrderItemId(Long salesOrderItemId) {

  39. this.salesOrderItemId = salesOrderItemId;

  40. }

  41.  
  42.  
  43. public int getQuantity() {

  44. return quantity;

  45. }

  46.  
  47. public void setQuantity(int quantity) {

  48. this.quantity = quantity;

  49. }

  50.  
  51. public double getSalesPrice() {

  52. return salesPrice;

  53. }

  54.  
  55. public void setSalesPrice(double salesPrice) {

  56. this.salesPrice = salesPrice;

  57. }

  58.  
  59. public SalesOrder getSalesOrder() {

  60. return salesOrder;

  61. }

  62.  
  63. public void setSalesOrder(SalesOrder salesOrder) {

  64. this.salesOrder = salesOrder;

  65. }

  66. }

  67.  

解决双向依赖的方法如下:

@JsonIgnore

在不希望被序列化的field或property上使用@JsonIgnore标记,即可使该属性在序列化和解序列化的过程中不被访问。

 
  1. @Entity

  2. @Table(name="sales_order")

  3. public class SalesOrder {

  4.  
  5. ...

  6.  
  7. /**订单中商品列表清单**/

  8. @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

  9. @OrderBy(value = "order_item_id")

  10. @Fetch(FetchMode.JOIN)

  11. private List<SalesOrderItem> salesOrderItems;

  12.  
  13. ...

  14. //getters and setters

  15. }

  16.  
  17. @Entity

  18. @Table(name = "sales_order_item")

  19. public class SalesOrderItem {

  20.  
  21. ...

  22.  
  23. //对应的销售单实体类

  24. @ManyToOne

  25. @JoinColumn(name = "order_id")

  26. @JsonIgnore

  27. private SalesOrder salesOrder;

  28.  
  29. ...

  30. //getters and setters

  31. }

这里可能会出现不适用的场景,比如说,当我希望从SalesOrderItem的方向获取SalesOrder的数据,将会出现无法被序列化的情况。

这里需要特别强调一下 不要使用transient标记属性 会报错

@JsonManagedReference and @JsonBackReference.

@JsonManagedReference标记在父类对子类的引用变量上,并将@JsonBackReference标记在子类对父类的引用变量上。

 
  1. @Entity

  2. @Table(name="sales_order")

  3. public class SalesOrder {

  4.  
  5. ...

  6.  
  7. /**订单中商品列表清单**/

  8. @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

  9. @OrderBy(value = "order_item_id")

  10. @Fetch(FetchMode.JOIN)

  11. @JsonManagedReference

  12. private List<SalesOrderItem> salesOrderItems;

  13.  
  14. ...

  15. //getters and setters

  16. }

  17.  
  18. @Entity

  19. @Table(name = "sales_order_item")

  20. public class SalesOrderItem {

  21.  
  22. ...

  23.  
  24. //对应的销售单实体类

  25. @ManyToOne

  26. @JoinColumn(name = "order_id")

  27. @JsonBackReference

  28. private SalesOrder salesOrder;

  29.  
  30. ...

  31. //getters and setters

  32. }

通过这种方式确保在双向关系中只有单个反向的实例被序列化

@JsonIdentityInfo

该annotation用于标注在entity上。当entity被标注后,jackson在每一次序列化的时候都会为该实例生成专门的ID(也可以是实例自带的属性),通过这种方式辨别实例。这种方式适用于存在一个实体关联链的场景。比如Order -> OrderLine -> User -> Order

 
  1. @Entity

  2. @Table(name="sales_order")

  3. @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")

  4. public class SalesOrder {

  5.  
  6. ...

  7.  
  8. /**订单中商品列表清单**/

  9. @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

  10. @OrderBy(value = "order_item_id")

  11. @Fetch(FetchMode.JOIN)

  12. private List<SalesOrderItem> salesOrderItems;

  13.  
  14. ...

  15. //getters and setters

  16. }

  17.  
  18. @Entity

  19. @Table(name = "sales_order_item")

  20. @JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")

  21. public class SalesOrderItem {

  22.  
  23. ...

  24.  
  25. //对应的销售单实体类

  26. @ManyToOne

  27. @JoinColumn(name = "order_id")

  28. private SalesOrder salesOrder;

  29.  
  30. ...

  31. //getters and setters

  32. }

@JsonIgnoreProperties 个人心中的全场最佳

@JsonIgnoreProperties不同于@JsonIgnore在于,你可以注明该变量中的哪个属相不被序列化。从而允许在双向访问上都不存在环或是缺失。

 
  1. @Entity

  2. @Table(name="sales_order")

  3. public class SalesOrder {

  4.  
  5. ...

  6.  
  7. /**订单中商品列表清单**/

  8. @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

  9. @OrderBy(value = "order_item_id")

  10. @Fetch(FetchMode.JOIN)

  11. @JsonIgnoreProperties("salesOrder")

  12. private List<SalesOrderItem> salesOrderItems;

  13.  
  14. ...

  15. //getters and setters

  16. }

  17.  
  18. @Entity

  19. @Table(name = "sales_order_item")

  20. public class SalesOrderItem {

  21.  
  22. ...

  23.  
  24. //对应的销售单实体类

  25. @ManyToOne

  26. @JoinColumn(name = "order_id")

  27. @JsonIgnoreProperties("salesOrderItems")

  28. private SalesOrder salesOrder;

  29.  
  30. ...

  31. //getters and setters

  32. }

半场小结

其实jackson中还有很多很实用的功能,例如如何将Date类序列化成界面展示的格式等,将在下一次更新中说明。有兴趣的可以收藏加关注哦。

其它教程传送门

springmvc + ajax 实现
http://www.mkyong.com/spring-...

jackson annotation教程
http://tutorials.jenkov.com/j...

stack overflow上相关问题回答
https://stackoverflow.com/que...
https://stackoverflow.com/que...
https://stackoverflow.com/que...

这里需要指出的是,虽然某些回答说,要将annotation标注在私有变量的get方法上,但是po主发现标注在私有变量上还是可以实现功能的。

data hiding using jsonignore and spring data jpa
https://dzone.com/articles/da...

转载于https://segmentfault.com/a/1190000010246362

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值