參考文章:
第1章 Spring Boot到底是什麼?_陳小房的部落格
第2章 第一個Spring Boot項目_陳小房的部落格
第3章 Spring Boot項目配置詳解_陳小房的部落格
第4章 Spring Boot的Web應用開發入門_陳小房的部落格
第5章 Spring Boot模闆Thymeleaf入門詳解_陳小房的部落格
第6章 MyBatis架構入門詳解(1)_陳小房的部落格
第7章 MyBatis架構入門詳解(2)_陳小房的部落格
第8章 使用注解的方式整合MyBatis 入門詳解_陳小房的部落格
目錄
1. 什麼是JPA
1.1 JPA和MyBatis的關系
2. Spring Data JPA
2.1 配置SpringData JPA
3. 驗證Spring Data JPA
4. JpaRepository類定義
5. JpaRepository基礎方法
5.1 新增資料
save
saveAll
5. 2 更新資料庫方法
5.3 查找資料
findById
findAll
findAllById
existsById
findAll(Sort sort)
findAll(Pageable pageable)
count
5.4 資料删除
deleteById
deleteAll()
1. 什麼是JPA
JPA(Java Persistence API, Java 持久化API)是SUN公司提出的Java持久化規範,它提供了一種對象/關系映射的管理工具來管理Java中的關系型資料庫。JPA的主要目的是簡化現有的持久化開發工作并且整合ORM架構,JPA本身并不是ORM架構,它是一種規範,這種規範可以私下通過注解或者XML描述“對象-關系表”之間的映射關系,并将實體對象持久化到資料庫中,進而極大地簡化現有的持久化開發工作。
1.1 JPA和MyBatis的關系
實作JPA的架構有Hibernate, TopLink, 而我們前面介紹的MyBatis不屬于實作JPA的架構,主要差別有:
- Mybatis是對象和結果集的映射,而JPA規範強調的是對象和關系表之間的映射。
- 遵守JPA規範的架構具有良好的移植性,不用關心用什麼資料庫,而Mybatis架構在更改資料庫時需要修改底層SQL。
2. Spring Data JPA
2.1 配置SpringData JPA
Spring Data 是Spring 的一個子項目, 旨在統一和簡化對各類型持久化存儲, 而不拘泥于是關系型資料庫還是NoSQL 資料存儲。
Spring Data JPA是Spring Data項目的一部分,它是在ORM架構思想、JPA規範的基礎上封裝的一套JPA應用架構。使用Spring Data JPA隻需要繼承和擴充Spring Data 中統一的資料通路接口Repository接口無需編寫SQL實作資料庫通路。
在pom.xml檔案中添加spring-boot-starter-data-jpa依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
在資料庫中建立一個schema用于JPA測試
然後在application.properties檔案中進行相關配置
spring.datasource.url=jdbc:mysql://localhost:3306/jpa_demo?useSSL=true&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=itJMF-4RObQ2
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#JPA 配置
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
這裡對JPA配置進行簡單說明
spring.jpa.hibernate.ddl-auto
該配置比較常用,配置實體類維護資料庫表結構的具體行為。它主要有4個選項
- create: 啟動時删資料庫中的表,然後建立新表,退出時不删除資料表
- create-drop: 啟動時删資料庫中的表,然後建立,退出時删除資料表 如果表不存在則報錯
- update: 如果啟動時表格式不一緻則更新表,原有資料保留
- validate: 項目啟動表結構進行校驗 如果不一緻則報錯”
這裡我們選擇了update
spring.jpa.show-sql=true 操作時在控制台列印真實的SQL語句,便于調試
spring.jpa.properties.hibernate.format_sql=true 以JSON格式列印輸出SQL語句友善檢視
3. 驗證Spring Data JPA
首先,建立Scenic實體類,它是一個實體類,按照JPA的設計思想,這個實體類也是定義資料庫中的表結構的類,示例代碼如下
@Entity
@Table(name = "scenics")
public class Scenic {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private int scenicId;
private int cityId;
//@Column(name = "scenicName")
@Column(length = 64)
private String scenicName;
private int price;
public Scenic() {
}
public Scenic(Integer cityId, String scenicName, int price ){
this.cityId = cityId;
this.scenicName = scenicName;
this.price = price;
}
public int getScenicId() {
return scenicId;
}
public void setScenicId(int scenicId) {
this.scenicId = scenicId;
}
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getScenicName() {
return scenicName;
}
public void setScenicName(String scenicName) {
this.scenicName = scenicName;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
注意這個實體類中使用了JPA相關的注解
@Entity 這個注解是使用JPA時必須的注解,代表這個類對應了一個資料庫表
@Table 這是一個可選的注解,用于說明資料庫實體對應的表資訊,包括表的名稱、索引資訊等,如果沒有則表名和實體類的名稱一緻,一般情況下我們不使用該注解,這裡為了示範,我們使用該注解将表名申明為Scenics
@Id 代表對應的主鍵
@GeneratedValue 設定資料庫主鍵自動生成規則。strategy屬性提供4種選項
AUTO:主鍵由程式控制,是預設選項。
IDENTITY:主鍵由資料庫自動生成,即采用資料庫ID自增長的方式,注意Oracle不支援這種方式。
SEQUENCE:通過資料庫的序列産生主鍵,通過@SequenceGenerator注解指定序列名,注意MySQL不支援這種方式。
TABLE:通過特定的資料庫表産生主鍵,使用該政策可以使應用更易于資料庫移植。
@Column注解:聲明實體屬性的表字段的定義。預設的實體每個屬性都對應表的一個字段,字段名預設與屬性名保持一緻。字段的類型根據實體屬性類型自動對應。這裡主要聲明了字元字段的長度length,如果不聲明,則系統會采用255作為該字段的長度。
這裡需要注意JPA自動建表的字段命名規則采用的是下劃線,如屬性scenicName會将生成資料庫字段scenic_name,如果需要生成非下劃線字段,可以采用@Column(name = "ScenicName")
運作測試
運作Spring boot應用,控制台輸出
檢視資料庫,可以看到已經自動建立了scenics資料庫表
4. JpaRepository類定義
接下來學習如何通過類來操作資料庫,在Spring Data JPA中使用JpaRepository接口類完成對資料庫的操作。JpaRepository是Spring Data JPA中最重要的類之一。
這是JpaRepository接口的定義
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>
可以看出JpaRepository繼承了接口PagingAndSortingRepository和QueryByExampleExecutor。而PagingAndSortingRepository又繼承CrudRepository。
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
是以JpaRepository接口同時擁有了基本CRUD功能以及分頁功能。
JpaRepository提供了30多個預設方法,基本能滿足項目中的資料庫操作功能。當我們需要定義自己的Repository接口的時候,我們可以直接繼承JpaRepository,進而獲得SpringBoot Data JPA為我們内置的多種基本資料操作方法。
5. JpaRepository基礎方法
下面對主要的方法進行測試
在項目目錄下建立repository包,添加接口檔案ScenicRepository
代碼如下
@Repository
public interface ScenicRepository extends JpaRepository<Scenic, Integer> {
}
在測試用例類Chapter9ApplicationTests中添加測試方法進行測試
5.1 新增資料
save
JPARepository接口提供save方法用于新增資料,它的使用很簡單,隻要傳入需要新增的實體類即可
@Resource
private ScenicRepository scenicRepository;
@Test
public void testSave() {
Scenic scenic = new Scenic(1, "scenic1", 10);
scenicRepository.save(scenic);
}
執行結果
saveAll
如果需要批量新增則可以使用saveAll方法
@Test
public void testSaveAll() {
List<Scenic> scenicList = new ArrayList<>();
scenicList.add(new Scenic(11, "savaAll_11", 100));
scenicList.add(new Scenic(12, "saveAll_12", 200));
scenicList.add(new Scenic(13, "saveAll_13", 300));
scenicRepository.saveAll(scenicList);
}
執行結果
5. 2 更新資料庫方法
更新已有資料庫的方法也是save
@Test
public void testUpdate() {
Scenic scenic = scenicRepository.findById(1).get();
scenic.setScenicName("scenicUpdate");
scenicRepository.save(scenic);
}
執行結果
5.3 查找資料
findById
可以根據id查詢對應的實體,函數原型如下
/**
* 根據id查詢對應的實體。
*/
Optional<T> findById(ID id);
測試代碼
@Test
public void testFindById() {
Scenic scenic = scenicRepository.findById(2).get();
System.out.println(scenic.getScenicName() + ":" + scenic.getScenicId());
}
執行輸出
findAll
查詢所有的實體。方法原型
Iterable<T> findAll();
測試代碼
@Test
public void testFindAll() {
List<Scenic> scenicList = scenicRepository.findAll();
for(Scenic scenic: scenicList) {
System.out.println( scenic.getScenicName()+ ":" + scenic.getScenicId() + ":" + scenic.getPrice());
}
}
測試結果
findAllById
如果不想查詢全部資料,但是需要根據給定的Id查詢部分資料,可以使用findAllById
@Test
public void testFindAllById() {
List<Integer> idList = new ArrayList<>();
idList.add(2);
idList.add(3);
List<Scenic> scenicList = scenicRepository.findAllById(idList);
for (Scenic scenic : scenicList) {
System.out.println(scenic.getScenicName() + ":" + scenic.getScenicId() + ":" + scenic.getPrice());
}
}
測試結果
existsById
有時候我們并不想查找資料,而是隻想知道資料是否存在與資料庫表中,這個時候可以用existsById
boolean existsById(ID id);
測試代碼
@Test
public void testExistsById() {
System.out.println(scenicRepository.existsById(1));
}
測試結果
findAll(Sort sort)
有時候需要對查詢到的資料進行排序,此時可以傳遞一個排序函數。例如我們對secenic查詢結果按照price降序排列
@Test
public void testFindAllSort() {
Sort sort = Sort.by(Sort.Direction.DESC, "price");
List<Scenic> scenicList = scenicRepository.findAll(sort);
for(Scenic scenic: scenicList) {
System.out.println( scenic.getScenicName()+ ":" + scenic.getScenicId() + ":" + scenic.getPrice());
}
}
執行結果
findAll(Pageable pageable)
傳回一頁實體,根據Pageable參數提供的規則進行過濾
@Test
public void testFindAllPageable() {
// 分頁查詢
// PageRequest.of 的第一個參數表示第幾頁(注意:第一頁從序号0開始),第二個參數表示每頁的大小
// PageRequest of(int page, int size)
Pageable pageable = PageRequest.of(1, 2);
Page<Scenic> page = scenicRepository.findAll(pageable);
System.out.println("總頁數:" + page.getTotalPages());
System.out.println("總記錄數:" + page.getTotalElements());
System.out.println("目前第幾頁:" + (page.getNumber() + 1));
System.out.println("目前頁面的記錄數:" + page.getNumberOfElements());
}
測試結果
count
和SQL指令一樣,用于統計資料元素個數
編寫測試代碼
@Test
public void testCount() {
Long cnt = scenicRepository.count();
System.out.println("元素個數: "+cnt);
}
運作結果
5.4 資料删除
deleteById
根據id删除對應的資料
@Test
public void testDeleteById() {
System.out.println("*************************");
testFindAll();
scenicRepository.deleteById(1);
System.out.println("*************************");
testFindAll();
System.out.println("*************************");
}
删除前
删除後
deleteAll()
deleteAll可以删除對應資料庫表中的所有資料
@Test
public void testDeleteAll() {
scenicRepository.deleteAll();
}