SpringBoot中JPA的學習

歡迎!發表於2020-11-29

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();
  關於這裡JpaRepository的原理,可以參考這篇部落格的內容:  
  
  這裡寫一下自定義JPA對應的函式名:
 
KeywordSampleJPQL 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)

 

相關文章