spring boot 學習筆記 (9) Spring Data JPA
Spring Data JPA 是 Spring Boot 體系中約定優於配置的最佳實現,大大簡化了專案中資料庫的操作
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 的實現產品。
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 都可以依賴於它實現。
快速上手
新增依賴
<dependency> <groupId>org.Springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
新增配置檔案
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,方便除錯的時候檢視
實體類
@Entity
public class 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) 註解。
Repository 構建
建立的 Repository 只要繼承 JpaRepository 即可,就會幫我們自動生成很多內建方法。另外還有一個功能非常實用,可以根據方法名自動生產 SQL,比如 findByUserName 會自動生產一個以 userName 為引數的查詢方法,比如 findAll 會自動查詢表裡面的所有資料等。
public interface 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 提供了很多示例的查詢方法,如下:
public interface 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
public interface 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,就會擁有了很多常用的資料庫操作方法。
測試
建立好 UserRepository 之後,當業務程式碼中需要使用時直接將此介面注入到對應的類中,在 Spring Boot 啟動時,會自動根據註解內容建立實現類並注入到目標類中。
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {
@Resource
private UserRepository userRepository;
@Test
public void test() {
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
String formattedDate = dateFormat.format(date);
userRepository.save(new User("aa", "[email protected]", "aa", "aa123456",formattedDate));
userRepository.save(new User("bb", "[email protected]", "bb", "bb123456",formattedDate));
userRepository.save(new User("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 的增、刪、改成功。
基本查詢
我們可以將 Spring Data JPA 查詢分為兩種,一種是 Spring Data JPA 預設實現的,另一種是需要根據查詢的情況來自行構建。
預生成方法
預生成方法就是我們上面看到的那些方法,因為繼承了 JpaRepository 而擁有了父類的這些內容。
(1)繼承 JpaRepository
public interface 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);
// ...
}
所有父類擁有的方法都可以直接呼叫,根據方法名也可以看出它的含義。
自定義查詢
Spring Data JPA 可以根據介面方法名來實現資料庫操作,主要的語法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 後面跟屬性名稱,利用這個功能僅需要在定義的 Repository 中新增對應的方法名即可,使用時 Spring Boot 會自動幫我們實現,示例如下。
根據使用者名稱查詢使用者:
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);
可以根據查詢的條件不斷地新增和拼接,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) |
自定義 SQL 查詢
使用 Spring Data 大部分的 SQL 都可以根據方法名定義的方式來實現,但是由於某些原因必須使用自定義的 SQL 來查詢,Spring Data 也可以完美支援。
在 SQL 的查詢方法上面使用 @Query 註解,在註解內寫 Hql 來查詢內容。
@Query("select u from User u")
Page<User> findALL(Pageable pageable);
當然如果感覺使用原生 SQL 更習慣,它也是支援的,需要再新增一個引數 nativeQuery = true。
@Query("select * from user u where u.nick_name = ?1", nativeQuery = true)
Page<User> findByNickName(String nickName, Pageable pageable);
@Query 上面的 1 代表的是方法引數裡面的順序,如果有多個引數也可以按照這個方式新增 1、2、3....。除了按照這種方式傳參外,還可以使用 @Param 來支援。
@Query("select u from User u where u.nickName = :nickName")
Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);
如涉及到刪除和修改需要加上 @Modifying,也可以根據需要新增 @Transactional 對事務的支援、操作超時設定等。
@Transactional(timeout = 10)
@Modifying
@Query("update User set userName = ?1 where id = ?2")
int modifyById(String userName, Long id);
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteById(Long id);
使用已命名的查詢
除了使用 @Query 註解外,還可以預先定義好一些查詢,併為其命名,然後再 Repository 中新增相同命名的方法。
定義命名的 Query:
@Entity
@NamedQueries({
@NamedQuery(name = "User.findByPassWord", query = "select u from User u where u.passWord = ?1"),
@NamedQuery(name = "User.findByNickName", query = "select u from User u where u.nickName = ?1"),
})
public class User {
……
}
通過 @NamedQueries 註解可以定義多個命名 Query,@NamedQuery 的 name 屬性定義了 Query 的名稱,注意加上 Entity 名稱 . 作為字首,query 屬性定義查詢語句。
定義對應的方法:
List<User> findByPassWord(String passWord);
List<User> findByNickName(String nickName);
Query 查詢策略
到此,我們有了三種方法來定義 Query:(1)通過方法名自動建立 Query,(2)通過 @Query 註解實現自定義 Query,(3)通過 @NamedQuery 註解來定義 Query。那麼,Spring Data JPA 如何來查詢這些 Query 呢?
通過配置 @EnableJpaRepositories 的 queryLookupStrategy 屬性來配置 Query 查詢策略,有如下定義。
- CREATE:嘗試從查詢方法名構造特定於儲存的查詢。一般的方法是從方法名中刪除一組已知的字首,並解析方法的其餘部分。
- USE_DECLARED_QUERY:嘗試查詢已宣告的查詢,如果找不到,則丟擲異常。查詢可以通過某個地方的註釋定義,也可以通過其他方式宣告。
- CREATE_IF_NOT_FOUND(預設):CREATE 和 USE_DECLARED_QUERY 的組合,它首先查詢一個已宣告的查詢,如果沒有找到已宣告的查詢,它將建立一個自定義方法基於名稱的查詢。它允許通過方法名進行快速查詢定義,還可以根據需要引入宣告的查詢來定製這些查詢調優。
一般情況下使用預設配置即可,如果確定專案 Query 的具體定義方式,可以更改上述配置,例如,全部使用 @Query 來定義查詢,又或者全部使用命名的查詢。
分頁查詢
Spring Data JPA 已經幫我們內建了分頁功能,在查詢的方法中,需要傳入引數 Pageable,當查詢中有多個引數的時候 Pageable 建議作為最後一個引數傳入。
@Query("select u from User u")
Page<User> findALL(Pageable pageable);
Page<User> findByNickName(String nickName, Pageable pageable);
Pageable 是 Spring 封裝的分頁實現類,使用的時候需要傳入頁數、每頁條數和排序規則,Page 是 Spring 封裝的分頁物件,封裝了總頁數、分頁資料等。返回物件除使用 Page 外,還可以使用 Slice 作為返回值。
Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable);
Page 和 Slice 的區別如下。
- Page 介面繼承自 Slice 介面,而 Slice 繼承自 Iterable 介面。
- Page 介面擴充套件了 Slice 介面,添加了獲取總頁數和元素總數量的方法,因此,返回 Page 介面時,必須執行兩條 SQL,一條複雜查詢分頁資料,另一條負責統計資料數量。
- 返回 Slice 結果時,查詢的 SQL 只會有查詢分頁資料這一條,不統計資料數量。
- 用途不一樣:Slice 不需要知道總頁數、總資料量,只需要知道是否有下一頁、上一頁,是否是首頁、尾頁等,比如前端滑動載入一頁可用;而 Page 知道總頁數、總資料量,可以用於展示具體的頁數資訊,比如後臺分頁查詢。
@Test
public void testPageQuery() {
int page=1,size=2;
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByNickName("aa", pageable);
}
- Sort,控制分頁資料的排序,可以選擇升序和降序。
- PageRequest,控制分頁的輔助類,可以設定頁碼、每頁的資料條數、排序等。
還有一些更簡潔的方式來排序和分頁查詢,如下。
限制查詢
有時候我們只需要查詢前 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);
複雜查詢
我們可以通過 AND 或者 OR 等連線詞來不斷拼接屬性來構建多條件查詢,但如果引數大於 6 個時,方法名就會變得非常的長,並且還不能解決動態多條件查詢的場景。到這裡就需要給大家介紹另外一個利器 JpaSpecificationExecutor 了。
JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封裝,可以用於動態生成 Query 來滿足我們業務中的各種複雜場景。Spring Data JPA 為我們提供了 JpaSpecificationExecutor 介面,只要簡單實現 toPredicate 方法就可以實現複雜的查詢。
我們來看一下 JpaSpecificationExecutor 的原始碼:
public interface JpaSpecificationExecutor<T> {
//根據 Specification 條件查詢單個物件,注意的是,如果條件能查出來多個會報錯
T findOne(@Nullable Specification<T> spec);
//根據 Specification 條件查詢 List 結果
List<T> findAll(@Nullable Specification<T> spec);
//根據 Specification 條件,分頁查詢
Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
//根據 Specification 條件,帶排序的查詢結果
List<T> findAll(@Nullable Specification<T> spec, Sort sort);
//根據 Specification 條件,查詢數量
long count(@Nullable Specification<T> spec);
}
JpaSpecificationExecutor 的原始碼很簡單,根據 Specification 的查詢條件返回 List、Page 或者 count 資料。在使用 JpaSpecificationExecutor 構建複雜查詢場景之前,我們需要了解幾個概念:
- Root
<T>
root,代表了可以查詢和操作的實體物件的根,開一個通過 get("屬性名") 來獲取對應的值。 - CriteriaQuery<?> query,代表一個 specific 的頂層查詢物件,它包含著查詢的各個部分,比如 select 、from、where、group by、order by 等。
- CriteriaBuilder cb,來構建 CritiaQuery 的構建器物件,其實就相當於條件或者是條件組合,並以 Predicate 的形式返回。
使用案例
下面的使用案例中會報錯這幾個物件的使用。
首先定義一個 UserDetail 物件,作為演示的資料模型。
@Entity
public class UserDetail {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, unique = true)
private Long userId;
private Integer age;
private String realName;
private String status;
private String hobby;
private String introduction;
private String lastLoginIp;
}
建立 UserDetail 對應的 Repository:
public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>,JpaRepository<UserDetail, Long> {
}
定義一個查詢 Page<UserDetail>
的介面:
public interface UserDetailService {
public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable);
}
在 UserDetailServiceImpl 中,我們來演示 JpaSpecificationExecutor 的具體使用。
@Service
public class UserDetailServiceImpl implements UserDetailService{
@Resource
private UserDetailRepository userDetailRepository;
@Override
public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable){
return userDetailRepository.findAll((root, query, cb) -> {
List<Predicate> predicates = new ArrayList<Predicate>();
//equal 示例
if (!StringUtils.isNullOrEmpty(detailParam.getIntroduction())){
predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction()));
}
//like 示例
if (!StringUtils.isNullOrEmpty(detailParam.getRealName())){
predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%"));
}
//between 示例
if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null) {
Predicate agePredicate = cb.between(root.get("age"), detailParam.getMinAge(), detailParam.getMaxAge());
predicates.add(agePredicate);
}
//greaterThan 大於等於示例
if (detailParam.getMinAge()!=null){
predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge()));
}
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
}, pageable);
}
}
上面的示例是根據不同條件來動態查詢 UserDetail 分頁資料,UserDetailParam 是引數的封裝,示例中使用了常用的大於、like、等於等示例,根據這個思路我們可以不斷擴充套件完成更復雜的動態 SQL 查詢。
使用時只需要將 UserDetailService 注入呼叫相關方法即可:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaSpecificationTests {
@Resource
private UserDetailService userDetailService;
@Test
public void testFindByCondition() {
int page=0,size=10;
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
UserDetailParam param=new UserDetailParam();
param.setIntroduction("程式設計師");
param.setMinAge(10);
param.setMaxAge(30);
Page<UserDetail> page1=userDetailService.findByCondition(param,pageable);
for (UserDetail userDetail:page1){
System.out.println("userDetail: "+userDetail.toString());
}
}
}
多表查詢
多表查詢在 Spring Data JPA 中有兩種實現方式,第一種是利用 Hibernate 的級聯查詢來實現,第二種是建立一個結果集的介面來接收連表查詢後的結果,這裡主要介紹第二種方式。
我們還是使用上面的 UserDetail 作為資料模型來使用,定義一個結果集的介面類,介面類的內容來自於使用者表和使用者詳情表。
public interface UserInfo {
String getUserName();
String getEmail();
String getAddress();
String getHobby();
}
在執行中 Spring 會給介面(UserInfo)自動生產一個代理類來接收返回的結果,程式碼中使用 getXX 的形式來獲取。
在 UserDetailRepository 中新增查詢的方法,返回型別設定為 UserInfo:
@Query("select u.userName as userName, u.email as email, d.introduction as introduction , d.hobby as hobby from User u , UserDetail d " +
"where u.id=d.userId and d.hobby = ?1 ")
List<UserInfo> findUserInfo(String hobby);
特別注意這裡的 SQL 是 HQL,需要寫類的名和屬性,這塊很容易出錯。
測試驗證:
@Test
public void testUserInfo() {
List<UserInfo> userInfos=userDetailRepository.findUserInfo("釣魚");
for (UserInfo userInfo:userInfos){
System.out.println("userInfo: "+userInfo.getUserName()+"-"+userInfo.getEmail()+"-"+userInfo.getHobby()+"-"+userInfo.getIntroduction());
}
}
執行測試方法後返回:
userInfo: [email protected]釣魚-程式設計師
證明關聯查詢成功,最後的返回結果來自於兩個表,按照這個思路可以進行三個或者更多表的關聯查詢。
多資料來源的使用
專案中使用多個數據源在以往工作中比較常見,微服務架構中不建議一個專案使用多個數據源。在微服務架構下,一個微服務擁有自己獨立的一個數據庫,如果此微服務要使用其他資料庫的資料,需要呼叫對應庫的微服務介面來呼叫,而不是在一個專案中連線使用多個數據庫,這樣微服務更獨立、更容易水平擴充套件。
雖然在微服務架構下,不提倡一個專案擁有多個數據源,但在 Spring Boot 體系中,專案實現多資料來源呼叫卻是一件很容易的事情
總結
Spring Data JPA 使用動態注入的原理,根據方法名動態生成方法的實現,因此根據方法名實現資料查詢,即可滿足日常絕大部分使用場景。除了這種查詢方式之外,Spring Data JPA 還支援多種自定義查詢來滿足更多複雜場景的使用,兩種方式相結合可以靈活滿足專案對 Orm 層的需求。
通過學習 Spring Data JPA 也可以看出 Spring Boot 的設計思想,80% 的需求通過預設、簡單的方式實現,滿足大部分使用場景,對於另外 20% 複雜的場景,提供另外的技術手段來解決。Spring Data JPA 中根據方法名動態實現 SQL,元件環境自動配置等細節,都是將 Spring Boot 約定優於配置的思想體現的淋淋盡致。