在上篇文章Spring Boot(二):Web 綜合開發中簡單介紹了一下 Spring Boot Jpa 的基礎性使用,這篇文章將更加全面的介紹 Spring Boot Jpa 常見用法以及注意事項。
使用 Spring Boot Jpa 開發時,發現國內對 Spring Boot Jpa 全面介紹的文章比較少案例也比較零碎,因此寫文章總結一下。本人也正在翻譯Spring Data JPA 參考指南,有興趣的同學歡迎聯絡我,一起加入翻譯中!
Spring Boot Jpa 介紹
首先了解 Jpa 是什麼?
Jpa (Java Persistence API) 是 Sun 官方提出的 Java 持久化規範。它為 Java 開發人員提供了一種物件/關聯對映工具來管理 Java 應用中的關係資料。它的出現主要是為了簡化現有的持久化開發工作和整合 ORM 技術,結束現在 Hibernate,TopLink,JDO 等 ORM 框架各自為營的局面。
值得注意的是,Jpa是在充分吸收了現有 Hibernate,TopLink,JDO 等 ORM 框架的基礎上發展而來的,具有易於使用,伸縮性強等優點。從目前的開發社群的反應上看,Jpa 受到了極大的支援和讚揚,其中就包括了 Spring 與 EJB3. 0的開發團隊。
注意:Jpa 是一套規範,不是一套產品,那麼像 Hibernate,TopLink,JDO 他們是一套產品,如果說這些產品實現了這個 Jpa 規範,那麼我們就可以叫他們為 Jpa 的實現產品。
Spring Boot Jpa
Spring Boot Jpa 是 Spring 基於 ORM 框架、Jpa 規範的基礎上封裝的一套 Jpa 應用框架,可使開發者用極簡的程式碼即可實現對資料的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴充套件!學習並使用 Spring Data Jpa 可以極大提高開發效率!
Spring Boot Jpa 讓我們解脫了 DAO 層的操作,基本上所有 CRUD 都可以依賴於它來實現
基本查詢
基本查詢也分為兩種,一種是 Spring Data 預設已經實現,一種是根據查詢的方法來自動解析成 SQL。
預先生成方法
Spring Boot Jpa 預設預先生成了一些基本的CURD的方法,例如:增、刪、改等等
1 繼承 JpaRepository
public interface UserRepository extends JpaRepository<User, Long> {
}
2 使用預設方法
@Test
public void testBaseQuery() throws Exception {
User user=new User();
userRepository.findAll();
userRepository.findOne(1l);
userRepository.save(user);
userRepository.delete(user);
userRepository.count();
userRepository.exists(1l);
// ...
}
就不解釋了根據方法名就看出意思來
自定義簡單查詢
自定義的簡單查詢就是根據方法名來自動生成 SQL,主要的語法是findXXBy
,readAXXBy
,queryXXBy
,countXXBy
, getXXBy
後面跟屬性名稱:
User findByUserName(String userName);
也使用一些加一些關鍵字And
、 Or
User findByUserNameOrEmail(String username, 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);
具體的關鍵字,使用方法和生產成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 |
… where x.age in ?1 |
NotIn | findByAgeNotIn(Collection |
… 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) |
複雜查詢
在實際的開發中我們需要用到分頁、刪選、連表等查詢的時候就需要特殊的方法或者自定義 SQL
分頁查詢
分頁查詢在實際使用中非常普遍了,Spring Boot Jpa 已經幫我們實現了分頁的功能,在查詢的方法中,需要傳入引數Pageable
,當查詢中有多個引數的時候Pageable
建議做為最後一個引數傳入.
Page<User> findALL(Pageable pageable);
Page<User> findByUserName(String userName,Pageable pageable);
Pageable
是 Spring 封裝的分頁實現類,使用的時候需要傳入頁數、每頁條數和排序規則
@Test
public void testPageQuery() throws Exception {
int page=1,size=10;
Sort sort = new Sort(Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByUserName("testName", pageable);
}
限制查詢
有時候我們只需要查詢前N個元素,或者支取前一個實體。
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
自定義SQL查詢
其實 Spring Data 覺大部分的 SQL 都可以根據方法名定義的方式來實現,但是由於某些原因我們想使用自定義的 SQL 來查詢,Spring Data 也是完美支援的;在 SQL 的查詢方法上面使用@Query
註解,如涉及到刪除和修改在需要加上@Modifying
.也可以根據需要新增 @Transactional
對事物的支援,查詢超時的設定等。
@Modifying
@Query("update User u set u.userName = ?1 where u.id = ?2")
int modifyByIdAndUserId(String userName, Long id);
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);
@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
多表查詢
多表查詢 Spring Boot Jpa 中有兩種實現方式,第一種是利用 Hibernate 的級聯查詢來實現,第二種是建立一個結果集的介面來接收連表查詢後的結果,這裡主要第二種方式。
首先需要定義一個結果集的介面類。
public interface HotelSummary {
City getCity();
String getName();
Double getAverageRating();
default Integer getAverageRatingRounded() {
return getAverageRating() == null ? null : (int) Math.round(getAverageRating());
}
}
查詢的方法返回型別設定為新建立的介面
@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r where h.city = ?1 group by h")
Page<HotelSummary> findByCity(City city, Pageable pageable);
@Query("select h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r group by h")
Page<HotelSummary> findByCity(Pageable pageable);
使用
Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name"));
for(HotelSummary summay:hotels){
System.out.println("Name" +summay.getName());
}
在執行中 Spring 會給介面(HotelSummary)自動生產一個代理類來接收返回的結果,程式碼彙總使用
getXX
的形式來獲取
多資料來源的支援
同源資料庫的多源支援
日常專案中因為使用的分散式開發模式,不同的服務有不同的資料來源,常常需要在一個專案中使用多個資料來源,因此需要配置 Spring Boot Jpa 對多資料來源的使用,一般分一下為三步:
- 1 配置多資料來源
- 2 不同源的實體類放入不同包路徑
- 3 宣告不同的包路徑下使用不同的資料來源、事務支援
異構資料庫多源支援
比如我們的專案中,即需要對 mysql 的支援,也需要對 Mongodb 的查詢等。
實體類宣告@Entity
關係型資料庫支援型別、宣告@Document
為 Mongodb 支援型別,不同的資料來源使用不同的實體就可以了
interface PersonRepository extends Repository<Person, Long> {
…
}
@Entity
public class Person {
…
}
interface UserRepository extends Repository<User, Long> {
…
}
@Document
public class User {
…
}
但是,如果 User 使用者既使用 Mysql 也使用 Mongodb 呢,也可以做混合使用
interface JpaPersonRepository extends Repository<Person, Long> {
…
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
…
}
@Entity
@Document
public class Person {
…
}
也可以通過對不同的包路徑進行宣告,比如 A 包路徑下使用 mysql,B 包路徑下使用 MongoDB
@EnableJpaRepositories(basePackages = "com.neo.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.neo.repositories.mongo")
interface Configuration { }
其它
使用列舉
使用列舉的時候,我們希望資料庫中儲存的是列舉對應的 String 型別,而不是列舉的索引值,需要在屬性上面新增@Enumerated(EnumType.STRING)
註解
@Enumerated(EnumType.STRING)
@Column(nullable = true)
private UserType type;
不需要和資料庫對映的屬性
正常情況下我們在實體類上加入註解@Entity
,就會讓實體類和表相關連如果其中某個屬性我們不需要和資料庫來關聯只是在展示的時候做計算,只需要加上@Transient
屬性既可。
@Transient
private String userName;
原始碼案例
文章內容已經升級到 Spring Boot 2.x