本文源碼:Gitee·點這里
參考
引入包
引入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…By
、read…By
、query…By
、count…By
和get…By
,并解析剩余部分 - 可以包含其他表達式,如
Distinct
- 可以使用
And
和Or
進行串聯(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 。期望使用@Async Comments 方法,并且需要啟用 Spring 的異步方法執(zhí)行功能。 |
CompletableFuture<T> |
Java 8 CompletableFuture 。期望使用@Async Comments 方法,并且需要啟用 Spring 的異步方法執(zhí)行功能。 |
ListenableFuture |
A org.springframework.util.concurrent.ListenableFuture 。期望使用@Async Comments 方法,并且需要啟用 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> |
Page 和GeoResult<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
屬性的CascadeType
為PERSIST
表示:刪除用戶詳情時,不級聯(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
指向哪一個都可以,中途不要修改