Spring Data之JPA/Hibernate的OneToOne示例

OneToOne場景

OneToOne主要使用在存在一一對應的業務場景下,例如將一個用戶信息拆分爲了2個部分:基本信息和擴展信息;在這種場景下,就需要進行OneToOne的映射使用。

演示場景

用戶信息將被分拆爲2個部分: 基本信息和擴展信息.
基本信息包括:id,name, location, 所購產品
擴展信息:level(用戶級別), discountRate折扣率,remark備註等信息

他們之間是一一對應的關係,都從屬於user這個實體類。

類定義

UserEntity.java類的定義:

/**
 * User DAO.
 * @author  xxx
 * @date 2019-05-04
 */
@EqualsAndHashCode(callSuper = true)
@Table(name="t_user")
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class UserEntity extends BaseEntity {
    @Column
    private String name;

    @Column
    private String location;

    /**
     * Who owns the foreign Key, who will be the owner, and declare the JoinColumn.
     *
     */
    @JsonManagedReference
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name="user_ext_id", referencedColumnName = "id")
    private UserExtEntity userExtEntity;
}

UserExtEntity.java用戶擴展信息定義如下:

/**
 * 用戶的擴展信息.
 * @author xxx
 * @date 2019/05/03
 *
 */
@Table(name="t_user_ext")
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class UserExtEntity extends BaseEntity {
    //用戶級別
    @Column
    private Integer level;

    /**
     * 折扣率
     */
    @Column(name="discount_rate")
    private Float discountRate;

    /**
     * 用戶備註
     */
    @Column
    private String remark;

    @JsonBackReference
    @OneToOne(cascade = {CascadeType.REFRESH},
            mappedBy = "userExtEntity", fetch = FetchType.LAZY)
    private UserEntity user;
}

DAO層聲明

User DAO之UserRepsoitory.java:

/ * DAO
 *
 * @author: xxx
 * @date:  2019/05/03
 */
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
}

UserExt DAO之UserExtRepository.java:

/**
 *  User Ext Repository
 *
 * @author  xxx
 * @date 2019-05-05
 */
@Repository
public interface UserExtRepository extends JpaRepository<UserExtEntity, Long> {
}

BaseEntity.java 幾類定義:

@Data
@MappedSuperclass
public class BaseEntity implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @CreatedDate
    @Column(name="created_time")
    private Date createdTime;

    @LastModifiedDate
    @Column(name="updated_time")
    private Date updatedTime;

    @CreatedBy
    @Column(name="created_by")
    private String createdBy;

    @LastModifiedBy
    @Column(name="last_modified_by")
    private String lastModifiedBy;

    @Version
    private Integer version;
}

封裝結果數據類,ResultData.java:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResultData {
    private Integer code;
    private String message;
    private Object data;

    public static ResultData failure() {
        ResultData resultData = ResultData.builder().code(-1).message("failure").build();

        return resultData;
    }

    public static ResultData success() {
        ResultData resultData = ResultData.builder().code(0).message("success").build();

        return resultData;
    }
}

測試代碼

Controller層的測試代碼如下:

@RestController
@Slf4j
public class UserController {
   @Autowired
   private UserRepository userRepo;

   @Autowired
   private UserExtRepository extRepo;

   @GetMapping("/case1")
   public ResultData testCreate() {
       UserEntity user = new UserEntity();
       user.setName("zhangsan");
       user.setLocation("BeiJing");

       UserExtEntity userExtEntity = new UserExtEntity();
       userExtEntity.setDiscountRate(0.9f);
       userExtEntity.setLevel(1);
       userExtEntity.setRemark("remark info");

       user.setUserExtEntity(userExtEntity);

       user = this.userRepo.save(user);

       log.info("User:{}", user);

       ResultData resultData = ResultData.success();
       resultData.setData(user);

       return resultData;
   }
}

在postman中直接調用這些鏈接進行測試,然後進入到數據庫中進行數據檢查數據是否存在。

分析說明

@OneToOne之主從關係

其表示1對1的數據對應關係,主要是基於表的數據關係。
在@OneToOne註解的映射關係中,如果需要定義雙向映射關係,其中定義了外鍵約束的表中外鍵字段,其所對應的Entity屬性爲hosted(主),其另外Entity類中定義爲從屬關係。
在本示例中,將user_ext_id定義在了UserEntity對應的t_user表中,則UserEntity中的userExtEntity字段爲hosted(主),在UserExtEntity定義的user屬性則爲從屬。
從另外一個維度來觀察,定義了JoinColumn的字段爲hosted字段,host字段與JoinColumn是緊密相關的。

@OneToOne之屬性

cascade屬性定義了數據操作之時的同步類型,其涵蓋了CURD以及遊離態:

public enum CascadeType { 

    /** Cascade all operations */
    ALL, 

    /** Cascade persist operation */
    PERSIST, 

    /** Cascade merge operation */
    MERGE, 

    /** Cascade remove operation */
    REMOVE,

    /** Cascade refresh operation */
    REFRESH,

    /**
     * Cascade detach operation
     *
     * @since Java Persistence 2.0
     * 
     */   
    DETACH
}

fetch其表示當前Entity的數據是否提前加載,eager表示其預加載,在Entity創建之時,即從數據庫中拉取加載。Lazy表示只有在真正訪問該數據字段時,才從數據庫中進行加載拉取。

public enum FetchType {
    /** Defines that data can be lazily fetched. */
    LAZY,

    /** Defines that data must be eagerly fetched. */
    EAGER
}

mappedBy字段表示當前字段爲ORM映射中的從屬字段,其從屬於mappedBy中的值。在示例中是userExtEntity,這個屬性定義在UserEntity中。

Json轉換中的循環依賴

由於在ORM中的雙向依賴會存在循環依賴的關係,所以在進行結果輸出之時,會出現無限的循環直至異常報錯,對於此類問題,該如何處理呢?
基於@JsonManagedReference,@JsonBackReference進行Json雙向映射中的循環依賴管理,解決映射的問題。
@JsonManagedReference is the forward part of reference – the one that gets serialized normally. 前項部分,用於正常的序列化操作。
@JsonBackReference is the back part of reference – it will be omitted from serialization.
後項部分,這部分的內容將在序列化中被忽略。

總結

在本文中,通過一個相對完成的示例展示了對於OneToOne映射的基本用法,在其中包含了@OneToOne、@JoinColumn、@JsonManagedReference和@JsonBackReference的用法,大家多多體會其中用法。

參考資料

  1. Json循環依賴的解決
  2. JPA OneToOne
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章