
大家好,又見面了。
到這里呢,已經(jīng)是本SpringData JPA系列文檔的第三篇了,先來回顧下前面兩篇:
在第1篇《Spring Data JPA系列1:JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?給你個(gè)選擇SpringDataJPA的理由!》中,我們對(duì)JPA的整體概念有了全面的了解。
在第2篇《Spring Data JPA系列2:快速在SpringBoot項(xiàng)目中熟練使用JPA》中也知曉了SpringBoot項(xiàng)目快速集成SpringData JPA以及快速上手使用JPA來進(jìn)行基本的項(xiàng)目開發(fā)的技能。
本篇內(nèi)容將在上一篇已有的內(nèi)容基礎(chǔ)上,進(jìn)一步的聊一下項(xiàng)目中使用JPA的一些高階復(fù)雜場(chǎng)景的實(shí)踐指導(dǎo),覆蓋了主要核心的JPA使用場(chǎng)景,可以讓你在需求開發(fā)的時(shí)候?qū)PA的使用更加的游刃有余。
Repository
上一篇文檔中,我們知道業(yè)務(wù)代碼中直接調(diào)用Repository層中默認(rèn)提供的方法或者是自己自定義的接口方法,便可以進(jìn)行DB的相關(guān)操作。這里我們?cè)賹?duì)repository的整體實(shí)現(xiàn)情況進(jìn)一步探索下。
repository全貌梳理
先看下Repository相關(guān)的類圖:

整體類圖雖然咋看上去很龐雜,但其實(shí)主線脈絡(luò)還是比較清晰的。
- 先看下藍(lán)色的部分其實(shí)就是Repository的一整個(gè)接口定義鏈條,而橙色的則是我們自己自定義的一些Repository接口類,繼承父層接口的所有已有能力。
- 左側(cè)的類圖與接口,其實(shí)都是JPA提供的一些用于實(shí)現(xiàn)或者定制查詢操作的一些輔助實(shí)現(xiàn)類,后面章節(jié)中會(huì)看到他們的身影。
對(duì)主體repository層級(jí)提供的主要方法進(jìn)行簡(jiǎn)單的梳理,如下:

下面對(duì)各個(gè)repository接口進(jìn)行簡(jiǎn)單的獨(dú)立介紹。
JpaRepository與它的父類們
-
Repository位于Spring Data Common的lib里面,是Spring Data 里面做數(shù)據(jù)庫操作的最底層的抽象接口、最頂級(jí)的父類,源碼里面其實(shí)什么方法都沒有,僅僅起到一個(gè)標(biāo)識(shí)作用。 -
CrudRepository作為直接繼承Repository的次頂層接口類,看名字也可以大致猜測(cè)出其主要作用就是封裝提供基礎(chǔ)CRUD操作。 -
PagingAndSortingRepository繼承自CrudRepository,自然也就具備了CrudRepository提供的全部接口能力。此外,從其自身新提供的接口來看,增加了排序和分頁查詢列表的能力,非常符合其類名的含義。
JpaRepository與其前面的幾個(gè)父類相比是個(gè)特殊的存在,其中補(bǔ)充添加了一組JPA規(guī)范的接口方法。前面的幾個(gè)接口類都是Spring Data為了兼容NoSQL而進(jìn)行的一些抽象封裝(因?yàn)镾pringData項(xiàng)目是一個(gè)龐大的家族,支持各種SQL與NoSQL的數(shù)據(jù)庫,SpringData JPA是SpringData家族中面向SQL數(shù)據(jù)庫的一個(gè)子分支項(xiàng)目),從JpaRepository開始是對(duì)關(guān)系型數(shù)據(jù)庫進(jìn)行抽象封裝。

從類圖可以看得出來它繼承了PagingAndSortingRepository類,也就繼承了其所有方法,并且實(shí)現(xiàn)類也是SimpleJpaRepository。從類圖上還可以看出JpaRepository繼承和擁有了QueryByExampleExecutor的相關(guān)方法。

通過源碼和CrudRepository相比較,它支持Query By Example,批量刪除,提高刪除效率,手動(dòng)刷新數(shù)據(jù)庫的更改方法,并將默認(rèn)實(shí)現(xiàn)的查詢結(jié)果變成了List。
額外補(bǔ)充一句:
實(shí)際的項(xiàng)目編碼中,大部分的場(chǎng)景中,我們自定義Repository都是繼承
JpaRepository來實(shí)現(xiàn)的。
自定義Repository
先看個(gè)自定義Repository的例子,如下:

看下對(duì)應(yīng)類圖結(jié)構(gòu),自定義Repository繼承了JpaRepository,具備了其父系所有的操作接口,此外,額外擴(kuò)展了業(yè)務(wù)層面自定義的一些接口方法:

自定義Repository的時(shí)候,繼承JpaRepository需要傳入兩個(gè)泛型:
- 此Repository需要操作的具體Entity對(duì)象(Entity與具體DB中表映射,所以指定Entity也等同于指定了此Repository所對(duì)應(yīng)的目標(biāo)操作Table),
- 此Entity實(shí)體的主鍵數(shù)據(jù)類型(也就是第一個(gè)參數(shù)指定的Entity類中以@Id注解標(biāo)識(shí)的字段的類型)

分頁、排序,一招搞定
分頁,排序使用Pageable對(duì)象進(jìn)行傳遞,其中包含Page和Sort參數(shù)對(duì)象。
查詢的時(shí)候,直接傳遞Pageable參數(shù)即可(注意下,如果是用原生SQL查詢的方式,此法行不通,后文有詳細(xì)說明)。
// 定義repository接口的時(shí)候,直接傳入Pageable參數(shù)即可
List<UserEntity> findAllByDepartment(DepartmentEntity department, Pageable pageable);
還有一種特殊的分頁場(chǎng)景。比如,DB表中有100w條記錄,然后現(xiàn)在需要將這些數(shù)據(jù)全量的加載到ES中。如果逐條查詢?nèi)缓蟛迦隕S,顯然效率太慢;如果一次性全部查詢出來然后直接往ES寫,服務(wù)端內(nèi)存可能會(huì)爆掉。
這種場(chǎng)景,其實(shí)可以基于Slice結(jié)果對(duì)象進(jìn)行實(shí)現(xiàn)。Slice的作用是,只知道是否有下一個(gè)Slice可用,不會(huì)執(zhí)行count,所以當(dāng)查詢較大的結(jié)果集時(shí),只知道數(shù)據(jù)是足夠的就可以了,而且相關(guān)的業(yè)務(wù)場(chǎng)景也不用關(guān)心一共有多少頁。
private <T extends EsDocument, F> void fullLoadToEs(IESLoadService<T, F> esLoadService) {
try {
final int batchHandleSize = 10000;
Pageable pageable = PageRequest.of(0, batchHandleSize);
do {
// 批量加載數(shù)據(jù),返回Slice類型結(jié)果
Slice<F> entitySilce = esLoadService.slicePageQueryData(pageable);
// 具體業(yè)務(wù)處理邏輯
List<T> esDocumentData = esLoadService.buildEsDocumentData(entitySilce);
esUtil.batchSaveOrUpdateAsync(esDocumentData);
// 獲取本次實(shí)際上加載到的具體數(shù)據(jù)量
int pageLoadedCount = entitySilce.getNumberOfElements();
if (!entitySilce.hasNext()) {
break;
}
// 自動(dòng)重置page分頁參數(shù),繼續(xù)拉取下一批數(shù)據(jù)
pageable = entitySilce.nextPageable();
} while (true);
} catch (Exception e) {
log.error("error occurred when load data into es", e);
}
}
復(fù)雜搜索,其實(shí)不復(fù)雜
按照條件進(jìn)行搜索查詢,是項(xiàng)目中遇到的非常典型且常用的場(chǎng)景。但是條件搜索也分幾種場(chǎng)景,下面分開說下。
簡(jiǎn)單固定場(chǎng)景
所謂簡(jiǎn)單固定,即查詢條件就是固定的1個(gè)字段或者若干個(gè)字段,且查詢字段數(shù)量不會(huì)變,比如根據(jù)部門查詢具體人員列表這種。
這種情況,我們可以簡(jiǎn)單的直接在repository中,根據(jù)命名規(guī)范定義一個(gè)接口即可。
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
// 根據(jù)一個(gè)固定字段查詢
List<UserEntity> findAllByDepartment(DepartmentEntity department);
// 根據(jù)多個(gè)固定字段組合查詢
UserEntity findFirstByWorkIdAndUserNameAndDepartment(String workId, String userName, DepartmentEntity department);
}
簡(jiǎn)單不固定場(chǎng)景
考慮一種場(chǎng)景,界面上需要做一個(gè)用戶搜索的能力,要求支持根據(jù)用戶名、工號(hào)、部門、性別、年齡、職務(wù)等等若干個(gè)字段中的1個(gè)或者多個(gè)的組合來查詢符合條件的用戶信息。
顯然,上述通過直接在repository中按照命名規(guī)則定義接口的方式行不通了。這個(gè)時(shí)候,Example對(duì)象便排上用場(chǎng)了。
其實(shí)在前面整體介紹Repository的UML圖中,就已經(jīng)有了Example的身影了,雖然這個(gè)名字起的很敷衍,但其功能確是挺實(shí)在的。

看下具體用法:
public Page<UserEntity> queryUsers(Request request, UserEntity queryParams) {
// 查詢條件構(gòu)造出對(duì)應(yīng)Entity對(duì)象,轉(zhuǎn)為Example查詢條件
Example<UserEntity> example = Example.of(queryParams);
// 構(gòu)造分頁參數(shù)
Pageable pageable = PageHelper.buildPageable(request);
// 按照條件查詢,并分頁返回結(jié)果
return userRepository.findAll(example, pageable);
}
復(fù)雜場(chǎng)景
如果是一些自定義的復(fù)雜查詢場(chǎng)景,可以通過定制SQL語句的方式來實(shí)現(xiàn)。
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
@Query(
value = "select t.*,(select group_concat(a.assigner_name) from workflow_task a where a.state='R' and a.proc_inst_id=t.proc_inst_id) deal_person,"
+ " (select a.task_name from workflow_task a where a.state='R' and a.proc_inst_id=t.proc_inst_id limit 1) cur_step "
+ " from workflow_info t where t.state='R' and t.type in (?1) "
+ "and exists(select 1 from workflow_task b where b.assigner=?2 and b.state='R' and b.proc_inst_id=t.proc_inst_id) order by t.create_time desc",
countQuery = "select count(1) from workflow_info t where t.state='R' and t.type in (?1) "
+ "and exists(select 1 from workflow_task b where b.assigner=?2 and b.state='R' and b.proc_inst_id=t.proc_inst_id) ",
nativeQuery = true)
Page<FlowResource> queryResource(List<String> type, String workId, Pageable pageable);
}
此外,還可以基于JpaSpecificationExecutor提供的能力接口來實(shí)現(xiàn)。
自定義接口需要增加JpaSpecificationExecutor的繼承,然后利用Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);接口來實(shí)現(xiàn)復(fù)雜查詢能力。
// 增加對(duì)JpaSpecificationExecutor的繼承
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long>, JpaSpecificationExecutor<UserEntity> {
}
public List<UserEntity> queryUsers(QueryParams queryParams) {
// 構(gòu)造Specification查詢條件
Specification<UserEntity> specification =
(root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 范圍查詢條件構(gòu)造
predicates.add(cb.greaterThanOrEqualTo(root.get("age"), queryParams.getMinAge()));
predicates.add(cb.lessThanOrEqualTo(root.get("age"), queryParams.getMaxAge()));
// 精確匹配查詢條件構(gòu)造
predicates.add(cb.equal(root.get("department"), queryParams.getDepartment()));
// 關(guān)鍵字模糊匹配條件構(gòu)造
if (Objects.nonNull(queryParams.getNameKeyword())) {
predicates.add(cb.like(root.get("userName"), "%" + queryParams.getNameKeyword() + "%"));
}
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
};
// 執(zhí)行復(fù)雜查詢條件
return userRepository.findAll(specification);
}
自定義Listener,玩出花樣
實(shí)際項(xiàng)目中,經(jīng)常會(huì)有一種場(chǎng)景,就是需要監(jiān)聽某個(gè)數(shù)據(jù)的變更然后做一些額外的處理邏輯。一種邏輯,是寫操作的時(shí)候順便調(diào)用下相關(guān)業(yè)務(wù)的處理API,這樣會(huì)造成業(yè)務(wù)間耦合加深;優(yōu)化點(diǎn)的策略是搞個(gè)MQ隊(duì)列,然后在這個(gè)寫DB操作的同時(shí)發(fā)個(gè)消息到MQ里面,然后一堆的consumer會(huì)監(jiān)聽MQ并去做對(duì)應(yīng)的處理邏輯,這樣引入個(gè)消息隊(duì)列代價(jià)也有點(diǎn)高。
這個(gè)時(shí)候,我們可以借助JPA的自定義EntityListener功能來完美解決。通過監(jiān)聽某個(gè)Entity表的變更情況,通知或者調(diào)用相關(guān)其他的業(yè)務(wù)代碼處理,完美實(shí)現(xiàn)了與主體業(yè)務(wù)邏輯的解耦,也無需引入其他組件。
舉個(gè)例子:現(xiàn)有一個(gè)論壇發(fā)帖系統(tǒng),發(fā)帖Post和評(píng)論Comment屬于兩個(gè)相對(duì)獨(dú)立又有點(diǎn)關(guān)系的數(shù)據(jù),現(xiàn)在需要檢測(cè)當(dāng)評(píng)論變化的時(shí)候,需要更新下Post對(duì)應(yīng)記錄的評(píng)論數(shù)字段。下面演示下具體實(shí)現(xiàn)。
- 首先,定制一個(gè)Listener類,并指定Callbacks注解
public class CommentCountAuditListener {
/**
* 當(dāng)Comment表有新增數(shù)據(jù)的操作時(shí),觸發(fā)此方法的調(diào)用
*/
@PostPersist
public void postPersist(CommentEntity entity) {
// 執(zhí)行Post表中評(píng)論數(shù)字段的更新
// do something here...
}
/**
* 當(dāng)Comment表有刪除數(shù)據(jù)的操作時(shí),觸發(fā)此方法的調(diào)用
*/
@PostRemove
public void postRemove(CommentEntity entity) {
// 執(zhí)行Post表中評(píng)論數(shù)字段的更新
// do something here...
}
/**
* 當(dāng)Comment表有更新數(shù)據(jù)的操作時(shí),觸發(fā)此方法的調(diào)用
*/
@PostUpdate
public void postUpdate(CommentEntity entity) {
// 執(zhí)行Post表中評(píng)論數(shù)字段的更新
// do something here...
}
}
- 其次,在評(píng)論實(shí)體CommentEntity上,加上自定義Listener信息
@Entity
@Table("t_comment")
// 指定前面定制的Listener
@EntityListeners({CommentCountAuditListener.class})
public class CommentEntity extends AbstractAuditable {
// ...
}
這樣就搞定了。
自定義Listener還有個(gè)典型的使用場(chǎng)景,就是可以統(tǒng)一的記錄DB數(shù)據(jù)的操作日志。
定制化SQL,隨心所欲
JPA提供@Query注解,可以實(shí)現(xiàn)自定義SQL語句的能力。比如:
@Query(value = "select * from user " +
"where work_id in (?1) " +
"and department_id = 0 " +
"order by CREATE_TIME desc ",
nativeQuery = true)
List<OssFileInfoEntity> queryUsersByWorkIdIn(List<String> workIds);
如果需要執(zhí)行寫操作SQL的時(shí)候,需要額外增加@Modifying注解標(biāo)識(shí),如下:
@Modifying
@Query(value = "insert into user (work_id, user_name) values (?1, ?2)",
nativeQuery = true)
int createUser(String workId, String userName);
其中,nativeQuery = true表示@Query注解中提供的value值為原生SQL語句。如果nativeQuery未設(shè)置或者設(shè)置為false,則表示將使用JPQL語言來執(zhí)行。所謂JPQL,即JAVA持久化查詢語句,是一種類似SQL的語法,不同點(diǎn)在于其使用類名來替代表名,使用類字段來替代表字段名。比如:
@Query("SELECT u FROM com.vzn.demo.UserInfo u WHERE u.userName = ?1")
public UserInfo getUserInfoByName(String name);
幾個(gè)關(guān)注點(diǎn)要特別闡述下:
- like查詢的時(shí)候,參數(shù)前后的
%需要手動(dòng)添加,系統(tǒng)是不會(huì)自動(dòng)加上的
// like 需要手動(dòng)添加百分號(hào)
@Query("SELECT u FROM com.vzn.demo.UserInfo u WHERE u.userName like %?1")
public UserInfo getUserInfoByName(String name);
- 使用
nativeQuery=true查詢的時(shí)候(原生SQL方式),不支持API接口里面?zhèn)魅隨ort對(duì)象然后進(jìn)行混合執(zhí)行
// 錯(cuò)誤示范: 自定義sql與API中Sort參數(shù)不可同時(shí)混用
@Query("SELECT * FROM t_user u WHERE u.user_name = ?1", nativeQuery=true)
public UserInfo getUserInfoByName(String name, Sort sort);
// 正確示范: 自定義SQL完成對(duì)應(yīng)sort操作
@Query("SELECT * FROM t_user u WHERE u.user_name = ?1 order by ?2", nativeQuery=true)
public UserInfo getUserInfoByName(String name, String sortColumn);
- 未指定
nativeQuery=true查詢的時(shí)候(JPQL方式),支持API接口里面?zhèn)魅?code>Sort、PageRequest等對(duì)象然后進(jìn)行混合執(zhí)行,來完成排序、分頁等操作
// 正確:自定義jpql與API中Sort參數(shù)不可同時(shí)混用
@Query("SELECT u FROM com.vzn.demo.UserInfo u WHERE u.userName = ?1")
public UserInfo getUserInfoByName(String name, Sort sort);
- 支持使用參數(shù)名作為
@Query查詢中的SQL或者JPQL語句的入?yún)ⅲ〈鷧?shù)順序占位符
默認(rèn)情況下,參數(shù)是通過順序綁定在自定義執(zhí)行語句上的,這樣如果API接口傳參順序或者位置改變,極易引起自定義查詢傳參出問題,為了解決此問題,我們可以使用@Param注解來綁定一個(gè)具體的參數(shù)名稱,然后以參數(shù)名稱的形式替代位置順序占位符,這也是比較推薦的一種做法。
// 默認(rèn)的順序位置傳參
@Query("SELECT * FROM t_user u WHERE u.user_name = ?1 order by ?2", nativeQuery=true)
public UserInfo getUserInfoByName(String name, String sortColumn);
// 使用參數(shù)名稱傳參
@Query("SELECT * FROM t_user u WHERE u.user_name = :name order by :sortColumn", nativeQuery=true)
public UserInfo getUserInfoByName(@Param("name") String name, @Param("sortColumn") String sortColumn);
字段命名映射策略
一般而言,JAVA的編碼規(guī)范都要求filed字段命名需要遵循小駝峰命名的規(guī)范,比如userName,而DB中column命名的時(shí)候,很多人習(xí)慣于使用下劃線分隔的方式命名,比如user_name這種。這樣就涉及到一個(gè)映射的策略問題,需要讓JPA知道代碼里面的userName就對(duì)應(yīng)著DB中的user_name。
這里就會(huì)涉及到對(duì)命名映射策略的映射。主要有兩種映射配置,下面分別闡述下。
- implicit-strategy
配置項(xiàng)key值:
spring.jpa.hibernate.naming.implicit-strategy=xxxxx
取值說明:
| 值 | 映射規(guī)則說明 |
|---|---|
| org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImp | 默認(rèn)的命名策略,兼容JPA2.0規(guī)范 |
| org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl | 兼容老版本Hibernate的命名規(guī)范 |
| org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl | 與ImplicitNamingStrategyJpaCompliantImp基本相同 |
| org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl | 兼容JPA 1.0規(guī)范中的命名規(guī)范。 |
| org.hibernate.boot.model.naming.SpringImplicitNamingStrategy | 繼承ImplicitNamingStrategyJpaCompliantImpl,對(duì)外鍵、鏈表查詢、索引如果未定義,都有下劃線的處理策略,而table和column名字都默認(rèn)與字段一樣 |
- physical-strategy
配置項(xiàng)key值:
spring.jpa.hibernate.naming.physical-strategy=xxxxx
取值說明:
| 值 | 映射規(guī)則說明 |
|---|---|
| org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl | 默認(rèn)字符串一致映射,不做任何轉(zhuǎn)換處理,比如java類中userName,映射到table中列名也叫userName |
| org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy | java類中filed名稱小寫字母進(jìn)行映射到DB表column名稱,遇大寫字母時(shí)轉(zhuǎn)為分隔符"_"命名格式,比如java類中userName字段,映射到DB表column名稱叫user_name |
- physical-strategy與implicit-strategy
SpringData JPA只是對(duì)JPA規(guī)范的二次封裝,其底層使用的是Hibernate,所以此處涉及到Hibernate提供的一些處理策略。Hibernate將對(duì)象模型映射到關(guān)系數(shù)據(jù)庫分為兩個(gè)步驟:
- 從對(duì)象模型中確定邏輯名稱。邏輯名可以由用戶顯式指定(使用
@Column或@Table),也可以隱式指定。 - 將邏輯名稱映射到物理名稱,也就是數(shù)據(jù)庫中使用的名稱。
這里,implicit-strategy用于第一步隱式指定邏輯名稱,而physical-strategy則用于第二步中邏輯名稱到物理名稱的映射。
注意:
當(dāng)沒有使用@Table和@Column注解時(shí),implicit-strategy配置項(xiàng)才會(huì)被使用,即implicit-strategy定義的是一種缺省場(chǎng)景的處理策略;而physical-strategy屬于一種高優(yōu)先級(jí)的策略,只要設(shè)置就會(huì)被執(zhí)行,而不管是否有@Table和@Column注解。
小結(jié),承上啟下
好啦,本篇內(nèi)容就介紹到這里。
通過本篇的內(nèi)容,我們對(duì)于如何在項(xiàng)目中使用Spring Data JPA來進(jìn)行一些較為復(fù)雜場(chǎng)景的處理方案與策略有了進(jìn)一步的了解,再結(jié)合本系列此前的內(nèi)容,到此掌握的JPA的相關(guān)技能已經(jīng)足以應(yīng)付大部分項(xiàng)目開發(fā)場(chǎng)景。
在實(shí)際項(xiàng)目中,為了保障數(shù)據(jù)操作的可靠、避免臟數(shù)據(jù)的產(chǎn)生,需要在代碼中加入對(duì)數(shù)據(jù)庫操作的事務(wù)控制。在下一篇文檔中,我們將一起聊一聊Spring Data JPA業(yè)務(wù)代碼開發(fā)中關(guān)于數(shù)據(jù)庫事務(wù)的控制,以及編碼中存在哪些可能會(huì)導(dǎo)致事務(wù)失效的場(chǎng)景等等。
如果對(duì)本文有自己的見解,或者有任何的疑問或建議,都可以留言,我們一起探討、共同進(jìn)步。
補(bǔ)充
Spring Data JPA作為Spring Data中對(duì)于關(guān)系型數(shù)據(jù)庫支持的一種框架技術(shù),屬于ORM的一種,通過得當(dāng)?shù)氖褂茫梢源蟠蠛?jiǎn)化開發(fā)過程中對(duì)于數(shù)據(jù)操作的復(fù)雜度。本文檔隸屬于《
Spring Data JPA用法與技能探究》系列的第3篇。本系列文檔規(guī)劃對(duì)Spring Data JPA進(jìn)行全方位的使用介紹,一共分為5篇文檔,如果感興趣,歡迎關(guān)注交流。《Spring Data JPA用法與技能探究》系列涵蓋內(nèi)容:
- 開篇介紹 —— 《Spring Data JPA系列1:JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?給你個(gè)選擇SpringDataJPA的理由!》
- 快速上手 —— 《Spring Data JPA系列2:SpringBoot集成JPA詳細(xì)教程,快速在項(xiàng)目中熟練使用JPA》
- 深度進(jìn)階 —— 《Spring Data JPA系列3:JPA項(xiàng)目中核心場(chǎng)景與進(jìn)階用法介紹》
- 可靠保障 —— 《聊一聊數(shù)據(jù)庫的事務(wù),以及Spring體系下對(duì)事務(wù)的使用》
- 周邊擴(kuò)展 —— 《JPA開發(fā)輔助效率提升方案介紹》
我是悟道,聊技術(shù)、又不僅僅聊技術(shù)~
如果覺得有用,請(qǐng)點(diǎn)個(gè)關(guān)注,也可以關(guān)注下我的公眾號(hào)【架構(gòu)悟道】,獲取更及時(shí)的更新。
期待與你一起探討,一起成長(zhǎng)為更好的自己。