SpringData JPA基本/高級(jí)/多數(shù)據(jù)源的使用詳解
一、Spring Data JPA基本用法
Spring Data JPA 是 Spring Boot 體系中約定優(yōu)于配置的最佳實(shí)現(xiàn),大大簡(jiǎn)化了項(xiàng)目中數(shù)據(jù)庫(kù)的操作
1、概念
JPA由來(lái)
ORM 框架能夠?qū)?Java 對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)中,能夠直接持久化復(fù)雜的 Java 對(duì)象。ORM 框架的出現(xiàn),可以讓開(kāi)發(fā)者從數(shù)據(jù)庫(kù)編程中解脫出來(lái),把更多的精力放在了業(yè)務(wù)模型與業(yè)務(wù)邏輯上。目前比較流行的 ORM 框架有 Hibernate、MyBatis、TopLink、Spring JDBC 等。
在 JPA 規(guī)范之前,由于沒(méi)有官方的標(biāo)準(zhǔn),使得各 ORM 框架之間的 API 差別很大,使用了某種 ORM 框架的系統(tǒng)會(huì)嚴(yán)重受制于該 ORM 的標(biāo)準(zhǔn)?;诖?,Sun 引入新的 JPA ORM,主要的原因有:其一,簡(jiǎn)化現(xiàn)有 Java EE 和 Java SE 應(yīng)用開(kāi)發(fā)工作;其二,Sun 希望整合 ORM 技術(shù),實(shí)現(xiàn)統(tǒng)一的 API 調(diào)用接口。
JPA是什么
JPA(Java Persistence API)是 Sun 官方提出的 Java 持久化規(guī)范。它為 Java 開(kāi)發(fā)人員提供了一種對(duì)象 / 關(guān)聯(lián)映射工具來(lái)管理 Java 應(yīng)用中的關(guān)系數(shù)據(jù)。它的出現(xiàn)主要是為了簡(jiǎn)化現(xiàn)有的持久化開(kāi)發(fā)工作和整合 ORM 技術(shù),結(jié)束現(xiàn)在 Hibernate、TopLink、JDO 等 ORM 框架各自為營(yíng)的局面。
注意:JPA 是一套規(guī)范,不是一套產(chǎn)品,那么像 Hibernate、TopLink、JDO 它們是一套產(chǎn)品,如果說(shuō)這些產(chǎn)品實(shí)現(xiàn)了這個(gè) JPA 規(guī)范,那么我們就可以稱(chēng)他們?yōu)?JPA 的實(shí)現(xiàn)產(chǎn)品。
Spring Data JPA
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套 JPA 應(yīng)用框架,可以讓開(kāi)發(fā)者用極簡(jiǎn)的代碼即可實(shí)現(xiàn)對(duì)數(shù)據(jù)的訪問(wèn)和操作。它提供了包括增、刪、改、查等在內(nèi)的常用功能,且易于擴(kuò)展,學(xué)習(xí)并使用 Spring Data JPA 可以極大提高開(kāi)發(fā)效率。Spring Data JPA 其實(shí)就是 Spring 基于 Hibernate 之上構(gòu)建的 JPA 使用解決方案,方便在 Spring Boot 項(xiàng)目中使用 JPA 技術(shù)。
Spring Data JPA 讓我們解脫了 DAO 層的操作,基本上所有 CRUD 都可以依賴(lài)于它實(shí)現(xiàn)。
2、快速上手
1.添加依賴(lài)
<dependency> ? ? <groupId>org.springframework.boot</groupId> ? ? <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ?<dependency> ? ? <groupId>mysql</groupId> ? ? <artifactId>mysql-connector-java</artifactId> </dependency>
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 進(jìn)行輸出 spring.jpa.properties.hibernate.format_sql=true
hibernate.hbm2ddl.auto 參數(shù)的作用主要用于:自動(dòng)創(chuàng)建、更新、驗(yàn)證數(shù)據(jù)庫(kù)表結(jié)構(gòu),有四個(gè)值。
create
:每次加載 Hibernate 時(shí)都會(huì)刪除上一次生成的表,然后根據(jù) model 類(lèi)再重新來(lái)生成新表,哪怕兩次沒(méi)有任何改變也要這樣執(zhí)行,這就是導(dǎo)致數(shù)據(jù)庫(kù)表數(shù)據(jù)丟失的一個(gè)重要原因。create-drop
:每次加載 Hibernate 時(shí)根據(jù) model 類(lèi)生成表,但是 sessionFactory 一關(guān)閉,表就自動(dòng)刪除。update
:最常用的屬性,第一次加載 Hibernate 時(shí)根據(jù) model 類(lèi)會(huì)自動(dòng)建立起表的結(jié)構(gòu)(前提是先建立好數(shù)據(jù)庫(kù)),以后加載 Hibernate 時(shí)根據(jù) model 類(lèi)自動(dòng)更新表結(jié)構(gòu),即使表結(jié)構(gòu)改變了,但表中的行仍然存在,不會(huì)刪除以前的行。要注意的是當(dāng)部署到服務(wù)器后,表結(jié)構(gòu)是不會(huì)被馬上建立起來(lái)的,是要等應(yīng)用第一次運(yùn)行起來(lái)后才會(huì)。validate
:每次加載 Hibernate 時(shí),驗(yàn)證創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu),只會(huì)和數(shù)據(jù)庫(kù)中的表進(jìn)行比較,不會(huì)創(chuàng)建新表,但是會(huì)插入新值。
其他
dialect
主要是指定生成表名的存儲(chǔ)引擎為 InnoDBshow-sql
是否在日志中打印出自動(dòng)生成的 SQL,方便調(diào)試的時(shí)候查看
3.實(shí)體類(lèi)
@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 方法、構(gòu)造方法 }
@Entity(name="EntityName")
必須,用來(lái)標(biāo)注一個(gè)數(shù)據(jù)庫(kù)對(duì)應(yīng)的實(shí)體,數(shù)據(jù)庫(kù)中創(chuàng)建的表名默認(rèn)和類(lèi)名一致。其中,name 為可選,對(duì)應(yīng)數(shù)據(jù)庫(kù)中一個(gè)表,使用此注解標(biāo)記 Pojo 是一個(gè) JPA 實(shí)體。@Table(name="",catalog="",schema="")
可選,用來(lái)標(biāo)注一個(gè)數(shù)據(jù)庫(kù)對(duì)應(yīng)的實(shí)體,數(shù)據(jù)庫(kù)中創(chuàng)建的表名默認(rèn)和類(lèi)名一致。通常和@Entity配合使用,只能標(biāo)注在實(shí)體的 class 定義處,表示實(shí)體對(duì)應(yīng)的數(shù)據(jù)庫(kù)表的信息。@Id
必須,@Id定義了映射到數(shù)據(jù)庫(kù)表的主鍵的屬性,一個(gè)實(shí)體只能有一個(gè)屬性被映射為主鍵。@GeneratedValue(strategy=GenerationType,generator="")
可選,strategy: 表示主鍵生成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 種,分別表示讓 ORM 框架自動(dòng)選擇,generator: 表示主鍵生成器的名稱(chēng)。@Column(name = "user_code", nullable = false, length=32)
可選,@Column 描述了數(shù)據(jù)庫(kù)表中該字段的詳細(xì)定義,這對(duì)于根據(jù) JPA 注解生成數(shù)據(jù)庫(kù)表結(jié)構(gòu)的工具。name: 表示數(shù)據(jù)庫(kù)表中該字段的名稱(chēng),默認(rèn)情形屬性名稱(chēng)一致;nullable: 表示該字段是否允許為 null,默認(rèn)為 true;unique: 表示該字段是否是唯一標(biāo)識(shí),默認(rèn)為 false;length: 表示該字段的大小,僅對(duì) String 類(lèi)型的字段有效。@Transient
可選,@Transient 表示該屬性并非一個(gè)到數(shù)據(jù)庫(kù)表的字段的映射,ORM 框架將忽略該屬性。@Enumerated
可選,使用枚舉的時(shí)候,我們希望數(shù)據(jù)庫(kù)中存儲(chǔ)的是枚舉對(duì)應(yīng)的 String 類(lèi)型,而不是枚舉的索引值,需要在屬性上面添加 @Enumerated(EnumType.STRING) 注解。
4.Repository構(gòu)建
創(chuàng)建的 Repository 只要繼承 JpaRepository 即可,就會(huì)幫我們自動(dòng)生成很多內(nèi)置方法。另外還有一個(gè)功能非常實(shí)用,可以根據(jù)方法名自動(dòng)生產(chǎn) SQL,比如 findByUserName 會(huì)自動(dòng)生產(chǎn)一個(gè)以 userName 為參數(shù)的查詢方法,比如 findAll 會(huì)自動(dòng)查詢表里面的所有數(shù)據(jù)等。
public interface UserRepository extends JpaRepository<User,Long> { ? ? User findByUserName(String userName); ? ? User findByUserNameOrEmail(String username,String email); }
我們只需要在對(duì)應(yīng)的 Repository 中創(chuàng)建好方法,使用的時(shí)候直接將接口注入到類(lèi)中調(diào)用即可。在 IDEA 中打開(kāi)類(lèi) UserRepository,在這個(gè)類(lèi)的大括號(hào)內(nèi)的區(qū)域右鍵單擊,選擇 Diagrams | Show Diagram 選項(xiàng),即可打開(kāi)類(lèi)圖,如下:
通過(guò)上圖我們發(fā)現(xiàn) JpaRepository 繼承 PagingAndSortingRepository 和 QueryByExampleExecutor,PagingAndSortingRepository 類(lèi)主要負(fù)責(zé)排序和分頁(yè)內(nèi)容,QueryByExampleExecutor 提供了很多示例的查詢方法,如下:
public interface QueryByExampleExecutor<T> {? ? ? <S extends T> S findOne(Example<S> example); ? //根據(jù)“實(shí)例”查找一個(gè)對(duì)象 ? ? <S extends T> Iterable<S> findAll(Example<S> example); ? ? //根據(jù)“實(shí)例”查找一批對(duì)象 ? ? <S extends T> Iterable<S> findAll(Example<S> example,Sort sort); ?//根據(jù)“實(shí)例”查找一批對(duì)象,且排序 ? ? <S extends T> Page<S> findAll(Example<S> example,Pageable pageable); ?//根據(jù)“實(shí)例”查找一批對(duì)象,且排序和分頁(yè) ? ? <S extends T> long count(Example<S> example); ?//根據(jù)“實(shí)例”查找,返回符合條件的對(duì)象個(gè)數(shù) ? ? <S extends T> boolean exists(Example<S> example); ?//根據(jù)“實(shí)例”判斷是否有符合條件的對(duì)象 }
因此,繼承 JpaRepository 的會(huì)自動(dòng)擁有上述這些方法和排序、分頁(yè)功能。查看源碼我們發(fā)現(xiàn) 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 內(nèi)置了我們最常用的增、刪、改、查的方法,方便我們?nèi)ナ褂?,因?yàn)?JpaRepository 繼承了 PagingAndSortingRepository,PagingAndSortingRepository 繼承了 CrudRepository,所以繼承 JpaRepository 的類(lèi)也默認(rèn)擁有了上述方法。
因此使用 JPA 操作數(shù)據(jù)庫(kù)時(shí),只需要構(gòu)建的 Repository 繼承了 JpaRepository,就會(huì)擁有了很多常用的數(shù)據(jù)庫(kù)操作方法。
5.測(cè)試
創(chuàng)建好 UserRepository 之后,當(dāng)業(yè)務(wù)代碼中需要使用時(shí)直接將此接口注入到對(duì)應(yīng)的類(lèi)中,在 Spring Boot 啟動(dòng)時(shí),會(huì)自動(dòng)根據(jù)注解內(nèi)容創(chuàng)建實(shí)現(xiàn)類(lèi)并注入到目標(biāo)類(lèi)中。
@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","aa@126.com","aa","aa123456",formattedDate)); ? ? ? ? userRepository.save(new User("bb","bb@126.com","bb","bb123456",formattedDate)); ? ? ? ? userRepository.save(new User("cc","cc@126.com","cc","cc123456",formattedDate)); ? ? ? ? Assert.assertEquals(9,userRepository.findAll().size()); ? ? ? ? Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","cc@126.com").getNickName()); ? ? ? ? userRepository.delete(userRepository.findByUserName("aa1")); ? ? } }
6.基本查詢
我們可以將 Spring Data JPA 查詢分為兩種,一種是 Spring Data JPA 默認(rèn)實(shí)現(xiàn)的,另一種是需要根據(jù)查詢的情況來(lái)自行構(gòu)建。
預(yù)生成方法
預(yù)生成方法就是我們上面看到的那些方法,因?yàn)槔^承了 JpaRepository 而擁有了父類(lèi)的這些內(nèi)容。
(1)繼承 JpaRepository
public interface UserRepository extends JpaRepository<User,Long> { }
(2)使用默認(rèn)方法
//所有父類(lèi)擁有的方法都可以直接調(diào)用,根據(jù)方法名也可以看出它的含義。 @Test public void testBaseQuery() { userRepository.findAll(); userRepository.findById(1l); userRepository.save(user); userRepository.delete(user); userRepository.count(); userRepository.existsById(1l); // ... ?? ?}
7.自定義查詢
Spring Data JPA 可以根據(jù)接口方法名來(lái)實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作,主要的語(yǔ)法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 后面跟屬性名稱(chēng),利用這個(gè)功能僅需要在定義的 Repository 中添加對(duì)應(yīng)的方法名即可,使用時(shí) Spring Boot 會(huì)自動(dòng)幫我們實(shí)現(xiàn),示例如下。
根據(jù)用戶名查詢用戶:
User findByUserName(String userName);
也可以加一些關(guān)鍵字 And、or:
User findByUserNameOrEmail(String username,String email);
修改、刪除、統(tǒng)計(jì)也是類(lèi)似語(yǔ)法:
Long deleteById(Long id); Long countByUserName(String userName)
基本上 SQL 體系中的關(guān)鍵詞都可以使用,如 Like 、IgnoreCase、OrderBy:
List<User> findByEmailLike(String email); User findByUserNameIgnoreCase(String userName); List<User> findByUserNameOrderByEmailDesc(String email);
可以根據(jù)查詢的條件不斷地添加和拼接,Spring Boot 都可以正確解析和執(zhí)行,其他使用示例可以參考下表。
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(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) |
Count | countByFirstName | select count(*) from ... where x.firstName = ?1 |
Exists | existsByFirstName | like the dao.exists(Example),judge by attribution of firstName:select keyindex0_.id as col_0_0_ from key_index keyindex0_ where keyindex0_.name=? limit ? |
3、小結(jié)
第一部分講解了使用 JPA 大大解放了我們對(duì)數(shù)據(jù)庫(kù)的操作,經(jīng)常使用的 SQL 大部分都已經(jīng)被預(yù)生成,直接使用即可。另外 JPA 還有一個(gè)特點(diǎn),那就是再也不用關(guān)心數(shù)據(jù)庫(kù)的表結(jié)構(gòu)了,需要更改的時(shí)候只需要修改對(duì)應(yīng) Model 的屬性即可。在微服務(wù)架構(gòu)中,因?yàn)榉?wù)拆分得越來(lái)越小,微服務(wù)內(nèi)部只關(guān)心自己的業(yè)務(wù),需要復(fù)雜查詢的場(chǎng)景會(huì)越來(lái)越少,在微服務(wù)架構(gòu)中更推薦使用 JPA 技術(shù)。
二、Spring Data JPA高級(jí)用法
第一部分介紹了 Spring Data JPA 的使用方式和基本查詢,常用的增、刪、改、查需求 Spring Data JPA 已經(jīng)實(shí)現(xiàn)了。但對(duì)于復(fù)雜的數(shù)據(jù)庫(kù)場(chǎng)景,動(dòng)態(tài)生成方法不能滿足,對(duì)此 Spring Data JPA 提供了其他的解決方案。
1、自定義 SQL 查詢
使用 Spring Data 大部分的 SQL 都可以根據(jù)方法名定義的方式來(lái)實(shí)現(xiàn),但是由于某些原因必須使用自定義的 SQL 來(lái)查詢,Spring Data 也可以完美支持。
在 SQL 的查詢方法上面使用 @Query 注解,在注解內(nèi)寫(xiě) Hql 來(lái)查詢內(nèi)容。
@Query("select u from User u") Page<User> findALL(Pageable pageable);
當(dāng)然如果感覺(jué)使用原生 SQL 更習(xí)慣,它也是支持的,需要再添加一個(gè)參數(shù) nativeQuery = true。
@Query("select * from user u where u.nick_name = ?1", nativeQuery = true) Page<User> findByNickName(String nickName, Pageable pageable);
@Query 上面的 1 代表的是方法參數(shù)里面的順序,如果有多個(gè)參數(shù)也可以按照這個(gè)方式添加 1、2、3....。除了按照這種方式傳參外,還可以使用 @Param 來(lái)支持。
@Query("select u from User u where u.nickName = :nickName") Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);
如涉及到刪除和修改需要加上 @Modifying,也可以根據(jù)需要添加 @Transactional 對(duì)事務(wù)的支持、操作超時(shí)設(shè)置等。
@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 注解外,還可以預(yù)先定義好一些查詢,并為其命名,然后再 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 { ? ? …… }
通過(guò) @NamedQueries 注解可以定義多個(gè)命名 Query,@NamedQuery 的 name 屬性定義了 Query 的名稱(chēng),注意加上 Entity 名稱(chēng) . 作為前綴,query 屬性定義查詢語(yǔ)句,定義對(duì)應(yīng)的方法:
List<User> findByPassWord(String passWord); List<User> findByNickName(String nickName);
Query 查找策略
到此,我們有了三種方法來(lái)定義 Query:(1)通過(guò)方法名自動(dòng)創(chuàng)建 Query,(2)通過(guò) @Query 注解實(shí)現(xiàn)自定義 Query,(3)通過(guò) @NamedQuery 注解來(lái)定義 Query。那么,Spring Data JPA 如何來(lái)查找這些 Query 呢?
通過(guò)配置 @EnableJpaRepositories 的 queryLookupStrategy 屬性來(lái)配置 Query 查找策略,有如下定義。
CREATE
:嘗試從查詢方法名構(gòu)造特定于存儲(chǔ)的查詢。一般的方法是從方法名中刪除一組已知的前綴,并解析方法的其余部分。USE_DECLARED_QUERY
:嘗試查找已聲明的查詢,如果找不到,則拋出異常。查詢可以通過(guò)某個(gè)地方的注釋定義,也可以通過(guò)其他方式聲明。CREATE_IF_NOT_FOUND(默認(rèn))
:CREATE 和 USE_DECLARED_QUERY 的組合,它首先查找一個(gè)已聲明的查詢,如果沒(méi)有找到已聲明的查詢,它將創(chuàng)建一個(gè)自定義方法基于名稱(chēng)的查詢。它允許通過(guò)方法名進(jìn)行快速查詢定義,還可以根據(jù)需要引入聲明的查詢來(lái)定制這些查詢調(diào)優(yōu)。
2、分頁(yè)查詢Spring Data JPA 已經(jīng)幫我們內(nèi)置了分頁(yè)功能
在查詢的方法中,需要傳入?yún)?shù) Pageable,當(dāng)查詢中有多個(gè)參數(shù)的時(shí)候 Pageable 建議作為最后一個(gè)參數(shù)傳入。
@Query("select u from User u") Page<User> findALL(Pageable pageable); Page<User> findByNickName(String nickName, Pageable pageable);
Pageable 是 Spring 封裝的分頁(yè)實(shí)現(xiàn)類(lèi),使用的時(shí)候需要傳入頁(yè)數(shù)、每頁(yè)條數(shù)和排序規(guī)則,Page 是 Spring 封裝的分頁(yè)對(duì)象,封裝了總頁(yè)數(shù)、分頁(yè)數(shù)據(jù)等。返回對(duì)象除使用 Page 外,還可以使用 Slice 作為返回值。
Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable);
Page 和 Slice 的區(qū)別如下:
Page 接口繼承自 Slice 接口,而 Slice 繼承自 Iterable 接口。
Page 接口擴(kuò)展了 Slice 接口,添加了獲取總頁(yè)數(shù)和元素總數(shù)量的方法,因此,返回 Page 接口時(shí),必須執(zhí)行兩條 SQL,一條復(fù)雜查詢分頁(yè)數(shù)據(jù),另一條負(fù)責(zé)統(tǒng)計(jì)數(shù)據(jù)數(shù)量。
返回 Slice 結(jié)果時(shí),查詢的 SQL 只會(huì)有查詢分頁(yè)數(shù)據(jù)這一條,不統(tǒng)計(jì)數(shù)據(jù)數(shù)量。
用途不一樣:Slice 不需要知道總頁(yè)數(shù)、總數(shù)據(jù)量,只需要知道是否有下一頁(yè)、上一頁(yè),是否是首頁(yè)、尾頁(yè)等,比如前端滑動(dòng)加載一頁(yè)可用;而 Page 知道總頁(yè)數(shù)、總數(shù)據(jù)量,可以用于展示具體的頁(yè)數(shù)信息,比如后臺(tái)分頁(yè)查詢。
@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
,控制分頁(yè)數(shù)據(jù)的排序,可以選擇升序和降序。PageRequest
,控制分頁(yè)的輔助類(lèi),可以設(shè)置頁(yè)碼、每頁(yè)的數(shù)據(jù)條數(shù)、排序等。
限制查詢:有時(shí)候我們只需要查詢前 N 個(gè)元素,或者只取前一個(gè)實(shí)體。
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);
3、復(fù)雜查詢
我們可以通過(guò) AND 或者 OR 等連接詞來(lái)不斷拼接屬性來(lái)構(gòu)建多條件查詢,但如果參數(shù)大于 6 個(gè)時(shí),方法名就會(huì)變得非常的長(zhǎng),并且還不能解決動(dòng)態(tài)多條件查詢的場(chǎng)景。到這里就需要給大家介紹另外一個(gè)利器 JpaSpecificationExecutor 了。
JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封裝,可以用于動(dòng)態(tài)生成 Query 來(lái)滿足我們業(yè)務(wù)中的各種復(fù)雜場(chǎng)景。Spring Data JPA 為我們提供了 JpaSpecificationExecutor 接口,只要簡(jiǎn)單實(shí)現(xiàn) toPredicate 方法就可以實(shí)現(xiàn)復(fù)雜的查詢。
我們來(lái)看一下 JpaSpecificationExecutor 的源碼:
public interface JpaSpecificationExecutor<T> { ? ?//根據(jù) Specification 條件查詢單個(gè)對(duì)象,注意的是,如果條件能查出來(lái)多個(gè)會(huì)報(bào)錯(cuò) ? ?T findOne(@Nullable Specification<T> spec); ? ?//根據(jù) Specification 條件查詢 List 結(jié)果 ? ?List<T> findAll(@Nullable Specification<T> spec); ? ?//根據(jù) Specification 條件,分頁(yè)查詢 ? ?Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); ? ?//根據(jù) Specification 條件,帶排序的查詢結(jié)果 ? ?List<T> findAll(@Nullable Specification<T> spec, Sort sort); ? ?//根據(jù) Specification 條件,查詢數(shù)量 ? ?long count(@Nullable Specification<T> spec); }
JpaSpecificationExecutor 的源碼很簡(jiǎn)單,根據(jù) Specification 的查詢條件返回 List、Page 或者 count 數(shù)據(jù)。在使用 JpaSpecificationExecutor 構(gòu)建復(fù)雜查詢場(chǎng)景之前,我們需要了解幾個(gè)概念:
Root root
,代表了可以查詢和操作的實(shí)體對(duì)象的根,開(kāi)一個(gè)通過(guò) get("屬性名") 來(lái)獲取對(duì)應(yīng)的值。CriteriaQuery query
,代表一個(gè) specific 的頂層查詢對(duì)象,它包含著查詢的各個(gè)部分,比如 select 、from、where、group by、order by 等。CriteriaBuilder cb
,來(lái)構(gòu)建 CritiaQuery 的構(gòu)建器對(duì)象,其實(shí)就相當(dāng)于條件或者是條件組合,并以 Predicate 的形式返回。
使用案例: 首先定義一個(gè) UserDetail 對(duì)象,作為演示的數(shù)據(jù)模型。
@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; }
創(chuàng)建 UserDetail 對(duì)應(yīng)的 Repository:
public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>,JpaRepository<UserDetail, Long> ?{ }
定義一個(gè)查詢 Page<UserDetail> 的接口:
public interface UserDetailService { ? ? public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable); }
在 UserDetailServiceImpl 中,我們來(lái)演示 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); ? ? } }
上面的示例是根據(jù)不同條件來(lái)動(dòng)態(tài)查詢 UserDetail 分頁(yè)數(shù)據(jù),UserDetailParam 是參數(shù)的封裝,示例中使用了常用的大于、like、等于等示例,根據(jù)這個(gè)思路我們可以不斷擴(kuò)展完成更復(fù)雜的動(dòng)態(tài) SQL 查詢。
@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()); ? ? ? ? } ? ? } }
4、多表查詢
多表查詢?cè)?Spring Data JPA 中有兩種實(shí)現(xiàn)方式,第一種是利用 Hibernate 的級(jí)聯(lián)查詢來(lái)實(shí)現(xiàn),第二種是創(chuàng)建一個(gè)結(jié)果集的接口來(lái)接收連表查詢后的結(jié)果,這里主要介紹第二種方式。
我們還是使用上面的 UserDetail 作為數(shù)據(jù)模型來(lái)使用,定義一個(gè)結(jié)果集的接口類(lèi),接口類(lèi)的內(nèi)容來(lái)自于用戶表和用戶詳情表。
public interface UserInfo { ? ? String getUserName(); ? ? String getEmail(); ? ? String getAddress(); ? ? String getHobby(); }
在運(yùn)行中 Spring 會(huì)給接口(UserInfo)自動(dòng)生產(chǎn)一個(gè)代理類(lèi)來(lái)接收返回的結(jié)果,代碼中使用 getXX 的形式來(lái)獲取。
在 UserDetailRepository 中添加查詢的方法,返回類(lèi)型設(shè)置為 UserInfo,特別注意這里的 SQL 是 HQL,需要寫(xiě)類(lèi)的名和屬性,這塊很容易出錯(cuò)
@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);
測(cè)試驗(yàn)證
@Test public void testUserInfo() ?{ ? ? List<UserInfo> userInfos=userDetailRepository.findUserInfo("釣魚(yú)"); ? ? for (UserInfo userInfo:userInfos){ ? ? ? ? System.out.println("userInfo: "+userInfo.getUserName()+"-"+userInfo.getEmail()+"-"+userInfo.getHobby()+"-"+userInfo.getIntroduction()); ? ? } }
測(cè)試后返回
userInfo: aa-aa@126.com-釣魚(yú)-程序員
5、小結(jié)
Spring Data JPA 使用動(dòng)態(tài)注入的原理,根據(jù)方法名動(dòng)態(tài)生成方法的實(shí)現(xiàn),因此根據(jù)方法名實(shí)現(xiàn)數(shù)據(jù)查詢,即可滿足日常絕大部分使用場(chǎng)景。除了這種查詢方式之外,Spring Data JPA 還支持多種自定義查詢來(lái)滿足更多復(fù)雜場(chǎng)景的使用,兩種方式相結(jié)合可以靈活滿足項(xiàng)目對(duì) Orm 層的需求。
通過(guò)學(xué)習(xí) Spring Data JPA 也可以看出 Spring Boot 的設(shè)計(jì)思想,80% 的需求通過(guò)默認(rèn)、簡(jiǎn)單的方式實(shí)現(xiàn),滿足大部分使用場(chǎng)景,對(duì)于另外 20% 復(fù)雜的場(chǎng)景,提供另外的技術(shù)手段來(lái)解決。Spring Data JPA 中根據(jù)方法名動(dòng)態(tài)實(shí)現(xiàn) SQL,組件環(huán)境自動(dòng)配置等細(xì)節(jié),都是將 Spring Boot 約定優(yōu)于配置的思想體現(xiàn)的淋淋盡致。
三、Spring Data JPA多數(shù)據(jù)源使用
1、前言
項(xiàng)目中使用多個(gè)數(shù)據(jù)源在以往工作中比較常見(jiàn),微服務(wù)架構(gòu)中不建議一個(gè)項(xiàng)目使用多個(gè)數(shù)據(jù)源。在微服務(wù)架構(gòu)下,一個(gè)微服務(wù)擁有自己獨(dú)立的一個(gè)數(shù)據(jù)庫(kù),如果此微服務(wù)要使用其他數(shù)據(jù)庫(kù)的數(shù)據(jù),需要調(diào)用對(duì)應(yīng)庫(kù)的微服務(wù)接口來(lái)調(diào)用,而不是在一個(gè)項(xiàng)目中連接使用多個(gè)數(shù)據(jù)庫(kù),這樣微服務(wù)更獨(dú)立、更容易水平擴(kuò)展。
雖然在微服務(wù)架構(gòu)下,不提倡一個(gè)項(xiàng)目擁有多個(gè)數(shù)據(jù)源,但在 Spring Boot 體系中,項(xiàng)目實(shí)現(xiàn)多數(shù)據(jù)源調(diào)用卻是一件很容易的事情,本節(jié)課將介紹 Spring Data JPA 多數(shù)據(jù)源的使用。
Spring Data JPA 使用多數(shù)據(jù)源的整體思路是,配置不同的數(shù)據(jù)源,在啟動(dòng)時(shí)分別加載多個(gè)數(shù)據(jù)源配置,并且注入到不同的 repository 中。這樣不同的 repository 包就有不同的數(shù)據(jù)源,使用時(shí)注入對(duì)應(yīng)包下的 repository,就會(huì)使用對(duì)應(yīng)數(shù)據(jù)源的操作。
項(xiàng)目結(jié)構(gòu)如下:
onfig 啟動(dòng)時(shí)加載、配置多數(shù)據(jù)源;
model 存放數(shù)據(jù)操作的實(shí)體類(lèi);
repository 目錄下有兩個(gè)包路徑 test1 和 test2 ,分別代表兩個(gè)不同數(shù)據(jù)源下的倉(cāng)庫(kù),這兩個(gè)包下的 repository 可以相同也可以不同。
2、多數(shù)據(jù)源的支持
配置 Spring Data JPA 對(duì)多數(shù)據(jù)源的使用,一般分為以下幾步:
- 創(chuàng)建數(shù)據(jù)庫(kù) test1 和 test2
- 配置多數(shù)據(jù)源
- 不同源的 repository 放入不同包路徑
- 聲明不同的包路徑下使用不同的數(shù)據(jù)源、事務(wù)支持
- 不同的包路徑下創(chuàng)建對(duì)應(yīng)的 repository
- 測(cè)試使用
上面的一些步驟我們?cè)谇懊鎯烧n中已經(jīng)講過(guò)了,這里只補(bǔ)充不同的內(nèi)容。
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.primary.username=root spring.datasource.primary.password=root spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true spring.datasource.secondary.username=root spring.datasource.secondary.password=root spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver # 設(shè)置將項(xiàng)目中的 SQL 格式化后打印出來(lái),方便在開(kāi)發(fā)過(guò)程中調(diào)試跟蹤。 #SQL 輸出 spring.jpa.show-sql=true spring.jpa.properties.hibernate.hbm2ddl.auto=create spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect #format 一下 SQL 進(jìn)行輸出 spring.jpa.properties.hibernate.format_sql=true
創(chuàng)建 DataSourceConfig 添加 @Configuration 注解,在項(xiàng)目啟動(dòng)時(shí)運(yùn)行初始化數(shù)據(jù)庫(kù)資源。
@Configuration public class DataSourceConfig { }
在 DataSourceConfig 類(lèi)中加載配置文件,利用 ConfigurationProperties 自動(dòng)裝配的特性加載兩個(gè)數(shù)據(jù)源。
加載第一個(gè)數(shù)據(jù)源,數(shù)據(jù)源配置以 spring.datasource.primary 開(kāi)頭,注意當(dāng)有多個(gè)數(shù)據(jù)源時(shí),需要將其中一個(gè)標(biāo)注為 @Primary,作為默認(rèn)的數(shù)據(jù)源使用。
@Bean(name = "primaryDataSource") @Primary @ConfigurationProperties("spring.datasource.primary") public DataSource firstDataSource() { ? ? return DataSourceBuilder.create().build(); }
加載第二個(gè)數(shù)據(jù)源,數(shù)據(jù)源配置以 spring.datasource.secondary 為開(kāi)頭。
@Bean(name = "secondaryDataSource") @ConfigurationProperties("spring.datasource.secondary") public DataSource secondDataSource() { ? ? return DataSourceBuilder.create().build(); }
加載 JPA 的相關(guān)配置信息,JpaProperties 是 JPA 的一些屬性配置信息,構(gòu)建 LocalEntityManagerFactoryBean 需要參數(shù)信息注入到方法中。
@Autowired private JpaProperties jpaProperties; @Autowired private HibernateProperties hibernateProperties; @Bean(name = "vendorProperties") public Map<String, Object> getVendorProperties() { ? ? return hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings()); }
第一個(gè)數(shù)據(jù)源的加載配置過(guò)程
首先來(lái)看第一個(gè)數(shù)據(jù)源的加載配置過(guò)程,創(chuàng)建 PrimaryConfig 類(lèi),將上面創(chuàng)建好的第一個(gè)數(shù)據(jù)源注入到類(lèi)中,添加 @Configuration 和 @EnableTransactionManagement 注解,第一個(gè)代表啟動(dòng)時(shí)加載,第二個(gè)注
解表示啟用事務(wù),同時(shí)將第一個(gè)數(shù)據(jù)源和 JPA 配置信息注入到類(lèi)中。
@Configuration @EnableTransactionManagement public class PrimaryConfig { ? ? @Autowired ? ? @Qualifier("primaryDataSource") ? ? private DataSource primaryDataSource; ? ? @Autowired ? ? @Qualifier("vendorProperties") ? ? private Map<String, Object> vendorProperties; }
LocalEntityManagerFactoryBean 負(fù)責(zé)創(chuàng)建一個(gè)適合于僅使用 JPA 進(jìn)行數(shù)據(jù)訪問(wèn)的環(huán)境的 EntityManager,構(gòu)建的時(shí)候需要指明提示實(shí)體類(lèi)的包路徑、數(shù)據(jù)源和 JPA 配置信息。
@Bean(name = "entityManagerFactoryPrimary") @Primary public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) { ? ? return builder ? ? ? ? ? ? .dataSource(primaryDataSource) ? ? ? ? ? ? .properties(vendorProperties) ? ? ? ? ? ? .packages("com.neo.model") //設(shè)置實(shí)體類(lèi)所在位置 ? ? ? ? ? ? .persistenceUnit("primaryPersistenceUnit") ? ? ? ? ? ? .build(); }
利用上面的 entityManagerFactoryPrimary() 方法構(gòu)建好最終的 EntityManager。
@Bean(name = "entityManagerPrimary") @Primary public EntityManager entityManager(EntityManagerFactoryBuilder builder) { ? ? return entityManagerFactoryPrimary(builder).getObject().createEntityManager(); }
EntityManager 是 JPA 中用于增、刪、改、查的接口,它的作用相當(dāng)于一座橋梁,連接內(nèi)存中的 Java 對(duì)象和數(shù)據(jù)庫(kù)的數(shù)據(jù)存儲(chǔ)。使用 EntityManager 中的相關(guān)接口對(duì)數(shù)據(jù)庫(kù)實(shí)體進(jìn)行操作的時(shí)候, EntityManager
會(huì)跟蹤實(shí)體對(duì)象的狀態(tài),并決定在特定時(shí)刻將對(duì)實(shí)體的操作映射到數(shù)據(jù)庫(kù)操作上面,同時(shí)給數(shù)據(jù)源添加上 JPA 事務(wù)。
@Bean(name = "transactionManagerPrimary") @Primary PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) { ? ? return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject()); }
最后一步最為關(guān)鍵,將我們?cè)陬?lèi)中配置好的 EntityManager 和事務(wù)信息注入到對(duì)應(yīng)數(shù)據(jù)源的 repository 目錄下,這樣此目錄下的 repository 就會(huì)擁有對(duì)應(yīng)數(shù)據(jù)源和事務(wù)的信息。
@Configuration @EnableTransactionManagement @EnableJpaRepositories( ? ? ? ? entityManagerFactoryRef="entityManagerFactoryPrimary", ? ? ? ? transactionManagerRef="transactionManagerPrimary", ? ? ? ? basePackages= { "com.neo.repository.test1" })//設(shè)置dao(repo)所在位置 public class PrimaryConfig {}
其中,basePackages 支持設(shè)置多個(gè)包路徑,例如,basePackages= { "com.neo.repository.test1","com.neo.repository.test3" }
第二個(gè)數(shù)據(jù)源的加載配置過(guò)程
第二個(gè)數(shù)據(jù)源配置和第一個(gè)數(shù)據(jù)源配置類(lèi)似,只是方法上去掉了注解:@Primary,第二個(gè)數(shù)據(jù)源數(shù)據(jù)源加載配置類(lèi) SecondaryConfig 完整代碼如下:
@Configuration @EnableTransactionManagement @EnableJpaRepositories( ? ? ? ? entityManagerFactoryRef="entityManagerFactorySecondary", ? ? ? ? transactionManagerRef="transactionManagerSecondary", ? ? ? ? basePackages= { "com.neo.repository.test2" }) public class SecondaryConfig { ? ? @Autowired ? ? @Qualifier("secondaryDataSource") ? ? private DataSource secondaryDataSource; ? ? @Autowired ? ? @Qualifier("vendorProperties") ? ? private Map<String, Object> vendorProperties; ? ? @Bean(name = "entityManagerFactorySecondary") ? ? public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) { ? ? ? ? return builder ? ? ? ? ? ? ? ? .dataSource(secondaryDataSource) ? ? ? ? ? ? ? ? .properties(vendorProperties) ? ? ? ? ? ? ? ? .packages("com.neo.model") ? ? ? ? ? ? ? ? .persistenceUnit("secondaryPersistenceUnit") ? ? ? ? ? ? ? ? .build(); ? ? } ? ? @Bean(name = "entityManagerSecondary") ? ? public EntityManager entityManager(EntityManagerFactoryBuilder builder) { ? ? ? ? return entityManagerFactorySecondary(builder).getObject().createEntityManager(); ? ? } ? ? @Bean(name = "transactionManagerSecondary") ? ? PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) { ? ? ? ? return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject()); ? ? } }
到此多數(shù)據(jù)源的配置就完成了,項(xiàng)目中使用哪個(gè)數(shù)據(jù)源的操作,就注入對(duì)應(yīng)包下的 repository 進(jìn)行操作即可,接下來(lái)我們對(duì)上面配置好的數(shù)據(jù)源進(jìn)行測(cè)試。
創(chuàng)建 UserRepositoryTests 測(cè)試類(lèi),將兩個(gè)包下的 repository 都注入到測(cè)試類(lèi)中:
@RunWith(SpringRunner.class) @SpringBootTest public class UserRepositoryTests { ? ? @Resource ? ? private UserTest1Repository userTest1Repository; ? ? @Resource ? ? private UserTest2Repository userTest2Repository; }
首先測(cè)試兩個(gè)數(shù)據(jù)庫(kù)中都存入數(shù)據(jù),數(shù)據(jù)源1插入 2 條用戶信息,數(shù)據(jù)源2插入 1 條用戶信息。
@Test public void testSave() throws Exception { ? ? Date date = new Date(); ? ? DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); ? ? String formattedDate = dateFormat.format(date); ? ? userTest1Repository.save(new User("aa", "aa123456","aa@126.com", "aa", ?formattedDate)); ? ? userTest1Repository.save(new User("bb", "bb123456","bb@126.com", "bb", ?formattedDate)); ? ? userTest2Repository.save(new User("cc", "cc123456","cc@126.com", "cc", ?formattedDate)); }
執(zhí)行完測(cè)試用例后查看數(shù)據(jù)庫(kù),發(fā)現(xiàn) test1 庫(kù)有兩條數(shù)據(jù),test2 有一條,證明兩個(gè)數(shù)據(jù)源均保存數(shù)據(jù)正常。下面繼續(xù)測(cè)試刪除功能,使用兩個(gè)數(shù)據(jù)源的 repository 將用戶信息全部刪除。
@Test public void testDelete() throws Exception { ? ? userTest1Repository.deleteAll(); ? ? userTest2Repository.deleteAll(); }
執(zhí)行完測(cè)試用例后,發(fā)現(xiàn) test1 庫(kù)和 test2 庫(kù)用戶表的信息已經(jīng)被清空,證明多數(shù)據(jù)源刪除成功。
3、小結(jié)
Spring Data JPA 通過(guò)在啟動(dòng)時(shí)加載不同的數(shù)據(jù)源,并將不同的數(shù)據(jù)源注入到不同的 repository 包下,從而實(shí)現(xiàn)項(xiàng)目多數(shù)據(jù)源操作,在項(xiàng)目中使用多數(shù)據(jù)源時(shí),需要用到哪個(gè)數(shù)據(jù)源,只需要將對(duì)應(yīng)包下的 repository 注入操作即可。本課示例中以兩個(gè)數(shù)據(jù)源作為演示,但其實(shí)三個(gè)或者更多數(shù)據(jù)源配置、操作,都可以按照上面方法進(jìn)行配置使用。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- SpringBoot+Spring?Data?JPA整合H2數(shù)據(jù)庫(kù)的示例代碼
- 解決Spring Data Jpa 實(shí)體類(lèi)自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)表失敗問(wèn)題
- Springboot使用Spring Data JPA實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作
- Spring Data JPA進(jìn)行數(shù)據(jù)分頁(yè)與排序的方法
- springboot使用spring-data-jpa操作MySQL數(shù)據(jù)庫(kù)
- Spring boot中使用Spring-data-jpa方便快捷的訪問(wèn)數(shù)據(jù)庫(kù)(推薦)
- 詳解基于Spring Boot與Spring Data JPA的多數(shù)據(jù)源配置
- Spring?Data?JPA框架的核心概念與Repository接口詳解
相關(guān)文章
SpringBoot 返回Json實(shí)體類(lèi)屬性大小寫(xiě)的解決
這篇文章主要介紹了SpringBoot 返回Json實(shí)體類(lèi)屬性大小寫(xiě)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Springboot內(nèi)嵌tomcat應(yīng)用原理深入分析
懂得SpringBoot的童鞋應(yīng)該很清楚,不管應(yīng)用程序是屬于何種類(lèi)型,都是一個(gè)Main方法走遍天下,對(duì)于web應(yīng)用,只需要引入spring-boot-starter-web中這個(gè)依賴(lài),應(yīng)用程序就好像直接給我們來(lái)了個(gè)tomcat一樣,對(duì)于嵌入式Tomcat,其實(shí)也非常簡(jiǎn)單,就是調(diào)用Tomcat提供的外部類(lèi)2022-09-09Springboot利于第三方服務(wù)進(jìn)行ip定位獲取省份城市
本文主要介紹了Springboot利于第三方服務(wù)進(jìn)行ip定位獲取省份城市,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07java基礎(chǔ)學(xué)習(xí)JVM中GC的算法
這篇文章主要介紹了java基礎(chǔ)學(xué)習(xí)JVM中GC的算法,通過(guò)圖文加深對(duì)GC算法思路的理解。2017-11-11SpringBoot整合Junit實(shí)例過(guò)程解析
這篇文章主要介紹了SpringBoot整合Junit實(shí)例過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Spring+SpringMVC+MyBatis深入學(xué)習(xí)及搭建(二)之MyBatis原始Dao開(kāi)發(fā)和mapper代理開(kāi)發(fā)
這篇文章主要介紹了Spring+SpringMVC+MyBatis深入學(xué)習(xí)及搭建(二)之MyBatis原始Dao開(kāi)發(fā)和mapper代理開(kāi)發(fā),需要的朋友可以參考下2017-05-05javaWEB中前后臺(tái)亂碼問(wèn)題的解決方法總結(jié)
下面小編就為大家?guī)?lái)一篇javaWEB中前后臺(tái)亂碼問(wèn)題的解決方法總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08Java調(diào)用JavaScript實(shí)現(xiàn)字符串計(jì)算器代碼示例
這篇文章主要介紹了Java調(diào)用JavaScript實(shí)現(xiàn)字符串計(jì)算器代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-12-12