欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringData JPA快速上手之關(guān)聯(lián)查詢(xún)及JPQL語(yǔ)句書(shū)寫(xiě)詳解

 更新時(shí)間:2023年09月18日 11:26:25   作者:RenX000  
JPA都有SpringBoot的官方直接提供的starter,而Mybatis沒(méi)有,直到SpringBoot 3才開(kāi)始加入到官方模版中,這篇文章主要介紹了SpringData JPA快速上手,關(guān)聯(lián)查詢(xún),JPQL語(yǔ)句書(shū)寫(xiě)的相關(guān)知識(shí),感興趣的朋友一起看看吧

JPA框架

image-20230915194416786

? 在我們之前編寫(xiě)的項(xiàng)目中,我們不難發(fā)現(xiàn),實(shí)際上大部分的數(shù)據(jù)庫(kù)交互操作,到最后都只會(huì)做一個(gè)事情,那就是把數(shù)據(jù)庫(kù)中的數(shù)據(jù)映射為Java中的對(duì)象。比如我們要通過(guò)用戶(hù)名去查找對(duì)應(yīng)的用戶(hù),或是通過(guò)ID查找對(duì)應(yīng)的學(xué)生信息,在使用Mybatis時(shí),我們只需要編寫(xiě)正確的SQL語(yǔ)句就可以直接將獲取的數(shù)據(jù)映射為對(duì)應(yīng)的Java對(duì)象,通過(guò)調(diào)用Mapper中的方法就能直接獲得實(shí)體類(lèi),這樣就方便我們?cè)贘ava中數(shù)據(jù)庫(kù)表中的相關(guān)信息了。

? 但是以上這些操作都有一個(gè)共性,那就是它們都是通過(guò)某種條件去進(jìn)行查詢(xún),而最后的查詢(xún)結(jié)果,都是一個(gè)實(shí)體類(lèi),所以你會(huì)發(fā)現(xiàn)你寫(xiě)的很多SQL語(yǔ)句都是一個(gè)套路 select * from xxx where xxx=xxx ,實(shí)際上對(duì)于這種簡(jiǎn)單SQL語(yǔ)句,我們完全可以弄成一個(gè)模版來(lái)使用,那么能否有一種框架,幫我們把這些相同的套路給封裝起來(lái),直接把這類(lèi)相似的SQL語(yǔ)句給屏蔽掉,不再由我們編寫(xiě),而是讓框架自己去組合拼接。

認(rèn)識(shí)SpringData JPA

首先我們來(lái)看一個(gè)國(guó)外的統(tǒng)計(jì):

image-20230306224859664

在國(guó)外JPA幾乎占據(jù)了主導(dǎo)地位,而Mybatis并不像國(guó)內(nèi)那樣受待見(jiàn),所以你會(huì)發(fā)現(xiàn),JPA都有SpringBoot的官方直接提供的starter,而Mybatis沒(méi)有,直到SpringBoot 3才開(kāi)始加入到官方模版中。

那么,什么是JPA:

JPA(Java Persistence API)和JDBC類(lèi)似,也是官方定義的一組接口,但是它相比傳統(tǒng)的JDBC,它是為了實(shí)現(xiàn)ORM而生的,即Object-Relationl Mapping,它的作用是在關(guān)系型數(shù)據(jù)庫(kù)和對(duì)象之間形成一個(gè)映射,這樣,我們?cè)诰唧w的操作數(shù)據(jù)庫(kù)的時(shí)候,就不需要再去和復(fù)雜的SQL語(yǔ)句打交道,只要像平時(shí)操作對(duì)象一樣操作它就可以了。

其中比較常見(jiàn)的JPA實(shí)現(xiàn)有:

  • Hibernate:Hibernate是JPA規(guī)范的一個(gè)具體實(shí)現(xiàn),也是目前使用最廣泛的JPA實(shí)現(xiàn)框架之一。它提供了強(qiáng)大的對(duì)象關(guān)系映射功能,可以將Java對(duì)象映射到數(shù)據(jù)庫(kù)表中,并提供了豐富的查詢(xún)語(yǔ)言和緩存機(jī)制。
  • EclipseLink:EclipseLink是另一個(gè)流行的JPA實(shí)現(xiàn)框架,由Eclipse基金會(huì)開(kāi)發(fā)和維護(hù)。它提供了豐富的特性,如對(duì)象關(guān)系映射、緩存、查詢(xún)語(yǔ)言和連接池管理等,并具有較高的性能和可擴(kuò)展性。
  • OpenJPA:OpenJPA是Apache基金會(huì)的一個(gè)開(kāi)源項(xiàng)目,也是JPA規(guī)范的一個(gè)實(shí)現(xiàn)。它提供了高性能的JPA實(shí)現(xiàn)和豐富的特性,如延遲加載、緩存和分布式事務(wù)等。
  • TopLink:TopLink是Oracle公司開(kāi)發(fā)的一個(gè)對(duì)象關(guān)系映射框架,也是JPA規(guī)范的一個(gè)實(shí)現(xiàn)。雖然EclipseLink已經(jīng)取代了TopLink成為Oracle推薦的JPA實(shí)現(xiàn),但TopLink仍然得到廣泛使用。

JPA:具體的實(shí)現(xiàn)交給框架,我們只需要一一對(duì)應(yīng)自己設(shè)置的實(shí)體類(lèi)就行,方便很多

而實(shí)現(xiàn)JPA規(guī)范的框架一般最常用的就是 Hibernate ,它是一個(gè)重量級(jí)框架,學(xué)習(xí)難度相比Mybatis也更高一些,而SpringDataJPA也是采用Hibernate框架作為底層實(shí)現(xiàn),并對(duì)其加以封裝。

官網(wǎng):https://spring.io/projects/spring-data-jpa

使用JPA快速上手

同樣的,我們只需要導(dǎo)入stater依賴(lài)即可:

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

接著我們可以直接創(chuàng)建一個(gè)類(lèi),比如用戶(hù)類(lèi),只需要把一個(gè)賬號(hào)對(duì)應(yīng)的屬性全部定義好即可:

@Data
public class Account {
    int id;
    String username;
    String password;
}

通過(guò)注解形式在屬性上添加數(shù)據(jù)庫(kù)映射關(guān)系,這樣就能夠讓JPA知道我們的實(shí)體類(lèi)對(duì)應(yīng)的數(shù)據(jù)庫(kù)表長(zhǎng)啥樣,這里用到了很多注解:

@Data
@Entity   //表示這個(gè)類(lèi)是一個(gè)實(shí)體類(lèi)
@Table(name = "account")    //對(duì)應(yīng)的數(shù)據(jù)庫(kù)中表名稱(chēng)
public class Account {
    @GeneratedValue(strategy = GenerationType.IDENTITY)   //生成策略,這里配置為自增
    @Column(name = "id")    //對(duì)應(yīng)表中id這一列
    @Id     //此屬性為主鍵
    int id;
    @Column(name = "username")   //對(duì)應(yīng)表中username這一列
    String username;
    @Column(name = "password")   //對(duì)應(yīng)表中password這一列
    String password;
}

接著修改配置文件,把日志打印給打開(kāi):

spring:
  jpa:
    #開(kāi)啟SQL語(yǔ)句執(zhí)行日志信息
    show-sql: true
    hibernate:
      #配置為檢查數(shù)據(jù)庫(kù)表結(jié)構(gòu),沒(méi)有時(shí)會(huì)自動(dòng)創(chuàng)建
      ddl-auto: update

ddl-auto 屬性用于設(shè)置自動(dòng)表定義,可以實(shí)現(xiàn)自動(dòng)在數(shù)據(jù)庫(kù)中為我們創(chuàng)建一個(gè)表,表的結(jié)構(gòu)會(huì)根據(jù)我們定義的實(shí)體類(lèi)決定,它有以下幾種:

  • none : 不執(zhí)行任何操作,數(shù)據(jù)庫(kù)表結(jié)構(gòu)需要手動(dòng)創(chuàng)建。
  • create : 框架在每次運(yùn)行時(shí)都會(huì)刪除所有表,并重新創(chuàng)建。
  • create-drop : 框架在每次運(yùn)行時(shí)都會(huì)刪除所有表,然后再創(chuàng)建,但在程序結(jié)束時(shí)會(huì)再次刪除所有表。
  • update : 框架會(huì)檢查數(shù)據(jù)庫(kù)表結(jié)構(gòu),如果與實(shí)體類(lèi)定義不匹配,則會(huì)做相應(yīng)的修改,以保持它們的一致性。
  • validate : 框架會(huì)檢查數(shù)據(jù)庫(kù)表結(jié)構(gòu)與實(shí)體類(lèi)定義是否匹配,如果不匹配,則會(huì)拋出異常。

這個(gè)配置項(xiàng)的作用是為了避免手動(dòng)管理數(shù)據(jù)庫(kù)表結(jié)構(gòu),使開(kāi)發(fā)者可以更方便地進(jìn)行開(kāi)發(fā)和測(cè)試,但在生產(chǎn)環(huán)境中,更推薦使用數(shù)據(jù)庫(kù)遷移工具來(lái)管理表結(jié)構(gòu)的變更。

我們可以在日志中發(fā)現(xiàn),在啟動(dòng)時(shí)執(zhí)行了如下SQL語(yǔ)句:

image-20230915201350981

我們的數(shù)據(jù)庫(kù)中對(duì)應(yīng)的表已經(jīng)自動(dòng)創(chuàng)建好了

接著來(lái)看如何訪(fǎng)問(wèn)表,需要?jiǎng)?chuàng)建一個(gè)Repository實(shí)現(xiàn)類(lèi):

@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
}

注意JpaRepository有兩個(gè)泛型,前者是具體操作的對(duì)象實(shí)體,也就是對(duì)應(yīng)的表,后者是ID的類(lèi)型,接口中已經(jīng)定義了比較常用的數(shù)據(jù)庫(kù)操作。編寫(xiě)接口繼承即可,可以直接注入此接口獲得實(shí)現(xiàn)類(lèi):

@Resource
AccountRepository repository;
@Test
void contextLoads() {
    Account account = new Account();
    account.setUsername("小紅");
    account.setPassword("1234567");
    System.out.println(repository.save(account).getId());   //使用save來(lái)快速插入數(shù)據(jù),并且會(huì)返回插入的對(duì)象,如果存在自增ID,對(duì)象的自增id屬性會(huì)自動(dòng)被賦值,這就很方便了
}

執(zhí)行結(jié)果如下:

image-20230915201527776

同時(shí),查詢(xún)操作也很方便:

@Test
void contextLoads() {
  	//默認(rèn)通過(guò)通過(guò)ID查找的方法,并且返回的結(jié)果是Optional包裝的對(duì)象,非常人性化
    repository.findById(1).ifPresent(System.out::println);
}

得到結(jié)果為:

image-20230915195547388

包括常見(jiàn)的一些計(jì)數(shù)、刪除操作等都包含在里面,僅僅配置應(yīng)該接口就能完美實(shí)現(xiàn)增刪改查:

image-20230721000050875

我們發(fā)現(xiàn),使用了JPA之后,整個(gè)項(xiàng)目的代碼中沒(méi)有出現(xiàn)任何的SQL語(yǔ)句,可以說(shuō)是非常方便了,JPA依靠我們提供的注解信息自動(dòng)完成了所有信息的映射和關(guān)聯(lián)。

相比Mybatis,JPA幾乎就是一個(gè)全自動(dòng)的ORM框架,而Mybatis則頂多算是半自動(dòng)ORM框架。

方法名稱(chēng)拼接自定義SQL

雖然接口預(yù)置的方法使用起來(lái)非常方便,但是如果我們需要進(jìn)行條件查詢(xún)等操作或是一些判斷,就需要自定義一些方法來(lái)實(shí)現(xiàn),同樣的,我們不需要編寫(xiě)SQL語(yǔ)句,而是通過(guò)方法名稱(chēng)的拼接來(lái)實(shí)現(xiàn)條件判斷,這里列出了所有支持的條件判斷名稱(chēng):

屬性拼接方法名稱(chēng)示例執(zhí)行的語(yǔ)句
DistinctfindDistinctByLastnameAndFirstnameselect distinct … where x.lastname = ?1 and x.firstname = ?2
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname , findByFirstnameIs , findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNull,NullfindByAge(Is)Null… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(參數(shù)與附加 % 綁定)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(參數(shù)與前綴 % 綁定)
ContainingfindByFirstnameContaining… where x.firstname like ?1(參數(shù)綁定以 % 包裝)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
TruefindByActiveTrue… where x.active = true
FalsefindByActiveFalse… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)

比如我們想要實(shí)現(xiàn)根據(jù)用戶(hù)名模糊匹配查找用戶(hù):

@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
    //按照表中的規(guī)則進(jìn)行名稱(chēng)拼接,不用刻意去記,IDEA會(huì)有提示
    List<Account> findAllByUsernameLike(String str);
}

測(cè)試一下:

@Test
void contextLoads() {
    repository.findAllByUsernameLike("%明%").forEach(System.out::println);
}

image-20230721001035279

同時(shí)根據(jù)用戶(hù)名和ID一起查詢(xún):

@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
    List<Account> findAllByUsernameLike(String str);
    Account findByIdAndUsername(int id, String username);
    //也可以使用Optional類(lèi)進(jìn)行包裝,Optional<Account> findByIdAndUsername(int id, String username);
}
@Test
void contextLoads() {
    System.out.println(repository.findByIdAndUsername(1, "小明"));
}

想判斷數(shù)據(jù)庫(kù)中是否存在某個(gè)ID的用戶(hù):

@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
    List<Account> findAllByUsernameLike(String str);
    Account findByIdAndUsername(int id, String username);
    //使用exists判斷是否存在
    boolean existsAccountById(int id);
}

注意自定義條件操作的方法名稱(chēng)一定要遵循規(guī)則,不然會(huì)出現(xiàn)異常:

Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract  ...

有了這些操作,我們?cè)诰帉?xiě)一些簡(jiǎn)單SQL的時(shí)候就很方便了,用久了甚至直接忘記SQL怎么寫(xiě)。

關(guān)聯(lián)查詢(xún)

在實(shí)際開(kāi)發(fā)中,比較常見(jiàn)的場(chǎng)景還有關(guān)聯(lián)查詢(xún),也就是我們會(huì)在表中添加一個(gè)外鍵字段,而此外鍵字段又指向了另一個(gè)表中的數(shù)據(jù),當(dāng)我們查詢(xún)數(shù)據(jù)時(shí),可能會(huì)需要將關(guān)聯(lián)數(shù)據(jù)也一并獲取,比如我們想要查詢(xún)某個(gè)用戶(hù)的詳細(xì)信息,一般用戶(hù)簡(jiǎn)略信息會(huì)單獨(dú)存放一個(gè)表,而用戶(hù)詳細(xì)信息會(huì)單獨(dú)存放在另一個(gè)表中。當(dāng)然,除了用戶(hù)詳細(xì)信息之外,可能在某些電商平臺(tái)還會(huì)有用戶(hù)的購(gòu)買(mǎi)記錄、用戶(hù)的購(gòu)物車(chē),交流社區(qū)中的用戶(hù)帖子、用戶(hù)評(píng)論等,這些都是需要根據(jù)用戶(hù)信息進(jìn)行關(guān)聯(lián)查詢(xún)的內(nèi)容。

在JPA中,每張表實(shí)際上就是一個(gè)實(shí)體類(lèi)的映射,而表之間的關(guān)聯(lián)關(guān)系,也可以看作對(duì)象之間的依賴(lài)關(guān)系,比如用戶(hù)表中包含了用戶(hù)詳細(xì)信息的ID字段作為外鍵,那么實(shí)際上就是用戶(hù)表實(shí)體中包括了用戶(hù)詳細(xì)信息實(shí)體對(duì)象:

@Data
@Entity
@Table(name = "users_detail")
public class AccountDetail {
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    int id;
    @Column(name = "address")
    String address;
    @Column(name = "email")
    String email;
    @Column(name = "phone")
    String phone;
    @Column(name = "real_name")
    String realName;
}

而用戶(hù)信息和用戶(hù)詳細(xì)信息之間形成了一對(duì)一的關(guān)系,那么這時(shí)直接在類(lèi)中指定這種關(guān)系:

@Data
@Entity
@Table(name = "users")
public class Account {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    @Id
    int id;
    @Column(name = "username")
    String username;
    @Column(name = "password")
    String password;
    @JoinColumn(name = "detail_id")   //指定存儲(chǔ)外鍵的字段名稱(chēng)
    @OneToOne    //聲明為一對(duì)一關(guān)系
    AccountDetail detail;
}

在修改實(shí)體類(lèi)信息后,我們發(fā)現(xiàn)在啟動(dòng)時(shí)也進(jìn)行了更新,日志如下:

image-20230915202053963

接著往用戶(hù)詳細(xì)信息中添加一些數(shù)據(jù),一會(huì)可以直接進(jìn)行查詢(xún):

@Test
void pageAccount() {
    repository.findById(1).ifPresent(System.out::println);
}

查詢(xún)后,可以發(fā)現(xiàn),得到如下結(jié)果:

image-20230915202154380

也就是,在建立關(guān)系之后,我們查詢(xún)Account對(duì)象時(shí),會(huì)自動(dòng)將關(guān)聯(lián)數(shù)據(jù)的結(jié)果也一并進(jìn)行查詢(xún)。

那要是我們只想要Account的數(shù)據(jù),不想要用戶(hù)詳細(xì)信息數(shù)據(jù)怎么辦呢?我希望在我要用的時(shí)候再獲取詳細(xì)信息,這樣可以節(jié)省一些網(wǎng)絡(luò)開(kāi)銷(xiāo),我們可以設(shè)置懶加載,這樣只有在需要時(shí)才會(huì)向數(shù)據(jù)庫(kù)獲?。?/p>

@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY)    //將獲取類(lèi)型改為L(zhǎng)AZY
AccountDetail detail;

接著測(cè)試一下:

@Transactional   //懶加載屬性需要在事務(wù)環(huán)境下獲取,因?yàn)閞epository方法調(diào)用完后Session會(huì)立即關(guān)閉
@Test
void pageAccount() {
    repository.findById(1).ifPresent(account -> {
        System.out.println(account.getUsername());   //獲取用戶(hù)名
        System.out.println(account.getDetail());  //獲取詳細(xì)信息(懶加載)
    });
}

接著來(lái)看看控制臺(tái)輸出了什么:

Hibernate: select account0_.id as id1_0_0_, account0_.detail_id as detail_i4_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_ from users account0_ where account0_.id=?
Test
Hibernate: select accountdet0_.id as id1_1_0_, accountdet0_.address as address2_1_0_, accountdet0_.email as email3_1_0_, accountdet0_.phone as phone4_1_0_, accountdet0_.real_name as real_nam5_1_0_ from users_detail accountdet0_ where accountdet0_.id=?
AccountDetail(id=1, address=四川省成都市青羊區(qū), email=8371289@qq.com, phone=1234567890, realName=盧本)

可以看到,獲取用戶(hù)名之前,并沒(méi)有去查詢(xún)用戶(hù)的詳細(xì)信息,而是當(dāng)我們獲取詳細(xì)信息時(shí)才進(jìn)行查詢(xún)并返回AccountDetail對(duì)象。

也可以在添加數(shù)據(jù)時(shí),利用實(shí)體類(lèi)之間的關(guān)聯(lián)信息,一次性添加兩張表的數(shù)據(jù):需要稍微修改一下級(jí)聯(lián)關(guān)聯(lián)操作設(shè)定:

@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) //設(shè)置關(guān)聯(lián)操作為ALL
AccountDetail detail;
  • ALL:所有操作都進(jìn)行關(guān)聯(lián)操作
  • PERSIST:插入操作時(shí)才進(jìn)行關(guān)聯(lián)操作
  • REMOVE:刪除操作時(shí)才進(jìn)行關(guān)聯(lián)操作
  • MERGE:修改操作時(shí)才進(jìn)行關(guān)聯(lián)操作

可以多個(gè)并存,接著進(jìn)行一下測(cè)試:

@Test
void addAccount(){
    Account account = new Account();
    account.setUsername("Nike");
    account.setPassword("123456");
    AccountDetail detail = new AccountDetail();
    detail.setAddress("重慶市渝中區(qū)解放碑");
    detail.setPhone("1234567890");
    detail.setEmail("73281937@qq.com");
    detail.setRealName("張三");
  	account.setDetail(detail);
    account = repository.save(account);
    System.out.println("插入時(shí),自動(dòng)生成的主鍵ID為:"+account.getId()+",外鍵ID為:"+account.getDetail().getId());
}

可以看到日志結(jié)果:

Hibernate: insert into users_detail (address, email, phone, real_name) values (?, ?, ?, ?)
Hibernate: insert into users (detail_id, password, username) values (?, ?, ?)
插入時(shí),自動(dòng)生成的主鍵ID為:6,外鍵ID為:3

結(jié)束后會(huì)發(fā)現(xiàn)數(shù)據(jù)庫(kù)中兩張表都同時(shí)存在數(shù)據(jù)。

接著我們來(lái)看一對(duì)多關(guān)聯(lián),比如每個(gè)用戶(hù)的成績(jī)信息:

@JoinColumn(name = "uid")  //注意這里的name指的是Score表中的uid字段對(duì)應(yīng)的就是當(dāng)前的主鍵,會(huì)將uid外鍵設(shè)置為當(dāng)前的主鍵
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)   //在移除Account時(shí),一并移除所有的成績(jī)信息,依然使用懶加載
List<Score> scoreList;
@Data
@Entity
@Table(name = "users_score")   //成績(jī)表,注意只存成績(jī),不存學(xué)科信息,學(xué)科信息id做外鍵
public class Score {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    @Id
    int id;
    @OneToOne   //一對(duì)一對(duì)應(yīng)到學(xué)科上
    @JoinColumn(name = "cid")
    Subject subject;
    @Column(name = "socre")
    double score;
    @Column(name = "uid")
    int uid;
}
@Data
@Entity
@Table(name = "subjects")   //學(xué)科信息表
public class Subject {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cid")
    @Id
    int cid;
    @Column(name = "name")
    String name;
    @Column(name = "teacher")
    String teacher;
    @Column(name = "time")
    int time;
}

在數(shù)據(jù)庫(kù)中填寫(xiě)相應(yīng)數(shù)據(jù),接著我們就可以查詢(xún)用戶(hù)的成績(jī)信息了:

@Transactional
@Test
void test() {
    repository.findById(1).ifPresent(account -> {
        account.getScoreList().forEach(System.out::println);
    });
}

成功得到用戶(hù)所有的成績(jī)信息,包括得分和學(xué)科信息。

同樣的,我們還可以將對(duì)應(yīng)成績(jī)中的教師信息單獨(dú)分出一張表存儲(chǔ),并建立多對(duì)一的關(guān)系,因?yàn)槎嚅T(mén)課程可能由同一個(gè)老師教授(千萬(wàn)別搞暈了,一定要理清楚關(guān)聯(lián)關(guān)系,同時(shí)也是考驗(yàn)?zāi)愕幕A(chǔ)扎不扎實(shí)):

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tid")   //存儲(chǔ)教師ID的字段,和一對(duì)一是一樣的,也會(huì)當(dāng)前表中創(chuàng)個(gè)外鍵
Teacher teacher;

接著就是教師實(shí)體類(lèi)了:

@Data
@Entity
@Table(name = "teachers")
public class Teacher {
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    int id;
    @Column(name = "name")
    String name;
    @Column(name = "sex")
    String sex;
}

最后我們?cè)龠M(jìn)行一下測(cè)試:

@Transactional
@Test
void test() {
    repository.findById(3).ifPresent(account -> {
        account.getScoreList().forEach(score -> {
            System.out.println("課程名稱(chēng):"+score.getSubject().getName());
            System.out.println("得分:"+score.getScore());
            System.out.println("任課教師:"+score.getSubject().getTeacher().getName());
        });
    });
}

成功得到多對(duì)一的教師信息。

最后我們?cè)賮?lái)看最復(fù)雜的情況,現(xiàn)在我們一門(mén)課程可以由多個(gè)老師教授,而一個(gè)老師也可以教授多個(gè)課程,那么這種情況就是很明顯的多對(duì)多場(chǎng)景,現(xiàn)在又該如何定義呢?我們可以像之前一樣,插入一張中間表表示教授關(guān)系,這個(gè)表中專(zhuān)門(mén)存儲(chǔ)哪個(gè)老師教哪個(gè)科目:

@ManyToMany(fetch = FetchType.LAZY)   //多對(duì)多場(chǎng)景
@JoinTable(name = "teach_relation",     //多對(duì)多中間關(guān)聯(lián)表
        joinColumns = @JoinColumn(name = "cid"),    //當(dāng)前實(shí)體主鍵在關(guān)聯(lián)表中的字段名稱(chēng)
        inverseJoinColumns = @JoinColumn(name = "tid")   //教師實(shí)體主鍵在關(guān)聯(lián)表中的字段名稱(chēng)
)
List<Teacher> teacher;

接著,JPA會(huì)自動(dòng)創(chuàng)建一張中間表,并自動(dòng)設(shè)置外鍵,我們就可以將多對(duì)多關(guān)聯(lián)信息編寫(xiě)在其中了。

JPQL自定義SQL語(yǔ)句

雖然SpringDataJPA能夠簡(jiǎn)化大部分?jǐn)?shù)據(jù)獲取場(chǎng)景,但是難免會(huì)有一些特殊的場(chǎng)景,需要使用復(fù)雜查詢(xún)才能夠去完成,這時(shí)你又會(huì)發(fā)現(xiàn),如果要實(shí)現(xiàn),只能用回Mybatis了,因?yàn)槲覀冃枰约菏謩?dòng)編寫(xiě)SQL語(yǔ)句,過(guò)度依賴(lài)SpringDataJPA會(huì)使得SQL語(yǔ)句不可控。

使用JPA,我們也可以像Mybatis那樣,直接編寫(xiě)SQL語(yǔ)句,不過(guò)它是JPQL語(yǔ)言,與原生SQL語(yǔ)句很類(lèi)似,但是它是面向?qū)ο蟮?,?dāng)然我們也可以編寫(xiě)原生SQL語(yǔ)句。

比如我們要更新用戶(hù)表中指定ID用戶(hù)的密碼:

@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
    @Transactional    //DML操作需要事務(wù)環(huán)境,可以不在這里聲明,但是調(diào)用時(shí)一定要處于事務(wù)環(huán)境下
    @Modifying     //表示這是一個(gè)DML操作
    @Query("update Account set password = ?2 where id = ?1") //這里操作的是一個(gè)實(shí)體類(lèi)對(duì)應(yīng)的表,參數(shù)使用?代表,后面接第n個(gè)參數(shù)
    int updatePasswordById(int id, String newPassword);
}
@Test
void updateAccount(){
    repository.updatePasswordById(1, "654321");
}

現(xiàn)在我想使用原生SQL來(lái)實(shí)現(xiàn)根據(jù)用戶(hù)名稱(chēng)修改密碼:

@Transactional
@Modifying
@Query(value = "update users set password = :pwd where username = :name", nativeQuery = true) //使用原生SQL,和Mybatis一樣,這里使用 :名稱(chēng) 表示參數(shù),當(dāng)然也可以繼續(xù)用上面那種方式。
int updatePasswordByUsername(@Param("name") String username,   //我們可以使用@Param指定名稱(chēng)
                             @Param("pwd") String newPassword);
@Test
void updateAccount(){
    repository.updatePasswordByUsername("Admin", "654321");
}

通過(guò)編寫(xiě)原生SQL,在一定程度上彌補(bǔ)了SQL不可控的問(wèn)題。

雖然JPA能夠?yàn)槲覀儙?lái)非常便捷的開(kāi)發(fā)體驗(yàn),但是正是因?yàn)樘憬萘?,尤其是一些?guó)內(nèi)用到復(fù)雜查詢(xún)業(yè)務(wù)的項(xiàng)目,可能開(kāi)發(fā)到后期特別龐大時(shí),就只能從底層SQL語(yǔ)句開(kāi)始進(jìn)行優(yōu)化,而由于JPA盡可能地在屏蔽我們對(duì)SQL語(yǔ)句的編寫(xiě),所以后期優(yōu)化是個(gè)大問(wèn)題,并且Hibernate相對(duì)于Mybatis來(lái)說(shuō),更加重量級(jí)。不過(guò),在微服務(wù)的時(shí)代,單體項(xiàng)目一般不會(huì)太大,JPA的劣勢(shì)并沒(méi)有太明顯地體現(xiàn)出來(lái)。

到此這篇關(guān)于SpringData JPA快速上手,關(guān)聯(lián)查詢(xún),JPQL語(yǔ)句書(shū)寫(xiě)的文章就介紹到這了,更多相關(guān)SpringData JPA關(guān)聯(lián)查詢(xún)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 教你代碼中獲取當(dāng)前?JAR?包的存放位置

    教你代碼中獲取當(dāng)前?JAR?包的存放位置

    這篇文章主要介紹了如何獲取當(dāng)前JAR包的存放位置,要獲取當(dāng)前運(yùn)行的 JAR 包所存放的位置,可以使用 ProtectionDomain 和 CodeSource 類(lèi),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-08-08
  • 詳解JAVA Timer和TimerTask

    詳解JAVA Timer和TimerTask

    這篇文章主要介紹了JAVA Timer和TimerTask的相關(guān)資料,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • @Schedule?如何解決定時(shí)任務(wù)推遲執(zhí)行

    @Schedule?如何解決定時(shí)任務(wù)推遲執(zhí)行

    這篇文章主要介紹了@Schedule?如何解決定時(shí)任務(wù)推遲執(zhí)行問(wèn)題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • SpringBoot3使用?自定義注解+Jackson實(shí)現(xiàn)接口數(shù)據(jù)脫敏的步驟

    SpringBoot3使用?自定義注解+Jackson實(shí)現(xiàn)接口數(shù)據(jù)脫敏的步驟

    本文介紹了一種以?xún)?yōu)雅的方式實(shí)現(xiàn)對(duì)接口返回的敏感數(shù)據(jù),如手機(jī)號(hào)、郵箱、身份證等信息的脫敏處理,這種方法也是企業(yè)常用方法,話(huà)不多說(shuō)我們一起來(lái)看一下吧
    2024-03-03
  • Spring中實(shí)現(xiàn)的三種異步流式接口方法

    Spring中實(shí)現(xiàn)的三種異步流式接口方法

    在現(xiàn)代Web開(kāi)發(fā)中,接口超時(shí)是一個(gè)常見(jiàn)的問(wèn)題,尤其是在處理耗時(shí)操作時(shí),傳統(tǒng)的同步接口在處理長(zhǎng)時(shí)間任務(wù)時(shí)會(huì)阻塞請(qǐng)求線(xiàn)程,從而影響系統(tǒng)的響應(yīng)能力,本文將詳細(xì)講解Spring中實(shí)現(xiàn)的三種異步流式接口方法,需要的朋友可以參考下
    2024-10-10
  • Spring Security實(shí)現(xiàn)微信公眾號(hào)網(wǎng)頁(yè)授權(quán)功能

    Spring Security實(shí)現(xiàn)微信公眾號(hào)網(wǎng)頁(yè)授權(quán)功能

    這篇文章主要介紹了Spring Security中實(shí)現(xiàn)微信網(wǎng)頁(yè)授權(quán),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08
  • Maven項(xiàng)目打包為jar的四種方式

    Maven項(xiàng)目打包為jar的四種方式

    本文主要介紹了Maven項(xiàng)目打包為jar的四種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10
  • 我對(duì)@RestController注解的理解

    我對(duì)@RestController注解的理解

    這篇文章主要介紹了我對(duì)@RestController注解的理解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫(xiě)法

    MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫(xiě)法

    這篇文章主要介紹了MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫(xiě)法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 使用Spring靜態(tài)注入實(shí)現(xiàn)讀取配置工具類(lèi)新方式

    使用Spring靜態(tài)注入實(shí)現(xiàn)讀取配置工具類(lèi)新方式

    這篇文章主要介紹了使用Spring靜態(tài)注入實(shí)現(xiàn)讀取配置工具類(lèi)新方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02

最新評(píng)論