Spring Data JPA 的基本使用
1 第3-4課: Spring Data JPA 的基本使用
Spring Data JPA 是 Spring Boot 體系中約定優於配置的最佳實現,大大簡化了專案中資料庫的操作。從本課開始將會從 JPA 的由來開始講解,什麼是 JPA、Spring Boot JPA 的實現,以及如何使用。
1.1 概念
1.1.1 JPA 由來
ORM 框架能夠將 Java 物件對映到關係資料庫中,能夠直接持久化複雜的 Java 物件。ORM 框架的出現,可以讓開發者從資料庫程式設計中解脫出來,把更多的精力放在了業務模型與業務邏輯上。目前比較流行的 ORM 框架有 Hibernate、MyBatis、TopLink、Spring JDBC 等。
在 JPA 規範之前,由於沒有官方的標準,使得各 ORM 框架之間的 API 差別很大,使用了某種 ORM 框架的系統會嚴重受制於該 ORM 的標準。基於此,Sun 引入新的 JPA ORM,主要的原因有:其一,簡化現有 Java EE 和 Java SE 應用開發工作;其二,Sun 希望整合 ORM 技術,實現統一的 API 呼叫介面。
1.1.2 JPA 是什麼
JPA(Java Persistence API)是 Sun 官方提出的 Java 持久化規範。它為 Java 開發人員提供了一種物件 / 關聯對映工具來管理 Java 應用中的關係資料。它的出現主要是為了簡化現有的持久化開發工作和整合 ORM 技術,結束現在 Hibernate、TopLink、JDO 等 ORM 框架各自為營的局面。
值得注意的是,JPA 是在充分吸收了現有的 Hibernate、TopLink、JDO 等 ORM 框架的基礎上發展而來的,具有易於使用、伸縮性強等優點。從目前的開發社群的反應上看,JPA 受到了極大的支援和讚揚,其中就包括了 Spring 與 EJB 3.0 的開發團隊。
注意:JPA 是一套規範,不是一套產品,那麼像 Hibernate、TopLink、JDO 它們是一套產品,如果說這些產品實現了這個 JPA 規範,那麼我們就可以稱他們為 JPA 的實現產品。
1.1.3 Spring Data JPA
Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套 JPA 應用框架,可以讓開發者用極簡的程式碼即可實現對資料的訪問和操作。它提供了包括增、刪、改、查等在內的常用功能,且易於擴充套件,學習並使用 Spring Data JPA 可以極大提高開發效率。Spring Data JPA 其實就是 Spring 基於 Hibernate 之上構建的 JPA 使用解決方案,方便在 Spring Boot 專案中使用 JPA 技術。
Spring Data JPA 讓我們解脫了 DAO 層的操作,基本上所有 CRUD 都可以依賴於它實現。
1.2 快速上手
1.2.1 新增依賴
<dependency>
<groupId>
org.Springframework.boot
</groupId>
<artifactId>
spring-boot-starter-data-jpa
</artifactId>
</dependency>
<dependency>
<groupId>
mysql
</groupId>
<artifactId>
mysql-connector-java
</artifactId>
</dependency>
1.2.2 新增配置檔案
spring.datasource.url=jdbc:mysql://localhost:3306/
test?serverTimezone=UTC&useUnicode=
true&characterEncoding=utf-8&useSSL=
true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
#SQL 輸出
spring.jpa.show-sql=
true
#format 一下 SQL 進行輸出
spring.jpa.properties.hibernate.format_sql=
true
hibernate.hbm2ddl.auto 引數的作用主要用於:自動建立、更新、驗證資料庫表結構,有四個值。
- create:每次載入 Hibernate 時都會刪除上一次生成的表,然後根據 model 類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致資料庫表資料丟失的一個重要原因。
- create-drop:每次載入 Hibernate 時根據 model 類生成表,但是 sessionFactory 一關閉,表就自動刪除。
- update:最常用的屬性,第一次載入 Hibernate 時根據 model 類會自動建立起表的結構(前提是先建立好資料庫),以後載入 Hibernate 時根據 model 類自動更新表結構,即使表結構改變了,但表中的行仍然存在,不會刪除以前的行。要注意的是當部署到伺服器後,表結構是不會被馬上建立起來的,是要等應用第一次執行起來後才會。
- validate :每次載入 Hibernate 時,驗證建立資料庫表結構,只會和資料庫中的表進行比較,不會建立新表,但是會插入新值。
其中:
- dialect 主要是指定生成表名的儲存引擎為 InneoDB
- show-sql 是否在日誌中打印出自動生成的 SQL,方便除錯的時候檢視
1.2.3 實體類
@Entity
publicclass User
{
@Id
@GeneratedValue
private
Long id;
@Column
(nullable =
false,
unique =
true)
private
String userName;
@Column
(nullable =
false)
private
String passWord;
@Column
(nullable =
false,
unique =
true)
private
String email;
@Column
(nullable =
true,
unique =
true)
private
String nickName;
@Column
(nullable =
false)
private
String regTime;
//省略 getter settet 方法、構造方法
}
下面對上面用的註解做一個解釋。
@Entity(name="EntityName")
必須,用來標註一個數據庫對應的實體,資料庫中建立的表名預設和類名一致。其中,name 為可選,對應資料庫中一個表,使用此註解標記 Pojo 是一個 JPA 實體。@Table(name=""
,
catalog=""
,
schema="")
可選,用來標註一個數據庫對應的實體,資料庫中建立的表名預設和類名一致。通常和 @Entity 配合使用,只能標註在實體的 class 定義處,表示實體對應的資料庫表的資訊。@Id
必須,@Id 定義了對映到資料庫表的主鍵的屬性,一個實體只能有一個屬性被對映為主鍵。@GeneratedValue(strategy=GenerationType
,
generator="")
可選,strategy: 表示主鍵生成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 種,分別表示讓 ORM 框架自動選擇,generator: 表示主鍵生成器的名稱。@Column(name = "user_code"
,
nullable = false
,
length=32)
可選,@Column 描述了資料庫表中該欄位的詳細定義,這對於根據 JPA 註解生成資料庫表結構的工具。name: 表示資料庫表中該欄位的名稱,預設情形屬性名稱一致;nullable: 表示該欄位是否允許為 null,預設為 true;unique: 表示該欄位是否是唯一標識,預設為 false;length: 表示該欄位的大小,僅對 String 型別的欄位有效。@Transient
可選,@Transient 表示該屬性並非一個到資料庫表的欄位的對映,ORM 框架將忽略該屬性。@Enumerated
可選,使用列舉的時候,我們希望資料庫中儲存的是列舉對應的 String 型別,而不是列舉的索引值,需要在屬性上面新增 @Enumerated(EnumType.STRING) 註解。
1.2.4 Repository 構建
建立的 Repository 只要繼承 JpaRepository 即可,就會幫我們自動生成很多內建方法。另外還有一個功能非常實用,可以根據方法名自動生產 SQL,比如 findByUserName 會自動生產一個以 userName 為引數的查詢方法,比如 findAll 會自動查詢表裡面的所有資料等。
publicinterface UserRepository extends JpaRepository<User, Long>
{
User findByUserName(String userName)
;
User findByUserNameOrEmail(String username,String email)
;
}
我們只需要在對應的 Repository 中建立好方法,使用的時候直接將介面注入到類中呼叫即可。在 IDEA 中開啟類 UserRepository,在這個類的大括號內的區域右鍵單擊,選擇 Diagrams | Show Diagram 選項,即可開啟類圖,如下:
通過上圖我們發現 JpaRepository 繼承 PagingAndSortingRepository 和 QueryByExampleExecutor,PagingAndSortingRepository 類主要負責排序和分頁內容,QueryByExampleExecutor 提供了很多示例的查詢方法,如下:
publicinterface QueryByExampleExecutor<T>
{
<S extends T>
S findOne(Example<S> example);
//根據“例項”查詢一個物件
<S extends T>
Iterable<S> findAll(Example<S> example);
//根據“例項”查詢一批物件
<S extends T>
Iterable<S> findAll(Example<S> example, Sort sort);
//根據“例項”查詢一批物件,且排序
<S extends T>
Page<S> findAll(Example<S> example, Pageable pageable);
//根據“例項”查詢一批物件,且排序和分頁
<S extends T>
long count(Example<S> example);
//根據“例項”查詢,返回符合條件的物件個數
<S extends T>
boolean exists(Example<S> example);
//根據“例項”判斷是否有符合條件的物件
}
因此,繼承 JpaRepository 的會自動擁有上述這些方法和排序、分頁功能。檢視原始碼我們發現 PagingAndSortingRepository 又繼承了 CrudRepository。CrudRepository 的原始碼如下:
@NoRepositoryBean
publicinterface CrudRepository<T, ID> extends Repository<T, ID>
{
<S extends T>
S save(S entity);
<S extends T>
Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id)
;
boolean existsById(ID id)
;
Iterable<T> findAll()
;
Iterable<T> findAllById(Iterable<ID> ids)
;
long count()
;
void deleteById(ID id)
;
void delete(T entity)
;
void deleteAll(Iterable<? extends T> entities)
;
void deleteAll()
;
}
從 CrudRepository 的原始碼可以看出 CrudRepository 內建了我們最常用的增、刪、改、查的方法,方便我們去使用,因為 JpaRepository 繼承了 PagingAndSortingRepository,PagingAndSortingRepository 繼承了 CrudRepository,所以繼承 JpaRepository 的類也預設擁有了上述方法。
因此使用 JPA 操作資料庫時,只需要構建的 Repository 繼承了 JpaRepository,就會擁有了很多常用的資料庫操作方法。
1.2.5 測試
建立好 UserRepository 之後,當業務程式碼中需要使用時直接將此介面注入到對應的類中,在 Spring Boot 啟動時,會自動根據註解內容建立實現類並注入到目標類中。
@RunWith(SpringRunner.class)
@SpringBootTest
publicclass UserRepositoryTests
{
@Resource
private
UserRepository userRepository;
@Test
public void test()
{
Date date =
newDate();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG
,
DateFormat.LONG);
String formattedDate = dateFormat.format(date);
userRepository.save(
newUser(
"aa",
"[email protected]"
,
"aa"
,
"aa123456"
,
formattedDate));
userRepository.save(
newUser(
"bb",
"[email protected]"
,
"bb"
,
"bb123456"
,
formattedDate));
userRepository.save(
newUser(
"cc",
"[email protected]"
,
"cc"
,
"cc123456"
,
formattedDate));
Assert.assertEquals(
9,
userRepository.findAll().size());
Assert.assertEquals(
"bb",
userRepository.findByUserNameOrEmail(
"bb",
"[email protected]"
).getNickName());
userRepository.delete(userRepository.findByUserName(
"aa1"));
}
}
上述測試方法簡單測試了 JPA 的報錯和查詢功能,測試用例執行成功表示 JPA 的增、刪、改成功。
1.3 基本查詢
我們可以將 Spring Data JPA 查詢分為兩種,一種是 Spring Data JPA 預設實現的,另一種是需要根據查詢的情況來自行構建。
1.3.1 預生成方法
預生成方法就是我們上面看到的那些方法,因為繼承了 JpaRepository 而擁有了父類的這些內容。
(1)繼承 JpaRepository
publicinterface UserRepository extends JpaRepository<User, Long>
{
}
(2)使用預設方法
@
Test
public void testBaseQuery() {
userRepository.findAll();
userRepository.findById(
1l);
userRepository.save(user);
userRepository.
delete(user);
userRepository.count();
userRepository.existsById(
1l);
// ...
}
所有父類擁有的方法都可以直接呼叫,根據方法名也可以看出它的含義。
1.3.2 自定義查詢
Spring Data JPA 可以根據介面方法名來實現資料庫操作,主要的語法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 後面跟屬性名稱,利用這個功能僅需要在定義的 Repository 中新增對應的方法名即可,使用時 Spring Boot 會自動幫我們實現,示例如下。
根據使用者名稱查詢使用者:
User findByUserName(String userName);
也可以加一些關鍵字 And、or:
User findByUserNameOrEmail(
Stringusername
,
String
email);
修改、刪除、統計也是類似語法:
Long deleteById(Long id);
Long countByUserName(String userName)
基本上 SQL 體系中的關鍵詞都可以使用,如 LIKE 、IgnoreCase、OrderBy:
List<User> findByEmailLike(String email);
User findByUserNameIgnoreCase(String userName);
List<User> findByUserNameOrderByEmailDesc(String email);
可以根據查詢的條件不斷地新增和拼接,Spring Boot 都可以正確解析和執行,其他使用示例可以參考下表。
具體的關鍵字,使用方法和生產成 SQL 如下表所示
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 |
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 (parameter bound with appended %) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (parameter bound with prepended %) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (parameter bound wrapped in %) |
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> age) |
… 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) |
1.4 總結
通過這節課的學習發現使用 JPA 大大解放了我們對資料庫的操作,經常使用的 SQL 大部分都已經被預生成,直接使用即可。另外 JPA 還有一個特點,那就是再也不用關心資料庫的表結構了,需要更改的時候只需要修改對應 Model 的屬性即可。在微服務架構中,因為服務拆分得越來越小,微服務內部只關心自己的業務,需要複雜查詢的場景會越來越少,在微服務架構中更推薦使用 JPA 技術。