Spring Data JPA系列2:SpringBoot集成JPA詳細(xì)教程梳星,快速在項(xiàng)目中熟練使用JPA

image.png

大家好赞赖,又見面了。

這是Spring Data JPA系列的第2篇冤灾,在上一篇《Spring Data JPA系列1:JDBC前域、ORM、JPA韵吨、Spring Data JPA话侄,傻傻分不清楚?給你個(gè)選擇SpringDataJPA的理由学赛!》中年堆,我們對JPA的基本概念有了一個(gè)整體的了解,也對JAVA中進(jìn)行DB操作的一些周邊框架盏浇、概念等有了初步的感知变丧。同時(shí)也給出了SpringData JPA與MyBatis的選擇判斷依據(jù)。

那么绢掰,如果你已經(jīng)決定使用SpringData JPA來作為項(xiàng)目中DB操作的框架痒蓬,具體應(yīng)該如何去做呢?

作為SpringData JPA系列內(nèi)容的第二篇滴劲,此處以SpringBoot項(xiàng)目為基準(zhǔn)攻晒,講一下集成SpringData JPA的相關(guān)要點(diǎn),帶你快速的上手SpringData JPA班挖,并用實(shí)例演示常見的DB操作場景鲁捏,讓你分分鐘輕松玩轉(zhuǎn)JPA。

SpringBoot集成SpringData JPA

依賴引入

SpringBoot項(xiàng)目工程萧芙,在pom.xml中引入相關(guān)依賴包即可:


<!-- 數(shù)據(jù)庫相關(guān)操作 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

入口注解

SpringData JPA提供了部分注解给梅,可以添加在Application入口程序類上方假丧,來滿足相關(guān)訴求。當(dāng)然如果沒有額外的特殊訴求动羽,則可以什么都不需要加包帚。


@SpringBootApplication
// 可選,指定掃描的表映射實(shí)體Entity的目錄运吓,如果不指定渴邦,會掃描全部目錄
//@EntityScan("com.veezean.demo.entity")
// 可選,指定掃描的表repository目錄拘哨,如果不指定几莽,會掃描全部目錄
//@EnableJpaRepositories(basePackages = {"com.veezean.demo.repository"})
// 可選,開啟JPA auditing能力宅静,可以自動賦值一些字段章蚣,比如創(chuàng)建時(shí)間、最后一次修改時(shí)間等等
@EnableJpaAuditing
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}


這里@EntityScan@EnableJpaRepositories被注釋掉了姨夹,且默認(rèn)的情況下是不需要添加這個(gè)配置的纤垂,JPA會自動掃描程序所在包內(nèi)的所有定義的Entity和Repository對象并加載。但是磷账,某些比較大型的項(xiàng)目里面峭沦,我們可能會封裝一個(gè)common jar作為項(xiàng)目公共依賴,然后再分出若干子項(xiàng)目逃糟,每個(gè)子項(xiàng)目里面依賴common jar吼鱼,這個(gè)時(shí)候如果想要加載common jar里面定義的Entity和Repository,就需要用到這兩個(gè)注解绰咽。

參數(shù)配置

在application.properties中配置一些數(shù)據(jù)庫連接信息,如下:


spring.datasource.url=jdbc:mysql://<ip>:<port>/vzn-demo?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
spring.datasource.username=vzn-demo
spring.datasource.password=<password>

#Java代碼實(shí)體字段命名與數(shù)據(jù)庫表結(jié)構(gòu)字段之間的名稱映射策略
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
#下面配置開啟后菇肃,會禁止將駝峰轉(zhuǎn)為下劃線
#spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
# 控制是否可以基于程序中Entity的定義自動創(chuàng)建或者修改DB中表結(jié)構(gòu)
spring.jpa.properties.hibernate.hbm2ddl.auto=update
# 控制是否打印運(yùn)行時(shí)的SQL語句與參數(shù)信息
spring.jpa.show-sql=true

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-life-time=1800000

基礎(chǔ)編碼實(shí)操

通過前面的幾個(gè)步驟的操作,便完成了SpringData JPA與項(xiàng)目的集成對接取募。本章節(jié)介紹下在業(yè)務(wù)代碼里面應(yīng)該如何使用SpringData JPA來完成一些DB交互操作琐谤。

Table對應(yīng)Entity編寫

編寫數(shù)據(jù)庫中Table對應(yīng)的JAVA實(shí)體映射類,并通過相關(guān)注解玩敏,來描述字段的一些附加約束信息斗忌。

  • 用戶表實(shí)體

@Data
@Entity
@Table(name = "user")
@EntityListeners(value = AuditingEntityListener.class)
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String workId;
    private String userName;
    @ManyToOne(optional = false)
    @JoinColumn(name = "department")
    private DepartmentEntity department;
    @CreatedDate
    private Date createTime;
    @LastModifiedDate
    private Date updateTime;

}

  • 部門表實(shí)體

@Data
@Entity
@Table(name = "department")
@EntityListeners(value = AuditingEntityListener.class)
public class DepartmentEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String deptName;
    @CreatedDate
    private Date createTime;
    @LastModifiedDate
    private Date updateTime;
}

這里可以看到,所謂的Entity旺聚,其實(shí)也就是一個(gè)普通的JAVA數(shù)據(jù)類织阳,只是與普通的JAVA數(shù)據(jù)類相比,多了一些注解砰粹。沒錯(cuò)唧躲!SpringData JPA正式通過各種注解,來完成對各個(gè)字段的定義與行為約束,以及完成表間關(guān)聯(lián)關(guān)系(比如外鍵)惊窖。

常見的一些注解以及含義功能說明,在本文的末尾表格里面進(jìn)行了梳理厘贼,此處不贅述界酒。

自定義Repository編寫

繼承JpaRepository接口提供自定義Repository接口類,在自定義接口類中嘴秸,添加業(yè)務(wù)需要的定制化的DB操作接口毁欣。這里定制的時(shí)候,可以基于SpringData JPA的命名規(guī)范進(jìn)行接口方法的命名即可岳掐,無需關(guān)注其具體實(shí)現(xiàn)凭疮,也不需要提供實(shí)現(xiàn)類。

  • 用戶repository

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    List<UserEntity> findAllByDepartment(DepartmentEntity department);
    UserEntity findFirstByWorkId(String workId);
    List<UserEntity> findAllByDepartmentInAndUserNameLike(List<DepartmentEntity> departmentIds, String userName);

    @Query(value = "select * from user where user_name like ?1", nativeQuery = true)
    List<UserEntity> fuzzyQueryByName(String userName);
}

上述代碼里面串述,演示了2種自定義接口的策略:

  • 基于SpringData JPA的命名規(guī)范执解,直接定義接口
  • 使用自定義的SQL語句進(jìn)行個(gè)性化定制,這種適用于一些需要高度定制化處理的場景

JPA中支持的一些命名關(guān)鍵字與命名示例纲酗,參見本文后面梳理的表格衰腌。

業(yè)務(wù)層執(zhí)行DB操作

寫入數(shù)據(jù)

SpringData JPA寫操作邏輯很簡單,只有一個(gè)save方法即可觅赊,如果批量寫入操作右蕊,使用saveAll方法即可。

  • 會判斷ID吮螺,如果唯一ID已存在饶囚,則按照update邏輯執(zhí)行;
  • 如果唯一ID記錄不存在鸠补,則按照insert邏輯執(zhí)行萝风。

public void testUser() {
    DepartmentEntity deptEntity1 = new DepartmentEntity();
    deptEntity1.setDeptName("研發(fā)部門");
    deptEntity1.setId(1L);
    DepartmentEntity deptEntity2 = new DepartmentEntity();
    deptEntity2.setDeptName("產(chǎn)品部門");
    deptEntity2.setId(2L);
     // 寫入部門信息
    departmentRepository.save(deptEntity1);
    departmentRepository.save(deptEntity2);
    departmentRepository.flush();
   
    UserEntity entity1 = new UserEntity();
    entity1.setWorkId("123456");
    entity1.setDepartment(deptEntity1);
    entity1.setUserName("王小二");
    UserEntity entity2 = new UserEntity();
    entity2.setWorkId("234567");
    entity2.setDepartment(deptEntity1);
    entity2.setUserName("王小五");
    UserEntity entity3 = new UserEntity();
    entity3.setWorkId("345678");
    entity3.setDepartment(deptEntity1);
    entity3.setUserName("劉大壯");
    UserEntity entity4 = new UserEntity();
    entity4.setWorkId("345678");
    entity4.setDepartment(deptEntity2);
    entity4.setUserName("張三");
     // 寫入用戶信息
    userRepository.saveAll(Stream.of(entity1, entity2, entity3, entity4).collect(Collectors.toList()));
    userRepository.flush();
}

執(zhí)行調(diào)用后,查看數(shù)據(jù)庫紫岩,可見數(shù)據(jù)已經(jīng)寫入DB中:

  • Department表
image.png
  • User表
image.png

從上面可以看出闹丐,代碼里面其實(shí)并沒有對create_time和update_time字段進(jìn)行賦值,但是數(shù)據(jù)存儲到DB的時(shí)候被因,這兩個(gè)字段被自動賦值了卿拴,這個(gè)主要是因?yàn)殚_啟了自動Audit能力,主要2個(gè)地方的代碼有關(guān)系:

1梨与、Application啟動類上的注解堕花,開啟允許JPA自動Audit能力
@EnableJpaAuditing

2、Entity類上添加注解
@EntityListeners(value = AuditingEntityListener.class)

3粥鞋、Entity中具體字段上加上對應(yīng)注解:
@CreatedDate
private Date createTime;
@LastModifiedDate
private Date updateTime;

查詢數(shù)據(jù)

常見的數(shù)據(jù)查詢操作缘挽,代碼層面實(shí)現(xiàn)調(diào)用如下:



public void testUser() {
    DepartmentEntity deptEntity1 = new DepartmentEntity();
    deptEntity1.setDeptName("研發(fā)部門");
    deptEntity1.setId(1L);
    DepartmentEntity deptEntity2 = new DepartmentEntity();
    deptEntity2.setDeptName("產(chǎn)品部門");
    deptEntity2.setId(2L);
    // 獲取所有用戶列表 --- JPA默認(rèn)提供的方法
    List<UserEntity> userEntities = userRepository.findAll();
    log.info("findAll result :{}", userEntities);
    // 獲取符合條件用戶列表 --- 定制方法: 根據(jù)部門字段查詢符合條件列表
    List<UserEntity> userEntitiesInDept = userRepository.findAllByDepartment(deptEntity1);
    log.info("findAllByDepartment result count:{}", userEntitiesInDept);
    // 獲取符合條件用戶 --- 定制方法: 根據(jù)工號查詢用戶信息
    UserEntity userEntity = userRepository.findFirstByWorkId("123456");
    log.info("findFirstByWorkId result: {}", userEntity);
    // 多條件查詢符合條件用戶列表 --- 定制方法: 根據(jù)部門與名稱字段復(fù)合查詢
    List<UserEntity> fuzzyQueryUsers = userRepository.findAllByDepartmentInAndUserNameLike(Stream.of(deptEntity1,
            deptEntity2).collect(Collectors.toList()),
            "王%");
    log.info("findAllByDepartmentInAndUserNameLike result count: {}", fuzzyQueryUsers);
}

從上面的演示代碼可以看出,SpringData JPA的一個(gè)很大的優(yōu)勢,就是Repository層可以簡化大部分場景的代碼編碼事務(wù)壕曼,遵循一定的方法命名規(guī)范苏研,即可實(shí)現(xiàn)相關(guān)的能力。

比如:

    List<UserEntity> findAllByDepartmentInAndUserNameLike(List<DepartmentEntity> departmentIds, String userName);

看方法名就直接可以知道這個(gè)具體的DB操作邏輯:在給定的部門列表里面查詢所有名稱可以模糊匹配上的人員列表腮郊!至于如何去具體實(shí)現(xiàn)摹蘑,這個(gè)開發(fā)人員無需關(guān)注、也不需要去寫對應(yīng)SQL語句轧飞!

藏在配置中的小技能

在前面章節(jié)中有介紹集成SpringData JPA涉及到的一些常見配置衅鹿,此處對其中部分配置的含義與功能進(jìn)行一個(gè)補(bǔ)充介紹。

控制是否自動基于代碼Entity定義自動創(chuàng)建變更數(shù)據(jù)庫表結(jié)構(gòu)

spring.jpa.properties.hibernate.hbm2ddl.auto=update

如果設(shè)置為update过咬,程序運(yùn)行之后大渤,會自動在DB中將Table創(chuàng)建出來,并且相關(guān)約束條件(比如自增主鍵掸绞、關(guān)聯(lián)外鍵之類的)也會一并創(chuàng)建并設(shè)置上去泵三,如下示意,左側(cè)的代碼自動創(chuàng)建出右側(cè)DB中的表結(jié)構(gòu):

image.png

補(bǔ)充說明

雖然這個(gè)功能比較方便衔掸,但是強(qiáng)烈建議在生產(chǎn)環(huán)境上關(guān)閉此功能切黔。因?yàn)镈B表結(jié)構(gòu)改動變更,對于生產(chǎn)環(huán)境而言具篇,是一個(gè)非常重大的操作纬霞,一旦出問題甚至?xí)绊懙綄?shí)際數(shù)據(jù)。為了避免造成不可逆的危害驱显,保險(xiǎn)起見诗芜,還是人工手動操作變更下比較好。

控制是否打印相關(guān)操作的SQL語句

spring.jpa.show-sql=true

如果設(shè)置為true埃疫,則會在日志中打印每次DB操作所執(zhí)行的最終SQL語句內(nèi)容伏恐,這個(gè)比較適合與開發(fā)過程中的問題定位分析,生產(chǎn)環(huán)境上建議關(guān)閉(影響性能)栓霜。

如果開啟后翠桦,打印的日志示例如下:


2022-06-14 14:30:50.329  INFO 23380 --- [io-48080-exec-3] o.a.c.c.C.[.[localhost].[/veezean-demo]  : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-06-14 14:30:50.329  INFO 23380 --- [io-48080-exec-3] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-06-14 14:30:50.337  INFO 23380 --- [io-48080-exec-3] o.s.web.servlet.DispatcherServlet        : Completed initialization in 8 ms
Hibernate: insert into department (create_time, dept_name, update_time) values (?, ?, ?)
Hibernate: insert into department (create_time, dept_name, update_time) values (?, ?, ?)
Hibernate: insert into user (create_time, department, update_time, user_name, work_id) values (?, ?, ?, ?, ?)
Hibernate: insert into user (create_time, department, update_time, user_name, work_id) values (?, ?, ?, ?, ?)
Hibernate: insert into user (create_time, department, update_time, user_name, work_id) values (?, ?, ?, ?, ?)
Hibernate: insert into user (create_time, department, update_time, user_name, work_id) values (?, ?, ?, ?, ?)
2022-06-14 14:30:50.544  INFO 23380 --- [io-48080-exec-3] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory

了解幾個(gè)"常識"概念

通過前面內(nèi)容的介紹以及相關(guān)示例代碼的演示,可以看出SpringData JPA中有很多情況都是借助不同注解來約定一些屬性或者處理邏輯策略的胳蛮,且在自定義接口方法的時(shí)候销凑,需要遵循SpringData JPA固有的一套命名規(guī)范才行。

這里對一些高頻易用的注解與常見的接口方法命名規(guī)范進(jìn)行梳理介紹仅炊。

常用注解

image.png
image.png
image.png

Repository方法命名約定

DB里面一些關(guān)鍵字對應(yīng)的SpringData JPA中命名關(guān)鍵字列舉如下:

image.png
image.png

小結(jié)斗幼,承上啟下

好啦,本篇內(nèi)容就介紹到這里抚垄。

跟著本篇內(nèi)容蜕窿,可以讓你順利的完成SpringBoot項(xiàng)目與JPA的集成配置谋逻,以及對項(xiàng)目中如何使用JPA進(jìn)行代碼開發(fā)有了個(gè)整體的感知,可以應(yīng)付大部分場景的基礎(chǔ)業(yè)務(wù)代碼開發(fā)訴求桐经。

本系列教程是按照由面到點(diǎn)毁兆、由淺入深的邏輯進(jìn)行內(nèi)容編排的。在本系列的下一篇內(nèi)容中阴挣,我會進(jìn)一步對SpringData JPA中的一些核心類型與核心方法進(jìn)行剖析气堕,讓你不僅僅停留在簡單使用層面,更能對JPA有個(gè)深度的了解屯吊、達(dá)到精通級別送巡。如果感興趣的話摹菠,歡迎關(guān)注我的后續(xù)系列文檔盒卸。

如果對本文有自己的見解,或者有任何的疑問或建議次氨,都可以留言蔽介,我們一起探討、共同進(jìn)步煮寡。


補(bǔ)充

Spring Data JPA作為Spring Data中對于關(guān)系型數(shù)據(jù)庫支持的一種框架技術(shù)虹蓄,屬于ORM的一種,通過得當(dāng)?shù)氖褂眯宜海梢源蟠蠛喕_發(fā)過程中對于數(shù)據(jù)操作的復(fù)雜度薇组。

本文檔隸屬于《Spring Data JPA用法與技能探究》系列的第二篇。本系列文檔規(guī)劃對Spring Data JPA進(jìn)行全方位的使用介紹坐儿,一共分為5篇文檔律胀,如果感興趣,歡迎關(guān)注交流貌矿。

《Spring Data JPA用法與技能探究》系列涵蓋內(nèi)容:


我是悟道枷踏,聊技術(shù)玛荞、又不僅僅聊技術(shù)~

如果覺得有用,請點(diǎn)個(gè)關(guān)注呕寝,也可以關(guān)注下我的公眾號【架構(gòu)悟道】勋眯,獲取更及時(shí)的更新婴梧。

期待與你一起探討,一起成長為更好的自己客蹋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塞蹭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子讶坯,更是在濱河造成了極大的恐慌番电,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辆琅,死亡現(xiàn)場離奇詭異漱办,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)婉烟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門娩井,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人似袁,你說我怎么就攤上這事洞辣。” “怎么了昙衅?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵扬霜,是天一觀的道長。 經(jīng)常有香客問我而涉,道長著瓶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任啼县,我火速辦了婚禮材原,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谭羔。我一直安慰自己华糖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布瘟裸。 她就那樣靜靜地躺著客叉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪话告。 梳的紋絲不亂的頭發(fā)上兼搏,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音沙郭,去河邊找鬼佛呻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柿祈,長吁一口氣:“原來是場噩夢啊……” “哼绑莺!你這毒婦竟也來了暖眼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤纺裁,失蹤者是張志新(化名)和其女友劉穎诫肠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欺缘,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栋豫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谚殊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丧鸯。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖络凿,靈堂內(nèi)的尸體忽然破棺而出骡送,到底是詐尸還是另有隱情昂羡,我是刑警寧澤絮记,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站虐先,受9級特大地震影響怨愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛹批,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一撰洗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腐芍,春花似錦差导、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泣刹,卻和暖如春助析,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背椅您。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工外冀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掀泳。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓雪隧,卻偏偏與公主長得像西轩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子脑沿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容