首頁 > 軟體

Spring Boot 整合JPA 資料模型關聯使用操作(一對一、一對多、多對多)

2022-07-19 18:02:42

表關聯

上一篇介紹了JPA的簡單使用,這一篇介紹JPA在表關聯上的使用

一對一

設定引數
JPA對於資料實體一對一對映使用的是@OneToOne註解。

程式碼

User使用者表

/**
 * 使用者資訊
 * @author daify
 **/
@Data
@Entity
@Table(name = "cascade_user")
public class User {

    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    private Integer age;
    
    private Double point;
    /**
     * CascadeType包含的類別  級聯:給當前設定的實體操作另一個實體的許可權
     *      CascadeType.ALL 級聯所有操作
     *      CascadeType.PERSIST 級聯持久化(儲存)操作
     *      CascadeType.MERGE   級聯更新(合併)操作
     *      CascadeType.REMOVE  級聯刪除操作
     *      CascadeType.REFRESH 級聯重新整理操作
     *      CascadeType.DETACH 級聯分離操作,如果你要刪除一個實體,但是它有外來鍵無法刪除,這個級聯許可權會復原所有相關的外來鍵關聯。
     */
    @OneToOne(targetEntity = UserCart.class,
            cascade = CascadeType.ALL,
            mappedBy = "user")
    private UserCart userCart;

    @OneToOne(targetEntity = UserInfo.class,
            cascade = CascadeType.ALL)
    @JoinTable(name = "user_info_table",
            joinColumns = @JoinColumn(name="user_id"),
            inverseJoinColumns = @JoinColumn(name = "info_id"))
    private UserInfo userInfo;

}

UserCart使用者購物車表

@Data
@Entity
@Table(name = "cascade_user_cart")
public class UserCart {

    @Id
    @GeneratedValue
    private Long id;
    
    private Integer count;
    
    private Double totalMoney;
    
    private Date updateTime;

    /**
     * CascadeType包含的類別  級聯:給當前設定的實體操作另一個實體的許可權
     *      CascadeType.ALL 級聯所有操作
     *      CascadeType.PERSIST 級聯持久化(儲存)操作
     *      CascadeType.MERGE   級聯更新(合併)操作
     *      CascadeType.REMOVE  級聯刪除操作
     *      CascadeType.REFRESH 級聯重新整理操作
     *      CascadeType.DETACH 級聯分離操作,如果你要刪除一個實體,但是它有外來鍵無法刪除,這個級聯許可權會復原所有相關的外來鍵關聯。
     */
    @OneToOne(targetEntity = User.class,
            cascade = {},
            fetch = FetchType.LAZY)
    private User user;
}

使用者資訊

@Data
@Entity
@Table(name = "cascade_user_info")
public class UserInfo {

    @Id
    @GeneratedValue
    private Long id;
    
    private String userInfo;
    
    private String config;
}

上面例子中嘗試使用了兩種方式來維護一對一的關係,首先在User實體中同樣標註了@OneToOne但是設定了mappedBy,
這樣生成的表資料中,User和UserCart的關係將由UserCart負責維護。User表中並不會維護UserCart的資訊。
而在User和UserInfo的關係中使用了中間表user_info_table來維護雙方的關係,UserInfo實體中並沒有儲存任何User的資訊。

許可權

在User、UserCart、UserInfo三者中User為資料存在的主體,其他兩個實體都是依託於User資料的存在而存在。
所以在許可權中給User實體提供了全部全部許可權。

註解

@OneToOne主要提供了下面的引數內容

註解引數描述
@OneToOne 描述一個 一對一的關聯
 targetEntity目標類的實體
 cascade關聯到目標的操作
 fetch是否使用延遲載入
 mappedBy反向關聯

測試
因為上面一對一的例子中許可權被賦予給User表中,UserCart並沒有賦予任何許可權,所以儲存使用者的時候可以關聯儲存使用者購物車,刪除購物車的時候並不會刪除使用者,但是刪除使用者的時候會刪除購物車

通過儲存使用者關聯儲存購物車

    @Test
    public void testUserSave() {
        User defUser1 = UserMock.getDefUser1();
        UserCart defUserCart1 = UserCartMock.getDefUserCart1();
        defUser1.setUserCart(defUserCart1);
        defUserCart1.setUser(defUser1);
        // 此時儲存使用者,使用者購物車應該也被儲存
        userRepository.save(defUser1);
        List <UserCart> all = userCartRepository.findAll();
        Assert.assertNotNull(all);
        Assert.assertTrue(all.size() == 1);
    }

刪除使用者購物車的時候,使用者不會被刪除

    @Test
    public void testUserCartDelete() {
        User defUser1 = UserMock.getDefUser1();
        UserCart defUserCart1 = UserCartMock.getDefUserCart1();
        defUser1.setUserCart(defUserCart1);
        // 此時儲存使用者,使用者購物車應該也被儲存
        userRepository.save(defUser1);
        // 此時刪除使用者購物車並不會刪除使用者
        userCartRepository.deleteAll();
        List <User> all1 = userRepository.findAll();
        Assert.assertNotNull(all1);
        Assert.assertTrue(all1.size() == 1);
    }

刪除使用者的時候,購物車會被刪除

    @Test
    public void testUserDelete() {
        User defUser1 = UserMock.getDefUser1();
        UserCart defUserCart1 = UserCartMock.getDefUserCart1();
        defUser1.setUserCart(defUserCart1);
        // 此時儲存使用者,使用者購物車應該也被儲存
        userRepository.save(defUser1);
        // 此時刪除使用者購物車並不會刪除使用者
        userRepository.delete(defUser1);
        List <UserCart> all = userCartRepository.findAll();
        Assert.assertTrue(all.size() == 0);
    }

一對多和多對一

通過@OneToMany和@ManyToOne的組合我們可以實現雙向關聯。根據JPA規範,我們使用多方來維護關係。
通過在多方維護@JoinColumn來註釋屬性。

程式碼

訂單表

@Data
@Entity
@Table(name = "cascade_order")
public class Order {

    @Id
    @GeneratedValue
    private Long id;
    
    private String orderNo;
    
    private Integer count;
    
    private Double money;

    @OneToMany(mappedBy = "order",
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER)
    private List<OrderItem> orderItems;
    
    /**
    * 重寫toString防止死迴圈
    * @return 
    */
    @Override 
    public String toString() {
        return "OrderItem{" + "id=" + id + ", productId='" + productId + ''' + ", money=" + money
               + ", count=" + count + ", productName='" + productName + ''' + ", order=" + order
               + '}';
    }
    
}

訂單子項

@Data
@Entity
@Table(name = "cascade_order_item")
public class OrderItem {

    @Id
    @GeneratedValue
    private Long id;

    private String productId;

    private Double money;
    
    private Integer count;
    
    private String productName;
    
    @ManyToOne(targetEntity = Order.class,
            cascade = {},
            fetch = FetchType.EAGER)
    @JoinColumn(
            // 設定在OrderItem表中的關聯欄位(外來鍵)
            name="order_id"
    )
    // 防止json序列化死迴圈問題解決
    @JsonBackReference
    private Order order;
}

上門的例子中,訂單方為一端,訂單子項為多端,在多端除了使用了@ManyToOne
還使用了@JoinColumn註解來標識Order主鍵建立到OrderItem表的列的名稱,
當然沒有此註解的時候JPA會根據預設規則生成一個列名稱。

許可權
根據JPA規範一對多時候,關係的維護交給了多方來進行,但是很多時候多方的存在是依靠一方的。
比如(訂單、訂單子項)所以更新刪除的許可權需要授權給一方(Order)。

@ManyToOne,@OneToMany

註解引數描述
@ManyToOne 一個多對一的對映,該註解標註的屬性通常是資料庫表的外來鍵.
 targetEntity目標類的實體
 cascade關聯到目標的操作
 fetch是否使用延遲載入
@OneToMany 一個 一對多的關聯,該屬性應該為集體型別,在資料庫中並沒有實際欄位
 targetEntity目標類的實體
 cascade關聯到目標的操作
 fetch是否使用延遲載入
 mappedBy反向關聯
 orphanRemoval講移除操作級聯到關聯的實體中

測試

一對多的時候雖然多方維持了兩者關係,但是我們把許可權賦予了一方所以,刪除多方並不會級聯操作,刪除一方可以移除多方資料。所以下的測試可以通過

    @Test
    public void testOrder() {
        Order defOrder1 = OrderMock.getDefOrder1();
        List <OrderItem> allDefOrder = OrderItemMock.getAllDefOrder();
        // order 維持orderItem關係
        defOrder1.setOrderItems(allDefOrder);
        // orderItem維持order關係
        allDefOrder.forEach(o -> o.setOrder(defOrder1));
        Order save = orderRepository.save(defOrder1);
        List <OrderItem> all = orderItemRepository.findAll();
        Assert.assertTrue(all.size() == allDefOrder.size());

        orderItemRepository.delete(all.get(0));
        Order order = orderRepository.findById(save.getId()).get();
        Assert.assertNotNull(order);

        orderRepository.deleteById(order.getId());
        List <OrderItem> all1 = orderItemRepository.findAll();
        Assert.assertTrue(all1.size() == 0);

    }

多對多

設定方法
和一對多、一對一不同,多對多沒法只使用兩個資料實體完成相互之間的關係維護,這個時候需要一個關聯的中間表來維護他們之間的關係。
對於中間表的設定,你大可不去進行額外的設定讓JPA自動生成,當然你也可以使用之前介紹的@JoinTable註解進行設定。

許可權

和一對多、一對一不同,多對多一般是沒有辦法設定級聯操作的。因為雙方對應的都是集合物件,
而雙方某一條資料都可能被對方多條資料關聯。

程式碼

使用者表

@Data
@Entity
@Table(name = "cascade_user")
public class User {

    @Id
    @GeneratedValue
    private Long id;
    
    private String name;
    
    private Integer age;
    
    private Double point;

    @ManyToMany(targetEntity = UserTag.class,
            cascade = {},
            fetch = FetchType.LAZY)
    // 假如不定義,jpa會生成「表名1」+「_」+「表名2」的中間表
    @JoinTable(
            // 定義中間表的名稱
            name = "cascade_user_tag_table",
            // 定義中間表中關聯User表的外來鍵名
            joinColumns = {
                    @JoinColumn(name = "user_id",
                            referencedColumnName = "id")
            },
            // 定義中間表中關聯UserTag表的外來鍵名
            inverseJoinColumns = {
                    @JoinColumn(name = "tags_id",
                            referencedColumnName = "id")
            }
    )
    private Set<UserTag> tags;

}

使用者標籤表

@Data
@Entity
@Table(name = "cascade_user_tag")
public class UserTag {

    @Id
    @GeneratedValue
    private Long id;
    
    private String tagName;

    @ManyToMany(mappedBy = "tags",
            cascade = {},
            fetch = FetchType.LAZY)
    private List<User> userList;
}

@ManyToMany

下面是@OneToOne以及與其配合的@JoinTable提供的註解引數

註解引數描述
@ManyToMany 描述一個多對多的關聯.多對多關聯上是兩個一對多關聯,但是在ManyToMany描述中,中間表是由ORM框架自動處理
 targetEntity目標類的實體
 cascade關聯到目標的操作
 fetch是否使用延遲載入
 mappedBy反向關聯
@JoinTable JoinTable在many-to-many關係的所有者一邊定義。如果沒有定義JoinTable,使用JoinTable的預設值。
 catalog表的目錄
 foreignKey外來鍵約束,建立表的時候使用
 indexes表的索引,在生成表的時候使用
@JoinColumns 關係存在多個JoinColumn,用JoinColumns定義多個JoinColumn的屬性。
 foreignKey此列的外來鍵約束
 valueJoinColumn的集合
 inverseJoinColumns聯接表的外來鍵列,該列參照不擁有關聯的實體的主表
 joinColumns聯接表的外來鍵列,該列參照擁有關聯的實體的主表
 name進行連線的表名稱
 schema表的名稱空間

測試

多對多的情況下,我們使用雖然User和UserTag的關係由中間表維護,但是我們在User中設定了中間表的關係維護,所以此時刪除使用者的時候可以成功刪除,且可以成功移除中間表資料,但是這個時候移除UserTag資料的時候,卻會丟擲DataIntegrityViolationException異常。只能通過User移除中間關係。

    @Test
    public void testUserTag() {
        User defUser1 = UserMock.getDefUser1();
        UserTag defTag1 = UserTagMock.getDefTag1();
        UserTag defTag2 = UserTagMock.getDefTag2();
        defUser1 = userRepository.save(defUser1);
        defTag1 = userTagRepository.save(defTag1);
        defTag2 = userTagRepository.save(defTag2);

        defUser1.getTags().add(defTag1);
        defUser1.getTags().add(defTag2);

        defUser1 = userRepository.save(defUser1);
        // 此時會報錯,因為中間表關係為User維護
        try {
            userTagRepository.delete(defTag2);
        } catch (Exception e) {
            log.info(e.getCause().getMessage());
            Assert.assertTrue(e instanceof DataIntegrityViolationException);
        }
        // 修改User表中關係
        defUser1.setTags(new HashSet <>());
        defUser1.getTags().add(defTag1);
        defUser1 = userRepository.save(defUser1);
        // 此時可以刪除
        userTagRepository.delete(defTag2);
        // 直接刪除使用者沒問題
        userRepository.delete(defUser1);
    }

本篇文章涉及的原始碼下載地址:https://gitee.com/daifyutils/springboot-samples

到此這篇關於Spring Boot 整合JPA 資料模型關聯操作(一對一、一對多、多對多)的文章就介紹到這了,更多相關Spring Boot 整合JPA 資料模型關聯內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com