hibernate中因雙向依賴而形成的json怪相--springmvc項目

簡單說一下Jackson

若是想要詳細瞭解一下Jackson,能夠去其github上的項目主頁查看其版本狀況以及各項功能。除此之外,須要格外提一下Jackson的版本問題。Jackson目前主流版本有兩種,1.x和2.x,而這兩者的核心包是不一樣的。在2.0之後的Jackson版本中,groupId從原來的org.codehaus.jackson.core轉而變成了com.fasterxml.jackson.core。因此若是但願用到新性能的Jackson,請將原來的maven依賴改成如下的依賴。html

下文中的jackson-2-version是指特定的版本,當前的穩定版本爲 2.8.9。想要了解最新的版本狀況還請去項目主頁或是maven官網上查看。可是若是是要和spring mvc配合使用的話,還要注意一下他們之間的兼容問題。目前我採用的是4.2.6版本的springmvc和2.6.6版本的jackson
<!-- the core, which includes Streaming API, shared low-level abstractions (but NOT data-binding) -->
 <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>${jackson-2-version}</version>
 </dependency>

 <!-- Just the annotations; use this dependency if you want to 
    attach annotations to classes without connecting them to the code. -->
 <dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-annotations</artifactId>
   <version>${jackson-2-version}</version>
</dependency>

<!-- databinding; ObjectMapper, JsonNode and related classes are here -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>${jackson-2-version}</version>
</dependency>

<!-- smile (binary JSON). Other artifacts in this group do other formats. -->
<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-smile</artifactId>
  <version>${jackson-2-version}</version>
</dependency>
<!-- JAX-RS provider -->
<dependency>
   <groupId>com.fasterxml.jackson.jaxrs</groupId>
   <artifactId>jackson-jaxrs-json-provider</artifactId>
   <version>${jackson-2-version}</version>
</dependency>
<!-- Support for JAX-B annotations as additional configuration -->
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-jaxb-annotations</artifactId>
  <version>${jackson-2-version}</version>
</dependency>

若是是在springmvc中配置maven依賴,則須要的依賴包爲core,annotation和databind前端

從一個bug提及

剛開始上手springmvc的時候並無詳細去了解更多的JSON操做,只是簡單的瞭解了一下如何將對象轉化爲JSON數據傳送回前端。可是在這時出現了一個問題,就是當兩個entity之間存在雙向依賴時,傳回的JSON數據會出現無限的讀取狀況。也就是說,由於兩個實體中都存在着指向對方的指針,致使序列化的時候會出現兩者之間不斷相互訪問的狀況。hibernate這種實體設計方式一直讓我有些困惑,畢竟在通常代碼的設計模式中是應當儘可能避免出現雙向依賴的。java


這裏舉一個具體的例子說明這個狀況。
假設我有一個訂單,訂單中有多個商品。也就是訂單和商品之間是一對多的關係。訂單和商品的實體類以下:git

訂單實體類github

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by rale on 7/15/17.
 * 銷售單
 */
@Entity
@Table(name="sales_order")
public class SalesOrder {

    @Id
    @Column(name = "sales_order_id")
    private Long salesOrderId;

    /**訂單建立人員**/
    @Column(name = "salesman_id", nullable = false)
    private Long userId;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "sales_order_created_at")
    private Date createAt;


    /**訂單中商品列表清單**/
    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy(value = "order_item_id")
    @Fetch(FetchMode.JOIN)
    private List<SalesOrderItem> salesOrderItems;


    public Long getSalesOrderId() {
        return salesOrderId;
    }

    public void setSalesSource(SalesSource salesSource) {
        this.salesSource = salesSource;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Date getCreateAt() {
        return createAt;
    }

    public void setCreateAt(Date createAt) {
        this.createAt = createAt;
    }


    public List<SalesOrderItem> getSalesOrderItems() {
        return salesOrderItems==null ? new ArrayList<SalesOrderItem>() : salesOrderItems;
    }

    public void setSalesOrderItems(List<SalesOrderItem> salesOrderItems) {
        this.salesOrderItems = salesOrderItems;
    }

    public void addSalesOrderItem(SalesOrderItem salesOrderItem){
        if (this.salesOrderItems == null) this.salesOrderItems = new ArrayList<SalesOrderItem>();
        salesOrderItem.setSalesOrder(this);
        this.salesOrderItems.add(salesOrderItem);
    }


}

訂單商品實體類面試

import javax.persistence.*;

/**
 * Created by rale on 7/15/17.
 * 銷售清單中的單品和數量
 */
@Entity
@Table(name = "sales_order_item")
public class SalesOrderItem {
    @Id
    @GeneratedValue
    @Column(name = "order_item_id")
    private Long salesOrderItemId;

    @Column(name = "order_item_quantity")
    private int quantity;

    @Column(name = "order_item_price")
    private double salesPrice;

    //對應的銷售單實體類
    @ManyToOne
    @JoinColumn(name = "order_id")
    private SalesOrder salesOrder;

    public SalesOrderItem() {
    }

   
    public SalesOrderItem(Long salesOrderItemId){
        this.salesOrderItemId = salesOrderItemId;
    }

    public Long getSalesOrderItemId() {
        return salesOrderItemId;
    }

    public void setSalesOrderItemId(Long salesOrderItemId) {
        this.salesOrderItemId = salesOrderItemId;
    }

    
    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public double getSalesPrice() {
        return salesPrice;
    }

    public void setSalesPrice(double salesPrice) {
        this.salesPrice = salesPrice;
    }

    public SalesOrder getSalesOrder() {
        return salesOrder;
    }

    public void setSalesOrder(SalesOrder salesOrder) {
        this.salesOrder = salesOrder;
    }
}

解決雙向依賴的方法以下:ajax

@JsonIgnore

在不但願被序列化的field或property上使用@JsonIgnore標記,便可使該屬性在序列化和解序列化的過程當中不被訪問。spring

@Entity
@Table(name="sales_order")
public class SalesOrder {

    ...
    
    /**訂單中商品列表清單**/
    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy(value = "order_item_id")
    @Fetch(FetchMode.JOIN)
    private List<SalesOrderItem> salesOrderItems;
    
    ...
    //getters and setters
}

@Entity
@Table(name = "sales_order_item")
public class SalesOrderItem {

    ...

    //對應的銷售單實體類
    @ManyToOne
    @JoinColumn(name = "order_id")
    @JsonIgnore
    private SalesOrder salesOrder;
    
    ...
    //getters and setters
}

這裏可能會出現不適用的場景,好比說,當我但願從SalesOrderItem的方向獲取SalesOrder的數據,將會出現沒法被序列化的狀況。json

這裏須要特別強調一下 不要使用transient標記屬性 會報錯

@JsonManagedReference and @JsonBackReference.

@JsonManagedReference標記在父類對子類的引用變量上,並將@JsonBackReference標記在子類對父類的引用變量上。設計模式

@Entity
@Table(name="sales_order")
public class SalesOrder {

    ...
    
    /**訂單中商品列表清單**/
    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy(value = "order_item_id")
    @Fetch(FetchMode.JOIN)
    @JsonManagedReference
    private List<SalesOrderItem> salesOrderItems;
    
    ...
    //getters and setters
}

@Entity
@Table(name = "sales_order_item")
public class SalesOrderItem {

    ...

    //對應的銷售單實體類
    @ManyToOne
    @JoinColumn(name = "order_id")
    @JsonBackReference
    private SalesOrder salesOrder;
    
    ...
    //getters and setters
}

經過這種方式確保在雙向關係中只有單個反向的實例被序列化

@JsonIdentityInfo

該annotation用於標註在entity上。當entity被標註後,jackson在每一次序列化的時候都會爲該實例生成專門的ID(也能夠是實例自帶的屬性),經過這種方式辨別實例。這種方式適用於存在一個實體關聯鏈的場景。好比Order -> OrderLine -> User -> Order

@Entity
@Table(name="sales_order")
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class SalesOrder {

    ...
    
    /**訂單中商品列表清單**/
    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy(value = "order_item_id")
    @Fetch(FetchMode.JOIN)
    private List<SalesOrderItem> salesOrderItems;
    
    ...
    //getters and setters
}

@Entity
@Table(name = "sales_order_item")
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
public class SalesOrderItem {

    ...

    //對應的銷售單實體類
    @ManyToOne
    @JoinColumn(name = "order_id")
    private SalesOrder salesOrder;
    
    ...
    //getters and setters
}

@JsonIgnoreProperties 我的心中的全場最佳

@JsonIgnoreProperties不一樣於@JsonIgnore在於,你能夠註明該變量中的哪一個屬相不被序列化。從而容許在雙向訪問上都不存在環或是缺失。

@Entity
@Table(name="sales_order")
public class SalesOrder {

    ...
    
    /**訂單中商品列表清單**/
    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderBy(value = "order_item_id")
    @Fetch(FetchMode.JOIN)
    @JsonIgnoreProperties("salesOrder")
    private List<SalesOrderItem> salesOrderItems;
    
    ...
    //getters and setters
}

@Entity
@Table(name = "sales_order_item")
public class SalesOrderItem {

    ...

    //對應的銷售單實體類
    @ManyToOne
    @JoinColumn(name = "order_id")
    @JsonIgnoreProperties("salesOrderItems")
    private SalesOrder salesOrder;
    
    ...
    //getters and setters
}

半場小結

其實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...

clipboard.png
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~

相關文章
相關標籤/搜索