SpringBoot中JPA的學習
準備環境和專案配置
寫一下學習JPA的過程,主要是結合之前SpringBoot + Vue的專案和網上的部落格學習一下。
首先,需要配置一下maven檔案,有這麼兩個依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
然後是application中的配置問題,JPA有這麼一些常見的引數:
spring.jpa.show-sql 配置在日誌中列印出執行的 SQL 語句資訊。
spring.jpa.hibernate.ddl-auto配置了實體類維護資料庫表結構的具體行為,update表示當實體類的屬性發生變化時,表結構跟著更新,也可以取值create,create表示啟動的時候刪除上一次生成的表,並根據實體類重新生成表,這個時候之前表中的資料就會被清空;還可以取值create-drop,這個表示啟動時根據實體類生成表,但是當sessionFactory關閉的時候表會被刪除;validate表示啟動時驗證實體類和資料表是否一致;none則什麼都不做。
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 。在 SrpingBoot 2.0 版本中,Hibernate 建立資料表的時候,預設的資料庫儲存引擎選擇的是 MyISAM (之前好像是 InnoDB,這點比較詭異)。這個引數是在建表的時候,將預設的儲存引擎切換為 InnoDB 用的。
spring.jackson.serialization.indent_output=true表示格式化輸出的json字串,方便檢視。
定義資料實體類
在這個專案裡,資料實體類的定義方式大同小異:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
@Entity
@Table(name = "book")
@JsonIgnoreProperties({"handler","hibernateLazyInitializer"})
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
int id;
//把 category 物件的 id 屬性作為 cid 進行了查詢
@ManyToOne
@JoinColumn(name="cid")
private Category category;
String cover;
String title;
String author;
String date;
String press;
String abs;
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
...
}
下面分別寫一下這些註釋的作用以及和其他部分關聯:
@Entity 是一個必選的註解,宣告這個類對應了一個資料庫表。
@Table(name = "book") 是一個可選的註解。宣告瞭資料庫實體對應的表資訊。包括表名稱、索引資訊等。這裡宣告這個實體類對應的表名是 book。如果沒有指定,則表名和實體的名稱保持一致。
可以看一下對應的在MySQL中的資料表:
其中8個屬性分別表示序號、封面(存放圖床的url或者本地的url地址)、標題、作者、出版日期、出版社、簡介、表示類別的外來鍵。
@JsonIgnoreProperties因為是做前後端分離,而前後端資料互動用的是 json 格式。那麼物件就會被轉換為 json 資料。而本專案使用 jpa 來做實體類的持久化,jpa 預設會使用 hibernate,在 jpa 工作過程中,就會創造代理類來繼承該類 ,並新增 handler 和 hibernateLazyInitializer 這兩個無須 json 化的屬性,所以這裡需要用 JsonIgnoreProperties 把這兩個屬性忽略掉。這裡我看好多博主都沒有寫,在後面會對這個註解多做一些測試相關的內容。
@Id表示該欄位是一個id
@GeneratedValue註解存在的意義主要就是為一個實體生成一個唯一標識的主鍵、@GeneratedValue提供了主鍵的生成策略。@GeneratedValue註解有兩個屬性,分別是strategy和generator。generator屬性的值是一個字串,預設為"",其宣告瞭主鍵生成器的名稱。strategy屬性提供四種值:1.AUTO主鍵由程式控制, 是預設選項 ,不設定就是這個;2.IDENTITY 主鍵由資料庫生成, 採用資料庫自增長, Oracle不支援這種方;3.SEQUENCE 通過資料庫的序列產生主鍵, MYSQL不支援;4.Table提供特定的資料庫產生主鍵, 該方式更有利於資料庫的移植。需要注意的是很多博主將MySQL建表時設定自增屬性,這裡採用預設值表示自增。但是想運用到neo4j上可能要注意。
@Column(length = 32) 用來宣告實體屬性的表欄位的定義。預設的實體每個屬性都對應了表的一個欄位。欄位的名稱預設和屬性名稱保持一致(並不一定相等)。欄位的型別根據實體屬性型別自動推斷。這裡主要是宣告瞭字元欄位的長度。如果不這麼宣告,則系統會採用 255 作為該欄位的長度。這裡的話個人感覺原來博主的定義不夠嚴謹,應該是:
@Column(length = 20)
String date;
@JoinColumn 註解的作用:用來指定與所操作實體或實體集合相關聯的資料庫表中的列欄位。由於 @OneToOne(一對一)、@OneToMany(一對多)、@ManyToOne(多對一)、@ManyToMany(多對多) 等註解只能確定實體之間幾對幾的關聯關係,它們並不能指定與實體相對應的資料庫表中的關聯欄位,因此,需要與 @JoinColumn 註解來配合使用。我們也可以不寫@JoinColumn,Hibernate會自動生成一張中間表來進行繫結,通常並不推薦讓Hibernate自動去自動生成中間表,而是使用@JoinTable註解來指定中間表:
然後就是各個屬性的get和set方法,注意下屬性前面一般要加上private限制,但是這個博主沒有加,不太規範這裡。
實現持久層服務
public interface BookDAO extends JpaRepository<Book,Integer> {
List<Book> findAllByCategory(Category category);
/*
這個 findAllByTitleLikeOrAuthorLike,翻譯過來就是“根據標題或作者進行模糊查詢”,
引數是兩個 String,分別對應標題或作者。
記住這個寫法,我想當然的以為是 findAllByTitleOrAuthorLike,只設定一個引數就行,結果瞎折騰了好久。
因為 DAO 裡是兩個引數,所以在 Service 裡把同一個引數寫了兩遍。
使用者在搜尋時無論輸入的是作者還是書名,都會對兩個欄位進行匹配。
*/
List<Book> findAllByTitleLikeOrAuthorLike(String keyword1, String keyword2);
}
宣告BookDAO介面,繼承JpaRepository
,預設支援簡單的 CRUD 操作,非常方便。
注意這裡JpaRepository的第一個引數是剛剛定義的實體類,第二個引數是那個主鍵的型別,很多部落格這裡是有問題的。
然後可以放心的使用以下函式運算元據庫:
<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();
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)</age> | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<age> 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) |