在本教程中,我將向您展示如何在使用@OneToOne注解的Spring Boot示例中實現(xiàn)Spring JPA的一對一單向映射與Hibernate。您將了解:
如何配置Spring Data、JPA和Hibernate以與數(shù)據(jù)庫一起工作
如何為JPA一對一關(guān)系定義數(shù)據(jù)模型和存儲庫接口
使用Spring JPA與數(shù)據(jù)庫進行一對一關(guān)聯(lián)交互的方法
創(chuàng)建處理HTTP請求的Spring Rest Controller的方法
目錄
適當(dāng)?shù)膶崿F(xiàn)方式
JPA一對一示例
通過Spring Boot示例進行實踐
技術(shù)框架
項目結(jié)構(gòu)
設(shè)置Spring Boot項目
配置Spring數(shù)據(jù)源、JPA和Hibernate
定義數(shù)據(jù)模型
創(chuàng)建存儲庫接口
創(chuàng)建Spring Rest API控制器
總結(jié)
源代碼
進一步閱讀
實現(xiàn)JPA/Hibernate一對一映射的適當(dāng)方式
在關(guān)系數(shù)據(jù)庫中,表A和表B之間的一對一關(guān)系表示表A中的一行僅鏈接到表B中的一行,反之亦然。
例如,您需要為教程博客設(shè)計數(shù)據(jù)模型,其中一個教程具有相應(yīng)的詳細信息(創(chuàng)建日期時間、創(chuàng)建者)。因此,這是一個一對一關(guān)聯(lián)。
您可以使用Join Table(帶有@JoinTable注解)來實現(xiàn)。它將兩個實體的主鍵值存儲在一個新表中。
另一種方法是使用共享主鍵,其中外鍵位于tutorial_details表中。Tutorial實體是父實體,而Tutorial Details是子實體。
您可以使用JPA/Hibernate的@OneToOne注解將子實體與父實體進行映射。在這種情況下,只有子側(cè)定義了關(guān)系。我們稱之為單向一對一關(guān)聯(lián)。
現(xiàn)在看看tutorial_details表,它包含一個主鍵列(id)和一個外鍵列(tutorial_id)。您可以看到,我們真正只需要一個與教程(父實體)關(guān)聯(lián)的tutorial_details(子實體)行,并且子數(shù)據(jù)在其他關(guān)系中幾乎不會被使用。因此,我們可以省略子實體的id列:
JPA一對一示例
我們將從頭開始創(chuàng)建一個Spring項目,然后按照以下步驟實現(xiàn)JPA/Hibernate的一對一單向映射,使用tutorials和tutorial_details表:
我們還編寫了REST API來執(zhí)行對Details實體的CRUD操作。
以下是需要提供的API:
方法 | URL | 操作 |
---|---|---|
POST | /api/tutorials/:id/details | 為 Tutorial 創(chuàng)建新的 Details |
GET | /api/details/:id | 通過 :id 檢索 Details |
GET | /api/tutorials/:id/details | 檢索 Tutorial 的 Details |
PUT | /api/details/:id | 通過 :id 更新 Details |
PUT | /api/tutorials/:id/details | 更新 Tutorial 的 Details |
DELETE | /api/details/:id | 通過 :id 刪除 Details |
DELETE | /api/tutorials/:id/details | 刪除 Tutorial 的 Details |
DELETE | /api/tutorials/:id | 刪除 Tutorial(及其 Details) |
假設(shè)我們已經(jīng)有了如下的tutorials表格:
以下是示例請求:
創(chuàng)建新的Details實體:POST /api/tutorials/[:id]/details
此后的tutorial_details表格如下所示:
檢索特定Tutorial的Details:GET /api/tutorials/[:id]/details 或 /api/details/[:id]
更新特定Tutorial的Details:PUT /api/tutorials/[:id]/details 或 /api/details/[:id]
刪除特定Tutorial的Details:DELETE /api/tutorials/[:id]/details 或 /api/details/[:id]
請檢查tutorial_details表格,教程ID為1的行已被刪除:
刪除教程:DELETE /api/tutorials/[:id]
教程(id=3)及其Details已被刪除:
讓我們構(gòu)建我們的Spring Boot Data JPA一對一示例。
Spring Boot 一對一示例
技術(shù)棧
Java 17 / 11 / 8
Spring Boot 3 / 2(包括Spring Web MVC,Spring Data JPA)
H2/MySQL/PostgreSQL
Maven
項目結(jié)構(gòu)
讓我簡要解釋一下。
Tutorial和TutorialDetails數(shù)據(jù)模型類對應(yīng)于實體和表tutorials、tutorial_details。
TutorialRepository、TutorialDetailsRepository是擴展JpaRepository的接口,用于CRUD方法和自定義查找方法。它們將在TutorialController、TutorialDetailsController中進行自動注入。
TutorialController、TutorialDetailsController是RestControllers,具有用于RESTful CRUD API請求的RequestMapping方法。
application.properties中配置了Spring Datasource、JPA和Hibernate。
pom.xml包含了Spring Boot和MySQL/PostgreSQL/H2數(shù)據(jù)庫的依賴項。
關(guān)于異常包,為了保持本文簡潔,我不會進行解釋。如果需要更多細節(jié),您可以閱讀以下教程: 在Spring Boot中使用@RestControllerAdvice的示例
創(chuàng)建和設(shè)置Spring Boot項目
使用Spring Web工具或您的開發(fā)工具(Spring Tool Suite,Eclipse,Intellij)創(chuàng)建一個Spring Boot項目。
然后打開pom.xml文件,并添加以下依賴項:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
我們還需要添加一個額外的依賴項。
如果您想使用MySQL:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
或者PostgreSQL:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency>
或者H2(嵌入式數(shù)據(jù)庫):
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
配置Spring Datasource,JPA和Hibernate
在src/main/resources文件夾下,打開application.properties文件并編寫以下內(nèi)容。
對于MySQL:
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto=update
對于PostgreSQL:
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb spring.datasource.username=postgres spring.datasource.password=123 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto=update
其中spring.datasource.username和spring.datasource.password屬性與您的數(shù)據(jù)庫安裝相同。
Spring Boot使用Hibernate作為JPA實現(xiàn),我們根據(jù)數(shù)據(jù)庫類型配置MySQLDialect或PostgreSQLDialect。
spring.jpa.hibernate.ddl-auto用于數(shù)據(jù)庫初始化。我們將其值設(shè)置為update,這樣表將自動在數(shù)據(jù)庫中創(chuàng)建,對應(yīng)于定義的數(shù)據(jù)模型。對模型的任何更改也將觸發(fā)對表的更新。在生產(chǎn)環(huán)境中,此屬性應(yīng)該是validate。
對于H2數(shù)據(jù)庫:
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true # 默認路徑:h2-console spring.h2.console.path=/h2-ui
spring.datasource.url: jdbc:h2:mem:[database-name]用于內(nèi)存數(shù)據(jù)庫,jdbc:h2:file:[path/database-name]用于基于磁盤的數(shù)據(jù)庫。
我們?yōu)镠2數(shù)據(jù)庫配置了H2Dialect。
spring.h2.console.enabled=true告訴Spring啟動H2數(shù)據(jù)庫管理工具,并且您可以通過瀏覽器訪問此工具:http://localhost:8080/h2-console。
spring.h2.console.path=/h2-ui是為H2控制臺的URL設(shè)置,因此默認URL http://localhost:8080/h2-console 將改為 http://localhost:8080/h2-ui。
為JPA一對一映射定義數(shù)據(jù)模型
在model包中,我們定義Tutorial和TutorialDetails類。
Tutorial有四個字段:id、title、description、published。
model/Tutorial.java
package com.bezkoder.spring.jpa.onetoone.model; import jakarta.persistence.*; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties({"hibernateLazyInitializer"}) @Entity @Table(name = "tutorials") public class Tutorial { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(name = "title") private String title; @Column(name = "description") private String description; @Column(name = "published") private boolean published; public Tutorial() { } public Tutorial(String title, String description, boolean published) { this.title = title; this.description = description; this.published = published; } // getters and setters }
@Entity注解表示該類是一個持久化Java類。
@Table注解指定了映射到此實體的表名。
@Id注解用于主鍵。
@GeneratedValue注解用于定義主鍵的生成策略。
@Column注解用于定義數(shù)據(jù)庫中映射的列。
那么@JsonIgnoreProperties({"hibernateLazyInitializer"})呢?
當(dāng)我們使用JPA Repository從數(shù)據(jù)庫中獲取數(shù)據(jù)時,對于從父實體進行懶加載的字段,Hibernate返回一個具有映射到表的所有字段以及hibernateLazyInitializer的對象。
然后,當(dāng)我們將此實體序列化為JSON字符串格式時,所有字段和hibernateLazyInitializer都將被序列化。
因此,為了避免這種不必要的序列化,我們使用@JsonIgnoreProperties。
TutorialDetails類使用@OneToOne注解與Tutorial實體建立一對一關(guān)系,并使用@MapsId注解將id字段同時作為主鍵和外鍵(共享主鍵)。
我們通過使用@JoinColumn注解來設(shè)置共享主鍵列名。
model/TutorialDetails.java
package com.bezkoder.spring.jpa.onetoone.model; import java.util.Date; import jakarta.persistence.*; @Entity @Table(name = "tutorial_details") public class TutorialDetails { @Id private Long id; @Column private Date createdOn; @Column private String createdBy; @OneToOne(fetch = FetchType.LAZY) @MapsId @JoinColumn(name = "tutorial_id") private Tutorial tutorial; public TutorialDetails() { } public TutorialDetails(String createdBy) { this.createdOn = new Date(); this.createdBy = createdBy; } // getters and setters }
讓我們創(chuàng)建一個與數(shù)據(jù)庫交互的倉庫。
在repository包中,創(chuàng)建TutorialRepository和TutorialDetailsRepository接口,它們都擴展了JpaRepository。
repository/TutorialRepository.java
package com.bezkoder.spring.jpa.onetoone.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.bezkoder.spring.jpa.onetoone.model.Tutorial; @Repository public interface TutorialRepository extends JpaRepository<Tutorial, Long> { List<Tutorial> findByPublished(boolean published); List<Tutorial> findByTitleContaining(String title); } ``` repository/TutorialDetailsRepository.java ```java package com.bezkoder.spring.jpa.onetoone.repository; import jakarta.transaction.Transactional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.bezkoder.spring.jpa.onetoone.model.TutorialDetails; @Repository public interface TutorialDetailsRepository extends JpaRepository<TutorialDetails, Long> { @Transactional void deleteById(long id); @Transactional void deleteByTutorialId(long tutorialId); }
現(xiàn)在我們可以使用JpaRepository的方法:save()、findOne()、findById()、findAll()、count()、delete()、deleteById()等,而無需實現(xiàn)這些方法。
我們還定義了自定義的查找方法:
findByPublished():返回所有published字段值為輸入published的Tutorial。
findByTitleContaining():返回所有標(biāo)題包含輸入title的Tutorial。
deleteById():根據(jù)id刪除指定的Tutorial Details。
deleteByTutorialId():根據(jù)tutorialId刪除指定Tutorial的Details。
Spring Data JPA會自動插入實現(xiàn)。
使用@Query注解進行自定義查詢:
Spring Boot中的自定義查詢示例:使用Spring JPA @Query
您還可以找到有關(guān)為此JPA Repository編寫單元測試的方法:
@DataJpaTest的Spring Data Repository單元測試示例
Create Spring Rest APIs Controller
最后,我們創(chuàng)建一個Controller提供CRUD操作的API:創(chuàng)建、獲取、更新、刪除和查找Tutorial和Details。
controller/TutorialController.java
package com.bezkoder.spring.jpa.onetoone.controller; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import com.bezkoder.spring.jpa.onetoone.exception.ResourceNotFoundException; import com.bezkoder.spring.jpa.onetoone.model.Tutorial; import com.bezkoder.spring.jpa.onetoone.repository.TutorialDetailsRepository; import com.bezkoder.spring.jpa.onetoone.repository.TutorialRepository; @CrossOrigin(origins = "*") @RestController @RequestMapping("/api") public class TutorialController { @Autowired TutorialRepository tutorialRepository; @Autowired private TutorialDetailsRepository detailsRepository; @GetMapping("/tutorials") public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) { List<Tutorial> tutorials = new ArrayList<Tutorial>(); if (title == null) tutorialRepository.findAll().forEach(tutorials::add); else tutorialRepository.findByTitleContaining(title).forEach(tutorials::add); if (tutorials.isEmpty()) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } return new ResponseEntity<>(tutorials, HttpStatus.OK); } @GetMapping("/tutorials/{id}") public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) { Tutorial tutorial = tutorialRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id)); return new ResponseEntity<>(tutorial, HttpStatus.OK); } @PostMapping("/tutorials") public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) { Tutorial _tutorial = tutorialRepository.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), true)); return new ResponseEntity<>(_tutorial, HttpStatus.CREATED); } @PutMapping("/tutorials/{id}") public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) { Tutorial _tutorial = tutorialRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id)); _tutorial.setTitle(tutorial.getTitle()); _tutorial.setDescription(tutorial.getDescription()); _tutorial.setPublished(tutorial.isPublished()); return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK); } @DeleteMapping("/tutorials/{id}") public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) { if (detailsRepository.existsById(id)) { detailsRepository.deleteById(id); } tutorialRepository.deleteById(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @DeleteMapping("/tutorials") public ResponseEntity<HttpStatus> deleteAllTutorials() { tutorialRepository.deleteAll(); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @GetMapping("/tutorials/published") public ResponseEntity<List<Tutorial>> findByPublished() { List<Tutorial> tutorials = tutorialRepository.findByPublished(true); if (tutorials.isEmpty()) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } return new ResponseEntity<>(tutorials, HttpStatus.OK); } }
controller/TutorialDetailsController.java
package com.bezkoder.spring.jpa.onetoone.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import com.bezkoder.spring.jpa.onetoone.exception.ResourceNotFoundException; import com.bezkoder.spring.jpa.onetoone.model.TutorialDetails; import com.bezkoder.spring.jpa.onetoone.model.Tutorial; import com.bezkoder.spring.jpa.onetoone.repository.TutorialDetailsRepository; import com.bezkoder.spring.jpa.onetoone.repository.TutorialRepository; @CrossOrigin(origins = "*") @RestController @RequestMapping("/api") public class TutorialDetailsController { @Autowired private TutorialDetailsRepository detailsRepository; @Autowired private TutorialRepository tutorialRepository; @GetMapping({ "/details/{id}", "/tutorials/{id}/details" }) public ResponseEntity<TutorialDetails> getDetailsById(@PathVariable(value = "id") Long id) { TutorialDetails details = detailsRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial Details with id = " + id)); return new ResponseEntity<>(details, HttpStatus.OK); } @PostMapping("/tutorials/{tutorialId}/details") public ResponseEntity<TutorialDetails> createDetails(@PathVariable(value = "tutorialId") Long tutorialId, @RequestBody TutorialDetails detailsRequest) { Tutorial tutorial = tutorialRepository.findById(tutorialId) .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId)); detailsRequest.setCreatedOn(new java.util.Date()); detailsRequest.setTutorial(tutorial); TutorialDetails details = detailsRepository.save(detailsRequest); return new ResponseEntity<>(details, HttpStatus.CREATED); } @PutMapping("/details/{id}") public ResponseEntity<TutorialDetails> updateDetails(@PathVariable("id") long id, @RequestBody TutorialDetails detailsRequest) { TutorialDetails details = detailsRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Id " + id + " not found")); details.setCreatedBy(detailsRequest.getCreatedBy()); return new ResponseEntity<>(detailsRepository.save(details), HttpStatus.OK); } @DeleteMapping("/details/{id}") public ResponseEntity<HttpStatus> deleteDetails(@PathVariable("id") long id) { detailsRepository.deleteById(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @DeleteMapping("/tutorials/{tutorialId}/details") public ResponseEntity<TutorialDetails> deleteDetailsOfTutorial(@PathVariable(value = "tutorialId") Long tutorialId) { if (!tutorialRepository.existsById(tutorialId)) { throw new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId); } detailsRepository.deleteByTutorialId(tutorialId); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } }
這些控制器使用@Autowired注釋注入了TutorialRepository和TutorialDetailsRepository。它們定義了與數(shù)據(jù)庫交互的各種API,包括獲取、創(chuàng)建、更新和刪除Tutorial和Details。根據(jù)需求,您可以自由調(diào)整這些控制器的代碼。
總結(jié)
今天我們使用Spring Data JPA和Hibernate構(gòu)建了一個使用MySQL/PostgreSQL/嵌入式數(shù)據(jù)庫(H2)的Spring Boot示例。
我們還看到,@OneToOne和@MapsId注解是實現(xiàn)JPA一對一單向映射的適當(dāng)方式,而JpaRepository則支持在不需要樣板代碼的情況下進行CRUD操作和自定義查找方法。
使用@Query注解進行自定義查詢:
Spring JPA @Query示例:在Spring Boot中進行自定義查詢
如果您想為這個Spring項目添加分頁功能,可以在以下鏈接找到相關(guān)指南:
Spring Boot分頁&過濾示例 | Spring JPA,Pageable
對多個字段進行排序:
Spring Data JPA按多列排序/排序 | Spring Boot
處理Rest API的異常非常重要:
Spring Boot @ControllerAdvice和@ExceptionHandler示例
在Spring Boot中使用@RestControllerAdvice示例
或者,可以學(xué)習(xí)如何編寫JPA Repository的單元測試:
使用@DataJpaTest進行Spring Boot JPA Repository單元測試
您還可以了解以下內(nèi)容:
在Spring Boot中驗證請求體
如何使用本教程將Spring Boot應(yīng)用部署到AWS(免費)
使用Docker Compose實現(xiàn)Spring Boot和MySQL的容器化示例
或者:Docker Compose實現(xiàn)Spring Boot和Postgres的容器化示例
如何上傳Excel文件并將數(shù)據(jù)存儲在MySQL數(shù)據(jù)庫中
上傳CSV文件并將數(shù)據(jù)存儲在MySQL中
祝您學(xué)習(xí)愉快!再見。
進一步閱讀
使用Spring Security和JWT認證保護Spring Boot應(yīng)用程序
Spring Data JPA參考文檔
Spring Boot分頁和排序示例
完整的CRUD應(yīng)用程序:
Vue + Spring Boot示例
Angular 8 + Spring Boot示例
Angular 10 + Spring Boot示例
Angular 11 + Spring Boot示例
Angular 12 + Spring Boot示例
Angular 13 + Spring Boot示例
Angular 14 + Spring Boot示例
Angular 15 + Spring Boot示例
Angular 16 + Spring Boot示例
React + Spring Boot示例
源代碼
您可以在Github上找到本教程的完整源代碼。
一對多關(guān)系:使用Hibernate和Spring Boot的JPA一對多示例
多對多關(guān)系:在Spring Boot中使用Hibernate的JPA多對多示例
您可以將此實現(xiàn)應(yīng)用于以下教程:
Spring JPA + H2示例
Spring JPA + MySQL示例
Spring JPA + PostgreSQL示例
Spring JPA + Oracle示例
Spring JPA + SQL Server示例
更多派生查詢請參閱:
Spring Boot中的JPA Repository查詢示例
文檔:Spring Boot + Swagger 3示例(使用OpenAPI 3)文章來源:http://www.zghlxwxcb.cn/article/697.html
緩存:Spring Boot Redis緩存示例文章來源地址http://www.zghlxwxcb.cn/article/697.html
到此這篇關(guān)于JPA/Hibernate One To One在Spring Boot中的單向映射的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!