SpringBoot 全家桶 | JPA實例詳解

本文源碼:Gitee·點這里

參考

Spring Data JPA

引入包

引入jpa的包,同時引入mysql包和test

<dependencys>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencys>

配置文件

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot-familay?charset=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    hikari:
      maximum-pool-size: 32
      minimum-idle: 8
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
  • spring.jpa.show-sql=true 配置是否打印執(zhí)行的SQL語句
  • spring.jpa.hibernate.ddl-auto=update 配置程序啟動時自動創(chuàng)建數(shù)據(jù)庫的類型(注意:此配置很危險,禁止在生產(chǎn)中使用)
    • create 程序重啟時先刪除表,再創(chuàng)建表
    • create-drop 程序重啟時與create一樣;程序結(jié)束時刪除表
    • update 程序啟動時若沒有表格會創(chuàng)建,表內(nèi)有數(shù)據(jù)不會清空,只會更新
    • validate 程序重啟時會校驗實體類與數(shù)據(jù)庫中的字段是否相同,不同則報錯

實體類

代碼省略了get/set方法

@Entity
@Table(name = "AUTH_USER")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 32)
    private String name;

    @Column(length = 32, unique = true)
    private String account;

    @Column(length = 63)
    private String pwd;

    @Column
    private Date createTime;
}
  • @Entity 聲明此類為實體類
  • @Table 聲明該實體類對應(yīng)數(shù)據(jù)庫中的表信息
    • name指定表名,若不指定使用類名
  • @Id 聲明實體屬性為表主鍵
  • @GeneratedValue 主鍵生成策略,通過strategy屬性指定
    • TABLE 使用數(shù)據(jù)庫表來模擬序列產(chǎn)生主鍵
    • SEQUENCE 根據(jù)底層數(shù)據(jù)庫的序列來生成主鍵,通過@SequenceGenerator注解指定序列名,條件是數(shù)據(jù)庫支持序列
    • IDENTITY 使用數(shù)據(jù)庫ID自增主鍵,條件是數(shù)據(jù)庫支持
    • AUTO 默認(rèn),JPA根據(jù)數(shù)據(jù)庫類型自動選擇適合的策略
  • @Column 聲明實體屬性的表字段定義
    • name 指定字段名,不指定則使用該實體屬性名
    • unique 唯一索引,默認(rèn)false
    • nullable 可為空,默認(rèn)true
    • length 長度,默認(rèn)255
    • precision 精度,默認(rèn)0
    • columnDefinition 該字段的SQL片段,示例:@Column(name = "account",columnDefinition = "varchar(32) not null default'' unique")

以上創(chuàng)建好后,程序啟動后會自動在數(shù)據(jù)庫中創(chuàng)建表信息

持久層

定義持久層接口

JPA中實現(xiàn)持久層非常簡單,創(chuàng)建一個接口,并繼承 org.springframework.data.jpa.repository.JpaRepository<T, ID>T為實體類,ID為實體類主鍵類型;最后在接口上增加 @Repository 注解

@Repository
public interface UserDao extends JpaRepository<User, Long> {
}

JpaRepository繼承了CrudRepository,提供了基本的CRUD功能

public interface CrudRepository<T, ID extends Serializable>
  extends Repository<T, ID> {

  <S extends T> S save(S entity);      //(1)

  Optional<T> findById(ID primaryKey); //(2)

  Iterable<T> findAll();               //(3)

  long count();                        //(4)

  void delete(T entity);               //(5)

  boolean existsById(ID primaryKey);   //(6)

  // … more functionality omitted.
}
  • (1) 保存給定的實體,若已存在則更新。
  • (2) 返回由給定 ID 標(biāo)識的實體。
  • (3) 返回所有實體。
  • (4) 返回實體數(shù)。
  • (5) 刪除給定的實體。
  • (6) 指示是否存在具有給定 ID 的實體。

JpaRepository繼承了PagingAndSortingRepository,提供分頁和排序功能

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

定義查詢方法

JPA可以通過方法名稱派生查詢

  • 該機制從方法中剝離前綴find…Byread…Byquery…Bycount…Byget…By,并解析剩余部分
  • 可以包含其他表達式,如Distinct
  • 可以使用AndOr進行串聯(lián)

官方示例:

interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

方法名稱中支持的關(guān)鍵字

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname , findByFirstnameIs , findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1(參數(shù)附加了%)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1(參數(shù)與前綴%綁定)
Containing findByFirstnameContaining … where x.firstname like ?1(參數(shù)綁定在%中)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

查詢返回類型

Return type Description
void 表示沒有返回值。
Primitives Java primitives.
Wrapper types Java 包裝器類型。
T 唯一實體。期望查詢方法最多返回一個結(jié)果。如果未找到結(jié)果,則返回null。一個以上的結(jié)果觸發(fā)IncorrectResultSizeDataAccessException
Iterator<T> 一個Iterator
Collection<T> A Collection
List<T> A List
Optional<T> Java 8 或 Guava Optional。期望查詢方法最多返回一個結(jié)果。如果未找到結(jié)果,則返回Optional.empty()Optional.absent()。多個結(jié)果觸發(fā)IncorrectResultSizeDataAccessException
Option<T> Scala 或 Javaslang Option類型。語義上與前面描述的 Java 8 的Optional相同。
Stream<T> Java 8 Stream
Future<T> A Future。期望使用@AsyncComments 方法,并且需要啟用 Spring 的異步方法執(zhí)行功能。
CompletableFuture<T> Java 8 CompletableFuture。期望使用@AsyncComments 方法,并且需要啟用 Spring 的異步方法執(zhí)行功能。
ListenableFuture A org.springframework.util.concurrent.ListenableFuture。期望使用@AsyncComments 方法,并且需要啟用 Spring 的異步方法執(zhí)行功能。
Slice 一定大小的數(shù)據(jù)塊,用于指示是否有更多可用數(shù)據(jù)。需要Pageable方法參數(shù)。
Page<T> Slice以及其他信息,例如結(jié)果總數(shù)。需要Pageable方法參數(shù)。
GeoResult<T> 具有附加信息(例如到參考位置的距離)的結(jié)果條目。
GeoResults<T> GeoResult<T>列表以及其他信息,例如到參考位置的平均距離。
GeoPage<T> PageGeoResult<T>,例如到參考位置的平均距離。
Mono<T> 使用 Reactive 存儲庫的 Project Reactor Mono發(fā)出零或一個元素。期望查詢方法最多返回一個結(jié)果。如果未找到結(jié)果,則返回Mono.empty()。多個結(jié)果觸發(fā)IncorrectResultSizeDataAccessException
Flux<T> Project Reactor Flux使用 Reactive 存儲庫發(fā)出零,一個或多個元素。返回Flux的查詢也可以發(fā)出無限數(shù)量的元素。
Single<T> 使用 Reactive 存儲庫發(fā)出單個元素的 RxJava Single。期望查詢方法最多返回一個結(jié)果。如果未找到結(jié)果,則返回Mono.empty()。多個結(jié)果觸發(fā)IncorrectResultSizeDataAccessException
Maybe<T> 使用 Reactive 存儲庫的 RxJava Maybe發(fā)出零或一個元素。期望查詢方法最多返回一個結(jié)果。如果未找到結(jié)果,則返回Mono.empty()。多個結(jié)果觸發(fā)IncorrectResultSizeDataAccessException
Flowable<T> RxJava Flowable使用 Reactive 存儲庫發(fā)出零個,一個或多個元素。返回Flowable的查詢也可以發(fā)出無限數(shù)量的元素。

異步查詢結(jié)果

我們在這里重點講一下異步查詢結(jié)果。
JPA查詢結(jié)果支持異步,這意味著該方法在調(diào)用時立即返回,而實際查詢的執(zhí)行已提交給Spring的TaskExecutor的任務(wù)中。
官方示例:

interface PersonRepository extends Repository<User, Long> {
    @Async
    Future<User> findByFirstname(String firstname);               //(1)
    
    @Async
    CompletableFuture<User> findOneByFirstname(String firstname); //(2)
    
    @Async
    ListenableFuture<User> findOneByLastname(String lastname);    //(3)
}
  • (1) 使用java.util.concurrent.Future作為返回類型。
  • (2) 使用 Java 8 java.util.concurrent.CompletableFuture作為返回類型。
  • (3) 使用org.springframework.util.concurrent.ListenableFuture作為返回類型。

使用SQL語句查詢

JPA自定義查詢語句非常簡單,使用@Query注解即可

public interface UserDao extends JpaRepository<User, Long> {

    @Query("select u from User u where u.account = ?1")
    User selectByAccount(String account);

    @Transactional
    @Modifying
    @Query("update User u set u.pwd = ?2 where u.id = ?1")
    int updatePassword(Long id, String password);

}
  • SQL語句中使用的是實體類名和實體屬性,而不是表名和表字段
  • 如果使用update/delete,需要加上@Transactional@Modifying

實體關(guān)系映射

JPA 實體關(guān)系映射:@OneToOne 一對一關(guān)系、@OneToMany @ManyToOne 一對多和多對一關(guān)系、@ManyToMany 多對多關(guān)系。

OneToOne

一對一關(guān)系,即兩個表中的數(shù)據(jù)是一一對應(yīng)關(guān)系,這種主要應(yīng)用到對象的擴展信息中。

用戶實體類:

@Entity
@Table(name = "AUTH_USER")
public class User {

    /*
        其余代碼省略
     */

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
    private UserDetail userDetail;
}

用戶詳情實體類:

@Entity
@Table(name = "AUTH_USER_DETAIL")
public class UserDetail {

    /*
        其余代碼省略
     */

    @OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    private User user;
}
  • mappedBy加在了User實體為的userDetail屬性上,并指向user表示:在UserDetail實體類映射的表中創(chuàng)建關(guān)系字段(user_id),并設(shè)為外鍵,指向User實體類映射的表中主鍵
  • UserDetail實體類的user屬性的CascadeTypePERSIST表示:刪除用戶詳情時,不級聯(lián)刪除用戶信息

OneToMany/ManyToOne

一對多關(guān)系即一個表中的一行數(shù)據(jù)關(guān)聯(lián)另外一個表中的多行數(shù)據(jù),多對一與之相反。

用戶實體類:

@Entity
@Table(name = "AUTH_USER")
public class User {

    /*
        其余代碼省略
     */

    @ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
    private Company company;
}

公司實體類:

@Entity
@Table(name = "AUTH_COMPANY")
public class Company {

    /*
        其余代碼省略
     */

    @OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY, mappedBy = "company")
    private Set<User> users = new HashSet<>();
}
  • @mappedBy只能出現(xiàn)在@OneToMany注解中
  • 會在User實體類對應(yīng)映射表中創(chuàng)建一個外鍵(company_id),指向Company實體類對應(yīng)映射表的主鍵
  • 兩邊最好不要做級聯(lián)刪除, cascade設(shè)為PERSIST

ManyToMany

多對多關(guān)系即其中一個表中的一行,與另一表中的多行關(guān)聯(lián),比如本示例中用戶和角色的關(guān)系。

用戶實體類:

@Entity
@Table(name = "AUTH_USER")
public class User {

    /*
        其余代碼省略
     */

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Role> roles = new HashSet<>();
}

角色實體類:

@Entity
@Table(name = "AUTH_ROLE")
public class Role {

    /*
        其余代碼省略
     */

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "roles")
    private Set<User> users = new HashSet<>();
}
  • 會建立一個中間表,包含兩個實體類的主鍵關(guān)系
  • mappedBy指向哪一個都可以,中途不要修改

完整代碼

springboot-jpa

timg (1).jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。