JPA定義了一系列對(duì)象持久化的標(biāo)準(zhǔn)。
(零)配置
Maven導(dǎo)入以下包:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
在application.yml進(jìn)行以下配置:
spring:
profiles:
active: dev
#進(jìn)行MySQL數(shù)據(jù)庫(kù)配置
datasource:
#數(shù)據(jù)庫(kù)驅(qū)動(dòng)名
#MySQL8以前版本使用com.mysql.jdbc.Driver
#MySQL8以后版本使用下列驅(qū)動(dòng):
driver-class-name: com.mysql.cj.jdbc.Driver
#數(shù)據(jù)庫(kù)要鏈接的url
#serverTimezone不是必選項(xiàng),但某些時(shí)候會(huì)報(bào)錯(cuò),詳細(xì)原因自行百度
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
#用戶(hù)名與密碼
username: root
password: s14568460s
#JPA配置
jpa:
hibernate:
#該項(xiàng)決定項(xiàng)目運(yùn)行時(shí)以何種方式建表。
#create:?jiǎn)?dòng)時(shí)刪數(shù)據(jù)庫(kù)中的表,然后創(chuàng)建,退出時(shí)不刪除數(shù)據(jù)表
#create-drop:?jiǎn)?dòng)時(shí)刪數(shù)據(jù)庫(kù)中的表,然后創(chuàng)建,退出時(shí)刪除數(shù)據(jù)表。如果表不存在報(bào)錯(cuò)
#update:如果啟動(dòng)時(shí)表格式不一致則更新表,原有數(shù)據(jù)保留
#validate:項(xiàng)目啟動(dòng)表結(jié)構(gòu)進(jìn)行校驗(yàn),如果不一致則報(bào)錯(cuò)
ddl-auto: create
#JPA更新某個(gè)版本后,使用@GeneratedValue時(shí)做不到自增,加上這個(gè)就好了
#看不懂但是大為震撼
#也可以在@GeneratedValue中設(shè)置strategy = GenerationType.IDENTITY,就可以忽略下面被注釋掉的這句了
#use-new-id-generator-mappings: false
#在控制臺(tái)顯示SQL語(yǔ)句
show-sql: true
(一)JPA的簡(jiǎn)單使用(CRUD)
可以看下面兩個(gè)文章理解,比我講得好多了:springDataJpa的入門(mén)操作(基本CRUD)_tangiwang的博客-CSDN博客
Spring Boot 2.x 之 Spring Data JPA, Hibernate 5 - CokeCode - 博客園 (cnblogs.com)
博客園的那篇文章講各個(gè)注解講的很細(xì),強(qiáng)烈建議閱讀。
記得每次在方法內(nèi)執(zhí)行完增改后要對(duì)JpaRepository類(lèi)型的對(duì)象調(diào)用save方法。save的參數(shù)為new的表。刪直接調(diào)用dao層的delete方法就好。
(二)@Query注解
@Query注解可以指定JpaRepository方法對(duì)應(yīng)的查詢(xún)HSQL。
@Query中的參數(shù)HQL默認(rèn)是面向?qū)ο蟮恼Z(yǔ)句,與真實(shí)SQL語(yǔ)句有偏差。在該注解下,應(yīng)當(dāng)使用實(shí)體類(lèi)的類(lèi)名和屬性名,而非SQL庫(kù)中的表名和字段名。
想要在@Query輸入原生SQL語(yǔ)句,應(yīng)將其中參數(shù)nativeQuery設(shè)置為true。
@Query也可以實(shí)現(xiàn)更新與刪除:@Query 自定義查詢(xún)及更新刪除_Aimyone的博客-CSDN博客_query 刪除,但實(shí)現(xiàn)不了insert: springData學(xué)習(xí)(二)@Query注解詳解_java_chegnxuyuan的博客-CSDN博客。
(三)對(duì)象關(guān)聯(lián)關(guān)系
對(duì)象之間的關(guān)聯(lián)有一對(duì)一、一對(duì)多和多對(duì)多。
一對(duì)一:一個(gè)人對(duì)應(yīng)一個(gè)ID
一對(duì)多:一個(gè)班對(duì)多個(gè)人/一個(gè)人對(duì)多門(mén)課
多對(duì)多:多個(gè)項(xiàng)目對(duì)應(yīng)多個(gè)員工
抽象成數(shù)據(jù)庫(kù)中的表,我們不難發(fā)現(xiàn)對(duì)象關(guān)聯(lián)關(guān)系的實(shí)用性,如某表中的字段可能對(duì)應(yīng)另一個(gè)表整表。
接下來(lái)講講具體操作。
0、鍵的鏈接
見(jiàn)下文:數(shù)據(jù)庫(kù)設(shè)計(jì)(一對(duì)一、一對(duì)多、多對(duì)多)皮一下很開(kāi)心的猴頭-CSDN博客數(shù)據(jù)庫(kù)一對(duì)一
1、一對(duì)多/多對(duì)一
假設(shè)有以下兩表:
Person:id(主鍵),name,age
Class:classId(主鍵),className
接下來(lái)對(duì)二者的classId進(jìn)行鏈接。
//在Class類(lèi)下,建立一個(gè)屬性personList,使得一個(gè)班級(jí)對(duì)應(yīng)多人
@OneToMany(mappedBy = "class") //mappedBy用于創(chuàng)建一對(duì)多的映射關(guān)系,其值為Person類(lèi)中對(duì)應(yīng)的屬性名稱(chēng)
//mappedBy聲明自己不是關(guān)系維護(hù)端,一切由對(duì)方維護(hù)
private List<Person> personList;
//在Person類(lèi)下,建立一個(gè)class屬性,使得多人對(duì)應(yīng)一個(gè)班級(jí)
@ManyToOne
@JoinColumn(name = "cid") //指定外鍵列,name默認(rèn)值為變量名
private Class class;
(1)各自的增更,與之前的CRUD操作相同。
(2)主鍵表查詢(xún)(查詢(xún)Class中personList):有下面兩種方法。
①設(shè)置立即加載:調(diào)用classRespository.findAll(),在默認(rèn)情況下實(shí)行懶加載策略,即不查詢(xún)外部鏈接的庫(kù),只查詢(xún)自身的字段。針對(duì)此,我們可以在@OneToMany中將fetch屬性(默認(rèn)值為FetchType.LAZY)改為FetchType.EAGER。
②延長(zhǎng)Session生命周期:聲明事務(wù)@Transactional。
(3)外鍵表查詢(xún)(查詢(xún)Person中的class):與CRUD操作相同,因?yàn)槠錇榱⒓醇虞d,并非上述的懶加載。
(4)刪除主鍵表(刪除Class類(lèi)中某一項(xiàng)):直接刪除會(huì)報(bào)錯(cuò),因?yàn)镻erson類(lèi)中的class還在鏈接。有以下解決方法:
①級(jí)聯(lián)刪除:在Class類(lèi)中的OneToMany中對(duì)屬性cascade(默認(rèn)空)設(shè)置CascadeType.ALL。關(guān)于級(jí)聯(lián)的具體操作可以看Hibernate @OneToMany 及 @Cascade級(jí)聯(lián)操作 - 云+社區(qū) - 騰訊云 (tencent.com)。
②斷開(kāi)鍵的連接,再刪除主鍵表:先用update將外鍵設(shè)置為null,斷開(kāi)主外鍵連接,再執(zhí)行刪除操作(會(huì)報(bào)no session,需添事務(wù)延長(zhǎng)session周期)。
(5)刪除外鍵表(刪除Person類(lèi)中某一項(xiàng)):直接刪除即可。
這里有一個(gè)問(wèn)題:@OneToMany中fetch設(shè)置為FetchType.EAGER時(shí)會(huì)導(dǎo)致只輸出兩條SELECT語(yǔ)句而無(wú)法刪除Person中的某項(xiàng),解決方法是刪除這句或在@ManyToOne中設(shè)置此項(xiàng)為L(zhǎng)AZY(摘自JPA關(guān)于fetch=FetchType.EAGER級(jí)聯(lián)刪除的問(wèn)題_C.-CSDN博客
)。
(6)添加主鍵表信息同時(shí)添加外鍵表信息(添加Class同時(shí)添加Person):注意雙向維護(hù),主鍵和外鍵都需要添加。后續(xù)更新:若報(bào)錯(cuò),應(yīng)關(guān)注瞬時(shí)態(tài)對(duì)象與持久態(tài)對(duì)象,下文有提到。
2、多對(duì)多
假設(shè)有以下兩表:
Subject: sId(主鍵), sName
Person: pId(主鍵), pName
接下來(lái)對(duì)兩表進(jìn)行鏈接。注意的是,只應(yīng)當(dāng)指定一個(gè)表進(jìn)行主鍵的維護(hù),防止主鍵重復(fù)。
//在Subject類(lèi)下,建立一個(gè)屬性pList,使得一門(mén)課對(duì)應(yīng)多人
@ManyToMany(mappedBy = "sList") //Subject類(lèi)放棄外鍵維護(hù)權(quán)
private List<Person> pList = new ArrayList<>();
//在Person類(lèi)下,建立一個(gè)屬性sList,使得一人對(duì)應(yīng)多門(mén)課
@ManyToMany
@JoinTable(name = "sub_person",
uniqueConstraints = {@UniqueConstraint(columnNames = {"p_id", "s_id"})},
joinColumns = {@JoinColumn(name="p_id", referencedColumnName="pId")},
inverseJoinColumns = {@JoinColumn(name="s_id", referencedColumnName="sId")})
//@JoinTable描述當(dāng)前實(shí)體類(lèi)與中間表之間的關(guān)系
//name值為中間表名
//uniqueConstraints中間兩個(gè)字段名(非屬性名)組合起來(lái)組成聯(lián)合主鍵,其余一大坨照抄(注解相互嵌套不想解釋了)
//joinColumns表示當(dāng)前對(duì)象在中間表中的外鍵,name為外鍵名,referencedColumnName為參照主鍵表的主鍵名稱(chēng)
//inverseJoinColumns表示對(duì)方在中間表的外鍵,與joinColumns類(lèi)似,不再贅述。
private List<Subject> sList = new ArrayList<>();
Subject通過(guò)mappedBy屬性的設(shè)置放棄了外鍵維護(hù)權(quán)利,以后就由Person表維護(hù)兩表關(guān)系了。
看著復(fù)雜,其實(shí)就是一對(duì)多的補(bǔ)充擴(kuò)展。
(不過(guò)確實(shí)好難理解55555)
接下來(lái)是CRUD。
(1)添加操作:
首先new幾個(gè)Subject對(duì)象和Person對(duì)象。
在Subject類(lèi)直接添加Person到pList并對(duì)subjectDao進(jìn)行save:能正常運(yùn)行只能添加Subject,中間表和Person無(wú)法添加,因?yàn)閜List加了mappedBy屬性,不負(fù)責(zé)外鍵維護(hù)。被維護(hù)的表不需要向鍵內(nèi)添加任何數(shù)據(jù)。
在Person類(lèi)直接添加Subject到sList并對(duì)personDao進(jìn)行save:編譯錯(cuò)誤。現(xiàn)在Subject沒(méi)有受session管理而Person受session管理,直接save不能成功(具體原因:瞬時(shí)態(tài)對(duì)象異常。僅進(jìn)行personDao.save(person)時(shí),person為持久態(tài)對(duì)象,subject仍保留為瞬時(shí)態(tài)對(duì)象)。
有兩種方法實(shí)現(xiàn),①若既要添加新學(xué)生又要建立學(xué)生與課的關(guān)系,就把Subject交給session管理,即:進(jìn)行personDao.save(person)前,進(jìn)行subjectDao.save(subject)(將subject變?yōu)槌志脩B(tài)對(duì)象);②若表內(nèi)有學(xué)生,僅需建立學(xué)生與課的關(guān)系,可通過(guò)subjectDao.getOne取出對(duì)象并交給屬性引用,即Subject sub = subjectDao.getOne(1)(Dao從數(shù)據(jù)庫(kù)查詢(xún)出來(lái)sub)然后再直接添加。
(2)更新操作:僅能通過(guò)有外鍵維護(hù)權(quán)利(此文為Person)進(jìn)行兩表關(guān)系的更新,各自更新各自操作(與添加極其類(lèi)似)。
(3)查詢(xún)操作:Person查詢(xún)sList,與一對(duì)多類(lèi)似,需要設(shè)置立即加載,或延長(zhǎng)session生命周期;Subject只能查詢(xún)自己的屬性,不能查詢(xún)pList。
(4)刪除操作:刪除Person將從Person表刪除學(xué)生數(shù)據(jù),并從中間表刪除學(xué)生與課的關(guān)系;直接刪除Subject會(huì)失?。赡茉谥虚g表被引用)。
3、一對(duì)一
假設(shè)有以下兩表:
Boy: boyName
Girl: girlName
對(duì)兩表進(jìn)行連接:
//在Girl類(lèi)
@OneToOne(mappedBy = "girl")
private Boy boy;
//在Boy類(lèi)
@OneToOne
@JoinColumn(name = "girlName", unique = true)
//unique表示外鍵唯一
private Girl girl;
不翻譯了,根據(jù)多對(duì)多理解就行。
(1)添加操作:正常添加即可。注意應(yīng)從維護(hù)端維護(hù)鍵的關(guān)系,且應(yīng)關(guān)注瞬時(shí)態(tài)對(duì)象與持久態(tài)對(duì)象的關(guān)系。
(2)更新操作:正常更新即可。涉及兩表關(guān)系應(yīng)從維護(hù)端更新。
(3)查詢(xún)操作:與上文一對(duì)多、多對(duì)多類(lèi)似。
(4)刪除操作:與上文類(lèi)似。注意,若被維護(hù)端有引用(Girl中boy屬性不為空),無(wú)法直接刪除。
(四)延長(zhǎng)Session生命周期
上文在被維護(hù)端進(jìn)行查詢(xún)和刪除過(guò)程中都會(huì)報(bào)no session,我們需要添加一個(gè)注解@Transactional開(kāi)啟事務(wù)。
以一對(duì)多刪除Subject為例。
public class SubjectService{
@AutoWired
SubjectDao subjectDao;
@AutoWired
PersonDao personDao;
@Transactional //開(kāi)啟事務(wù),延長(zhǎng)session生命周期到service層
public void deleteSubjectWithStudent(){
//刪除所有課程與學(xué)生
List<Subject> subjects = subjectDao.findAll;
for(Subject Subject : subjects){
List<Person> persons = subject.getPersonList();
for(Person person : persons){
personDao.delete(person);
}
subject.setPersonList(null);
subjectDao.deleteById(subject);
}
}
}
這樣可以做到將session的生命周期延長(zhǎng)到service層。但要延長(zhǎng)到view層,我們需要使用過(guò)濾器OpenSessionInViewFIlter(SpringBoot配置好了)。只需導(dǎo)入spring-boot-starter-web即可。