這是JPA 關(guān)系映射
系列教程的第一篇:JPA One-To-One 外鍵關(guān)系映射
JPA 關(guān)系映射系列(SPring Boot, Postgresql):
- JPA One-To-One 外鍵 關(guān)系映射
- JPA One-To-Many 關(guān)系映射
- JPA Many-To-Many 關(guān)系映射
為了完成這邊教程你所需要的工具如下:
Spring Data JPA
Spring Boot
Postgresql 數(shù)據(jù)庫
如何在ubuntu下安裝Postgresql以及圖形界面客戶端PgAdmin3践剂,請(qǐng)戳我
項(xiàng)目依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
One To One 關(guān)系:
在這里 我們使用 book 以及 book_detail 來描述一對(duì)一的關(guān)系。
下面我們來看看如果使用 Spring Boot 來定義實(shí)體映射:
package com.example.demo;
import javax.persistence.*;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "book_detail")
private BookDetail bookDetail;
public Book() {
}
public Book(String name, BookDetail bookDetail) {
this.name = name;
this.bookDetail = bookDetail;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BookDetail getBookDetail() {
return bookDetail;
}
public void setBookDetail(BookDetail bookDetail) {
this.bookDetail = bookDetail;
}
}
package com.example.demo;
import javax.persistence.*;
@Entity
public class BookDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long numberOfPages;
@OneToOne(cascade = CascadeType.ALL,mappedBy = "bookDetail")
private Book book;
public BookDetail() {
}
public BookDetail(long numberOfPages, Book book) {
this.numberOfPages = numberOfPages;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getNumberOfPages() {
return numberOfPages;
}
public void setNumberOfPages(long numberOfPages) {
this.numberOfPages = numberOfPages;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
在 Spring Boot 中所有的實(shí)體類都需要用注解 @Entity
來標(biāo)記,被這個(gè)注解標(biāo)記的實(shí)體類將會(huì)在數(shù)據(jù)庫中生成一個(gè)對(duì)應(yīng)的 Table。表名通常情況下以類名作為默認(rèn)表名, 當(dāng)然你也可以指定一個(gè)已經(jīng)存在的表來對(duì)應(yīng)這個(gè)實(shí)體類硼瓣。比如:
@Entity
@Table(name ="book_specific")
@Id
標(biāo)記一個(gè)字段為 主鍵(Primary Key). 當(dāng)一個(gè)主鍵字段被定義的時(shí)候,主鍵的值將會(huì)被 ObjectDB 自動(dòng)注入到這個(gè)字段中。
@GeneratedValue
通常情況下,關(guān)系數(shù)據(jù)庫會(huì)為每一個(gè)數(shù)據(jù)庫維護(hù)一個(gè)特殊的全局?jǐn)?shù)字生成器置谦。這個(gè)數(shù)字生成器會(huì)自動(dòng)為每一個(gè)沒有定義主鍵字段的實(shí)體對(duì)象生成一個(gè) ID堂鲤。
@Id
@GeneratedValue
private long id;
在commit
期間, AUTO策略使用全局?jǐn)?shù)字生成器為每個(gè)新的實(shí)體對(duì)象生成主鍵。這些生成的值在數(shù)據(jù)庫級(jí)別是唯一的,不會(huì)被
回收,這些主鍵值被多個(gè)表共享 媒峡。
簡(jiǎn)單的來解釋一下這里所謂的主鍵值被多個(gè)表共享是什么意思瘟栖,
例如我們現(xiàn)在一個(gè)數(shù)據(jù)庫,里面有兩張表 A1 跟 A2谅阿。
A1里面有三條數(shù)據(jù) A11 A12 A13
A2里面有三條數(shù)據(jù) A21 A22 A23半哟,
每一個(gè)表的每一條數(shù)據(jù)的主鍵id通常情況是自增加的,
所以理論上的A11 的主鍵id=1签餐,A12的主鍵id=2寓涨,A13的主鍵id=3
所以理論上的A21 的主鍵id=1,A22的主鍵id=2氯檐,A23的主鍵id=3
但是如果你使用AUTO的策略的話戒良,主鍵的增加是根據(jù)你將數(shù)值插入數(shù)據(jù)庫的順序來決定的。
如果你先在A1表中插入 A11 A12 A13三條數(shù)據(jù)冠摄,然后在A2表中插入A21 A22 A23三條數(shù)據(jù)
那們的主鍵的id就會(huì)變成
A11 的主鍵id=1糯崎,A12的主鍵id=2,A13的主鍵id=3
A21 的主鍵id=4河泳,A22的主鍵id=5沃呢,A23的主鍵id=6
如果你兩個(gè)表是相互交替的插入數(shù)據(jù),那么主鍵的生成也是相互的交替的拆挥,
這就導(dǎo)致了每一張表的主鍵ID都不一定是從1開始增加的
這取決你插入數(shù)據(jù)的順序
這就是所謂的主鍵值被多個(gè)表共享
所以通常情況下我們?yōu)榱吮WC每一個(gè)表的主鍵ID都從一開始增加薄霜,我們需要設(shè)置
@GeneratedValue(strategy=GenerationType.IDENTITY
關(guān)于JPA中主鍵的生成策略,一共有四種模式:
@GeneratedValue(strategy=GenerationType.AUTO)
@GeneratedValue(strategy=GenerationType.IDENTITY)
@GeneratedValue(strategy=GenerationType.SEQUENCE)
@GeneratedValue(strategy=GenerationType.TABLE)
如果大家對(duì)這方面感興趣的話,請(qǐng)點(diǎn)擊關(guān)注樓主黄锤,或者給樓主留言搪缨,樓主會(huì)考慮寫一篇專門的文章來講解JPA中主鍵的生成策略。
@OneToOne
在兩個(gè)實(shí)體之間定義了一對(duì)一的關(guān)系,這里Book與BookDetail是一對(duì)一的關(guān)系鸵熟。
mappedBy=”bookDetail”
定義了兩張表之間由誰來維護(hù)彼此的關(guān)系副编。這里表示由Book這張表來維護(hù)兩者之間的關(guān)系。這里
BookDetail類中的mappeBy的值 bookDetail
是實(shí)體類Book中對(duì)BookDetail的引用字段名流强。當(dāng)我們定義了mappedBy屬性之后, JPA就會(huì)在Book表中新增加一列bookDetail,它的值就是BookDetail表中的主鍵值痹届。
@JoinColumn
通常情況下,如果不指定這個(gè)屬性打月,那么JPA會(huì)默認(rèn)幫你在Book類中新增一列以bookDetail為默認(rèn)名字队腐,如果你指定了這個(gè)屬性,那么JPA會(huì)按照你給的屬性命為新增的一列命名奏篙。
當(dāng)然了關(guān)于@JoinColumn
以及mappendBy
這兩個(gè)屬性的用法不僅僅與此柴淘,事實(shí)上他們是JPA中關(guān)系映射非常重要的注解方法。
當(dāng)然本文不在討論范圍之類秘通。在之后的OneToMany
以及 ManyToMany
教程中中我會(huì)詳細(xì)的討論這兩個(gè)屬性
當(dāng)然了如果你這么定義:
class Book(){
@OneToOne(cascade = CascadeType.ALL,mappedBy = "book")
private BookDetail bookDetail;
}
class BookDetail(){
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "book_id")
private Book book;
}
也是可以的为严,只不過這回在BookDetail表中新增加的一列,他的值就是Book表中的主鍵值肺稀。 這一次有BookDetail來維護(hù)兩個(gè)表之間的關(guān)系第股。
在OneToOne
關(guān)系中,誰來維護(hù)關(guān)系并不是特別重要话原,因?yàn)閮烧呤堑葍r(jià)的夕吻,你可以引用我的主鍵,同樣我也可以引用你的主鍵繁仁。 因此這跟OneToMany
和 ManyToMany
是不一樣的涉馅。 例如在OneToMany
中,關(guān)系的維護(hù)方永遠(yuǎn)是Many的那一方改备。比如班級(jí)跟學(xué)生就是一對(duì)多的關(guān)系控漠,這個(gè)時(shí)候就必須要在學(xué)生表中引用班級(jí)的主鍵作為一列。 因?yàn)槲覀兊谋硎遣荒鼙4婕系年P(guān)系悬钳。 在班級(jí)表中盐捷,每一個(gè)班級(jí)都有很多學(xué)生,這是一個(gè)集合默勾,這沒有辦法用數(shù)據(jù)庫在保存碉渡。 但是在學(xué)生表中,每一個(gè)學(xué)生都對(duì)應(yīng)唯一一個(gè)班級(jí)母剥。 所以我們可以引用班級(jí)的主鍵作為學(xué)生表的外鍵滞诺。 這就是為什么在一對(duì)多一方形导,關(guān)系的維護(hù)者永遠(yuǎn)是Many的那一方了。
對(duì)于OneToMany
和 ManyToMany
的關(guān)系映射习霹,是這一系列教程的后兩篇文章朵耕。 稍后我會(huì)詳細(xì)解釋這兩種映射關(guān)系
下一步我們需要給Spring Boot配置數(shù)據(jù)庫,以便它能夠連接到我們的數(shù)據(jù)庫:
在Spring Boot的application.properties
中添加以下語句:
# ===============================
# = DATA SOURCE
# ===============================
# Set here configurations for the database connection
spring.datasource.url=jdbc:postgresql://localhost:5432/jpa1
spring.datasource.username=postgres
spring.datasource.password=9145190618
spring.datasource.driver-class-name=org.postgresql.Driver
# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
# ===============================
# = JPA / HIBERNATE
# ===============================
# Show or not log for each sql query
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, update): with "create-drop" the database
# schema will be automatically created afresh for every start of application
spring.jpa.hibernate.ddl-auto=create-drop
# Naming strategy
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
# Allows Hibernate to generate SQL optimized for a particular DBMS
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
#There is also a /shutdown endpoint, but its only visible by default via JMX. To enable it as an HTTP endpoint, add endpoints.shutdown.enabled=true to your application.properties file.
endpoints.shutdown.enabled=true
如果你沒有實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫,那么你需要手動(dòng)用圖形GUI或者在命令行中創(chuàng)建數(shù)據(jù)庫
pring.datasource.url=jdbc:postgresql://localhost:5432/jpa
指明了數(shù)據(jù)庫為jpa,監(jiān)聽端口為5432,這是Postgresql數(shù)據(jù)庫的默認(rèn)監(jiān)聽端口。
這里pring.jpa.hibernate.ddl-auto
可以是none,update,create,create-drop,有關(guān)詳細(xì)信息,請(qǐng)參考Hibernate文檔淋叶。
none: 這的默認(rèn)值,不改變數(shù)據(jù)庫結(jié)構(gòu)阎曹。(一般在部署階段,可以使用這個(gè)模式)
update: Hibernate根據(jù)給定的實(shí)體結(jié)構(gòu)更改數(shù)據(jù)庫。
create: 每次創(chuàng)建數(shù)據(jù)庫,但不要在關(guān)閉時(shí)丟棄煞檩。
create-drop: 創(chuàng)建數(shù)據(jù)庫,然后在SessionFactory關(guān)閉時(shí)將其刪除(在開發(fā)階段,可以使用這個(gè)方式,因?yàn)槊看纬绦騿?dòng)時(shí)候 都會(huì)刪除已有的數(shù)據(jù),在重新創(chuàng)建)处嫌。我們這里從創(chuàng)建開始,因?yàn)槲覀冞€沒有數(shù)據(jù)庫結(jié)構(gòu)。第一次運(yùn)行后,我們可以根據(jù)程序要求將其切換為更新或無更新斟湃。當(dāng)您想對(duì)數(shù)據(jù)庫結(jié)構(gòu)進(jìn)行一些更改時(shí),請(qǐng)使用更新熏迹。
我們運(yùn)行程序的時(shí)候,我們可以看到:數(shù)據(jù)庫中已經(jīng)自動(dòng)創(chuàng)建了兩張表了:
最右邊是外建,其值為book表中主鍵的id值
Spring Data JPA 包含了一些內(nèi)置的Repository實(shí)現(xiàn)了一些常用的操作數(shù)據(jù)庫的功能凝赛, 比如:findOne注暗, findAll, save 等等哄酝。下面根據(jù)上面兩個(gè)實(shí)體類友存,我們?cè)谕ㄟ^JPA提供給我們的Repository來簡(jiǎn)單的操作我們的數(shù)據(jù)庫。
我們所需要做的事情就是為我們的實(shí)體類 Book 和 BookDetail 建立對(duì)應(yīng)的Repository接口:
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookDetailRepository extends JpaRepository<BookDetail, Long> {
}
下面我們運(yùn)行APP來使用Repository新建數(shù)據(jù)庫數(shù)據(jù):
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
@Autowired
BookRepository bookRepository;
@Autowired
BookDetailRepository bookDetailRepository;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... strings) throws Exception {
List<Book> books = new ArrayList<>();
books.add(new Book("Book A", new BookDetail(49)));
books.add(new Book("Book B", new BookDetail(59)));
books.add(new Book("Book C", new BookDetail(69)));
bookRepository.save(books);
}
}
運(yùn)行結(jié)果:
如果喜歡這篇文章陶衅,請(qǐng)給樓主點(diǎn)個(gè)贊或者加個(gè)關(guān)注。下次給大家?guī)淼氖荍PA One-To-Many 關(guān)系映射 以 及JPA Many-To-Many 關(guān)系映射
源碼已經(jīng)分享在Github