測試開發專題:spring-boot如何使用JPA進行雙向一對多配置

測試軒發表於2020-05-24

本片文章我們主要介紹spring-boot如何進行JPA的配置以及如何進行實體間的一對多配置。

依賴準備

要在spring-boot使用jpa需要在專案中有進入相關的依賴,pom檔案里加入下面內容

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

專案的配置檔案中需要對資料庫連結以及jpa進行配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/missyou?characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: 12345678
  jpa:
    hibernate:
      ddl-auto: update # 只針對新增的entity建立表
    properties:
      hibernate:
        show_sql: true  # 在對資料庫進行操作的時候列印出sql,方便在生產環境排查問題
        format_sql: true  # 列印sql的時候進行格式化,看起來方便

jpa實戰

  • 一對多關係

先定義兩個實體類Banner和BannerItem,一個Banner可以對應多個BannerItem,屬於典型的一對多的關係

@Entity
public class Banner {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(length = 16)
    private String name;

    @Transient  // 表明這個欄位不會對映到表中的欄位
    private String description;
    private String img;
    private String title;

    @OneToMany
    private List<BannerItem> items;  // 關聯屬性,導航屬性

}
@Entity
public class BannerItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String img;
    private String keyword;
    private Short type;
    private String name;
}

先對上面程式碼中的一些註解進行說明:

@Entity標記當前類為一個實體,對應資料庫中的一張表,用來表示這張表資訊,類名預設就是表名,jpa會根據命名規則自動小寫並加下劃線,比如BannerItem實體,生成的表名就是banner_item

@Id表名標記的欄位將作為主鍵

@GeneratedValue(strategy = GenerationType.IDENTITY) 設定id為自增長

@Column(length = 16) 設定欄位的屬性,比如長度、是否為空、是否唯一等

@OneToMany可以使用兩個實體建立一對多的關係,也就是一個Banner可以包含多個BannerItem

@Transient註解表示被標記的當前欄位不會對映到資料庫表的欄位,也就是說生成的表中不會包含這個欄位

此時執行程式,就會在資料庫中建立對應的表:

image-20200517142949967

這裡生成了三張表,banner表和banner_item表我們知道是對應的Banner實體和BannerItem實體,那banner_items這張表是怎麼回事呢?

在解釋這張表之前,我們先來看一下這個表的結構:

image-20200517143242451

表裡只有兩個欄位,banner_id和items_id,分別是banner的id和banner_item的id,這就是說這張表相當於一張關係表,在多對多的時候我們才需要一張關係表,而現在只是一對多,為什麼會產生這張表呢?

這是因為在上面的實體定義的時候,我們只是給這兩個實體建立了關係,並沒有任何一個欄位來表明哪一個BannerItem屬於哪一個Banner,那JPA看到你沒有做,那就我來做,他會自動幫你建立這樣一張關係表來維護這種歸屬關係。那有沒有辦法去掉這張表呢,當然有。

這個時候就需要新增一個外來鍵欄位了,來表明一個banner_item到底是歸屬於哪一個banner,我們來修改一下上面的實體定義:

Banner實體裡做如下修改:

@OneToMany
@JoinColumn(name = "bannerId")
private List<BannerItem> items; 

@JoinColumn註解指定外來鍵

BannerItem實體增加一個欄位:bannerId

private Long bannerId;

刪除之前的表,重新執行程式

image-20200517144540479

這個時候生成的表就只有兩個了,在banner_item表裡會新增一個外來鍵欄位。

下面我們通過一個程式例項來驗證上面的內容:

1、構造資料

表結構已經構造好了,現在我們填充一些資料,來使用JPA進行資料查詢

INSERT INTO missyou.banner (id, img, name, title) VALUES (1, 'http://sss.jpg', '頂部banner', '頂部banner');
INSERT INTO missyou.banner (id, img, name, title) VALUES (2, 'http://aaa.jpg', '頂部banner2', '頂部banner2');

INSERT INTO missyou.banner_item (id, banner_id, img, keyword, name, type) VALUES (1, 1, 'http://www.jpj', '衣服', '阿迪促銷', 1);
INSERT INTO missyou.banner_item (id, banner_id, img, keyword, name, type) VALUES (2, 1, 'http://www.jpj', '鞋', '阿迪促銷', 1);

2、新建Controller\service\repository

BannerRepository倉儲層,主要負責讀取資料庫

@Repository
public interface BannerRepository extends JpaRepository<Banner, Long> {

    Banner findOneById(Long id);

    Banner findOneByName(String name);
}

sevice層,具體的業務邏輯

@Service
public class BannerServiceImpl implements BannerService{

    @Autowired
    private BannerRepository bannerRepository;

    @Override
    public Banner getByName(String name) {
        return bannerRepository.findOneByName(name);
    }
}

controller層,負責接收客戶端請求返回資料

@GetMapping("/banner/name/{name}")
public Banner getBannerByName(@PathVariable String name){
    return bannerService.getByName(name);
}

請求介面:

image-20200517150608899

可以看到資料已經查詢出來了。

  • 雙向一對多

上面的示例是根據banner_id去查關聯的一組banner_item,但是有些場景我們可能需要通過某個item_id去查是屬於哪個banner,那這就是一種的反向的一對多,接下來我們看一下這總反向的一對多如何配置。

我們之前配置了一個items屬性

@OneToMany
@JoinColumn(name = "bannerId")
private List<BannerItem> items; 

這個屬性就可以幫我們定位到某個banenr關聯的一組bannerItem,可以叫做導航屬性,那麼反過來,如何從一個item定位到一個Banner,怎麼設定導航屬性?

首先需要在BannerItem實體類裡面增加一個banner屬性,並使用如下註解進行標記

@ManyToOne
@JoinColumn(name = "bannerId")
private Banner banner;

修改Banner實體

@OneToMany(mappedBy = "banner")
private List<BannerItem> items;

這裡其實就是把@JoinColumn(name = "bannerId")移動到BannerItem裡面。並且在BanenrItem裡要刪除掉之前定義的外來鍵欄位bannerId。

這是因為在設定雙向一對多的關係時,會預設在banner_item表裡生成一個外來鍵,如果自己再寫一個就會造成重複,程式就會報錯。

那如果自己很想指定,也可以,進行如下配置

@ManyToOne
@JoinColumn(insertable = false, updatable = false, name = "bannerId")
private Banner banner;

總結

以上就是我們介紹的關於在spring-boot中如何進行實體間的單向一對多和雙向一對多的配置,關於要在實際專案如何使用哪一種方式,還是要結合自己的業務,一般不需要進行雙向的配置,而且一般都不會去設定物理外來鍵,後面我們再討論如何進行多對多的配置以及如何去除物理外來鍵。

歡迎大家去 我的部落格 瞅瞅,裡面有更多關於測試實戰的內容哦!!

相關文章