【轉載】JPA & Spring Data JPA學習與使用小記

原文地址:https://www.cnblogs.com/z-sm/p/9176636.html

博主注:個人比較推崇Spring全家桶,Spring Boot + Spring Data JPA + Spring thymeleaf 。一個完整的Spring Boot + MySQL的增刪改查例子,一篇博文就能講的清道的明。這個 Spring Data JPA 將原本就是抽象規範的 JPA 又封裝了一遍,更加的簡單易用,可見我的此文,作大概瞭解。

軟件開發無非以實現業務爲第一要務,滿足性能爲第二,何苦糾纏於繁雜的各種技術實現框架(Struts Hibernate MyBatis Freemark)中去呢,學習各種流行框架它就不費時間不費腦子麼?有這個時間和女票聊天看劇,甚至打遊戲,它就不香麼?人生苦短,過於執拗徒增白髮(徒掉黑髮)。

框架或中間件是用來幫助你的,而不是代替你去思考和工作的。


什麼是JPA

JPA(Java Persistence API)是Java標準中的一套ORM規範,藉助JPA技術可以通過註解或者XML描述【對象-關係表】之間的映射關係,並將實體對象持久化到數據庫中(即Object Model與Data Model間的映射)。

 

JPA之於ORM(持久層框架,如MyBatis、Hibernate等,用於管理應用層Object與數據庫Data之間的映射)正如JDBC之於數據庫驅動。

JDBC是Java語言定義的一套標準,規範了客戶端程序訪問關係數據庫(如MySQL、Oracle、Postgres、SQLServer等)的應用程序接口,接口的具體實現(即數據庫驅動)由各關係數據庫自己實現。

隨着業務系統的複雜,直接用JDBC訪問數據庫對開發者來說變得很繁瑣,代碼難以維護,爲解決此問題,ORM(Object Relation Mapping)框架出現了,如MyBatis、Hibernate等,百花齊放。

愛大一統的Java又出手了,Java針對ORM提出了JPA,JPA 本質上是一種 ORM 規範,不是 ORM 框架,只是定製了一些規範,提供了一些編程的 API 接口,具體實現由 ORM 廠商實現,如Hiernate、Eclipselink等都是JAP的具體實現,主要有:

 

另:關於Java Persistence規範的演進(OMG、EJB1.0 CMP、EJB2.0 CMP等)可參閱:https://en.wikibooks.org/wiki/Java_Persistence/What_is_JPA%3F

JPA was meant to unify the EJB 2 CMP, JDO, Hibernate, and TopLink APIs and products

It is a standard and part of EJB3 and Java EE.

 

JPA主要包括Statix Named Query、Criteria Query API兩部分(Query包含select、update、delete、insert等)。分爲靜態查詢和動態查詢:

靜態查詢在編譯期即確定查詢邏輯,爲Static Named Query,如getByName等。 

動態查詢運行時確定查詢邏輯,主要是Criteria API。Spring的Specification Query API對Criteria API進行了簡化封裝,此外Spring還提供了Example動態查詢(query by example (QBE))。

使用JPA Query時與SQL Query最大的區別在於前者是面向Object Model(即定義的Java Bean)而後者是面向Data Model(即數據庫表)的。

JPQL allows the queries to be defined in terms of the object model, instead of the data model. Since developers are programming in Java using the object model, this is normally more intuitive. This also allows for data abstraction and database schema and database platform independence. 

 

Spring data JPA與JPA的關係

如上面所述,JPA是Java標準中的一套規範。其爲我們提供了:

  1. ORM映射元數據:JPA支持通過XML和註解兩種元數據形式描述對象和表間的映射關係,並持久化到數據庫表中。如@Entity、@Table等
  2. JPA的Criteria API:提供API來操作實體對象,執行CRUD操作,框架會自動將之轉換爲對應的SQL,使開發者從繁瑣的JDBC、SQL中解放出來。
  3. JPQL查詢語言:提供面向Java對象而非面向數據庫自動的查詢語言,避免程序與SQL語句耦合

關係圖:

 

Spring Data JPA是Spring提供的一套簡化JPA開發的框架(Criteria API還是太複雜了),按照約定好的【方法命名規則】寫dao層接口,就可以在不寫接口實現的情況下,實現對數據庫的訪問和操作。同時提供了很多除了CRUD之外的功能,如分頁、排序、複雜查詢等等。

關係圖:

通過Repository來支持上述功能,默認提供的幾種Repository已經滿足了絕大多數需求:

JpaRepository( 爲Repository的子接口:JpaRepository -> PagingAndSortingRepository -> CrudRepository -> Repository)

QueryByExampleExecutor

JpaSpecificationExecutor

後兩者用於更復雜的查詢,如動態查詢、關聯查詢等;第一種用得最多,提供基於方法名(query method)的查詢,用戶可基於第一種繼承創建自己的子接口(只要是Repository的子接口即可),並聲明各種基於方法名的查詢方法。

題外話:PagingAndSortingRepository及其繼承的幾個接口實際上不僅可用於Spring Data JPA,還可用於Spring Data MongoDB等,可見可複用性很好。

 

Spring Data JPA 其實並不依賴於 Spring 框架。

 

 

JPA註解

註解位置

通過JPA定義的Object至少需要@Entity、@Id註解,示例:

 View Code

這些註解的位置可以有兩種(Access Type):Field(在變量上)上、Property(在變量的get方法)上。一個Ojbect內的JPA註解要麼在Field上要麼在Property上(當然可以在類上),不能兩者同時有。詳情可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Mapping#Access_Type

  1. Field:will be accessed directly to store and load the value from the database。It avoids any unwanted side-effect code that may occur in the application get/set methods.
  2. Property:get and set methods will be used to store and load the value from the database. It allows the application to perform conversion of the database value when storing it in the object.

JPA 2.0開始允許通過@Acdess註解來指定默認access type並通過該註解來指定例外acess type,從而達到混合使用的效果。 

註解

**@Entity**

@Entity 標註用於實體類聲明語句之前,指出該Java 類爲實體類,將映射到指定的關係數據庫表。(類似的,使用@Document可以映射到mongodb)

應用了此註解後,將會自動將類名映射作爲數據庫表名、將類內的字段名映射爲數據庫表的列名。映射策略默認是按駝峯命名法拆分將類名或字段名拆分成多部分,然後以下劃線連接,如StudentEntity -> student_entity、studentName -> student_name。若不按默認映射,則可通過@Table、@Column指定,見下面。

**@Table**

當實體類與其映射的數據庫表名不同名時需要使用 @Table 標註說明,該標註與 @Entity 標註並列使用

  • schema屬性:指定數據庫名
  • name屬性:指定表名,不知道時表名爲類名

 

**@id**

@Id 標註用於聲明一個實體類的屬性映射爲數據庫的一個主鍵列

@Id標註也可置於屬性的getter方法之前。以下註解也一樣可以標註於getter方法前。

若同時指定了下面的@GeneratedValue則存儲時會自動生成主鍵值,否則在存入前用戶需要手動爲實體賦一個主鍵值。主鍵值類型可能是:

    • Primitive types: boolean, byte, short, char, int, long, float, double.
    • Equivalent wrapper classes from package java.lang:
      Byte, Short, Character, Integer, Long, Float, Double.
    • java.math.BigInteger, java.math.BigDecimal.
    • java.lang.String.
    • java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp.
    • Any enum type.
    • Reference to an entity object.
    • composite of several keys above

 

指定聯合主鍵,有@IdClass、@EmbeddedId兩種方法,可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Composite_Primary_Keys

**@IdClass**

修飾在實體類上,指定聯合主鍵。如:@IdClass(StudentExperimentEntityPK.class),主鍵類StudentExperimentEntityPK需要滿足:

  1. 實現Serializable接口
  2. 有默認的public無參數的構造方法
  3. 重寫equals和hashCode方法。equals方法用於判斷兩個對象是否相同,EntityManger通過find方法來查找Entity時,是根據equals的返回值來判斷的。hashCode方法返回當前對象的哈希碼

示例:

 CustomerCourseEntityPK

 CustomerCourseEntity

 

**@EmbeddedId**

功能與@IdClass一樣用於指定聯合主鍵。不同的在於其是修飾實體內的一個主鍵類變量,且主鍵類應該被@Embeddable修飾。

此外在主鍵類內指定的字段在實體類內可以不再指定,若再指定則需爲@Column加上insertable = false, updatable = false屬性

**@GeneratedValue**

@GeneratedValue 用於標註主鍵的生成策略,通過 strategy 屬性指定。默認情況下,JPA 自動選擇一個最適合底層數據庫的主鍵生成策略:SqlServer 對應 identity,MySQL 對應 auto increment

  • IDENTITY:採用數據庫 ID自增長的方式來自增主鍵字段,Oracle 不支持這種方式
  • AUTO: JPA自動選擇合適的策略,是默認選項
  • TABLE:通過表產生主鍵,框架藉由表模擬序列產生主鍵,使用該策略可以使應用更易於數據庫移植。
  • SEQUENCE:通過序列產生主鍵,通過 @SequenceGenerator 註解指定序列名,MySql 不支持這種方式

更多詳情可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Sequence_Strategies

 

**@Basic**

表示一個簡單的屬性到數據表的字段的映射,對於沒有任何標註的 getXxx() 方法,默認爲 @Basic

fetch 表示屬性的讀取策略,有 EAGER 和 LAZY 兩種,分別爲立即加載和延遲加載

optional 表示該屬性是否允許爲 null,默認爲 true

**@Column**

此註解不是必須的,無此字段也會將字段映射到表列。當實體的屬性與其映射的數據庫表的列不同名時需要使用 @Column 標註說明,其有屬性 name、unique、nullable、length 等。

類的字段名在數據庫中對應的字段名可以通過此註解的name屬性指定,不指定則默認爲將屬性名按駝峯命名法拆分並以下劃線連接,如createTime對應create_time。注意:即使name的值中包含大寫字母,對應到db後也會轉成小寫,如@Column(name="create_Time")在數據庫中字段名仍爲create_time。

可通過SpringBoot配置參數 spring.jpa.hibernate.naming.physical-strategy 配置上述對應策略,如指定name值是什麼數據庫中就對應什麼名字的列名。默認值爲: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy 

**@Transient**

表示該屬性並非一個到數據庫表的字段的映射,ORM 框架將忽略該屬性

如果一個屬性並非數據庫表的字段映射,就務必將其標識爲 @Transient,否則ORM 框架默認爲其註解 @Basic,例如工具方法不需要映射

**@Temporal**

在 JavaAPI 中沒有定義 Date 類型的精度,而在數據庫中表示 Date 類型的數據類型有 Date,Time,TimeStamp 三種精度(日期,時間,兩者兼具),進行屬性映射的時候可以使用 @Temporal 註解調整精度。目前此註解只能用於修飾java.util.Date、java.util.Calendar類型的變量,TemporalType取DATE、TIME、TIMESTAMP時在MySQL中分別對應的DATE、TIME、DATETIME類型。示例:

複製代碼


 
    @Temporal(TemporalType.TIMESTAMP)
    @CreationTimestamp //org.hibernate.annotations.CreationTimestamp,用於在JPA執行insert操作時自動更新該字段值
    @Column(name = "create_time", updatable=false )//爲防止手動set,可設false以免該字段被更新
    private Date createTime;
    @Temporal(TemporalType.TIMESTAMP)
    @UpdateTimestamp //org.hibernate.annotations.UpdateTimestamp,用於在JPA執行update操作時自動更新該字段值
    @Column(name = "update_time")
    private Date updateTime;

複製代碼

@CreationTimestamp、@UpdateTimestamp是Hibernate的註解,SpringData JPA也提供了類似功能(推薦用此):@CreatedDate、@LastModifiedDate、@CreatedBy、@LastModifiedBy,可參閱https://blog.csdn.net/tianyaleixiaowu/article/details/77931903

**@MappedSuperClass**

用來修飾一個類,類中聲明瞭各Entity共有的字段,也即數據庫中多表中共有的字段,如create_time、update_time、id等。

標註爲@MappedSuperclass的類將不是一個完整的實體類,他將不會映射到數據庫表,但是他的屬性都將映射到其子類的數據庫字段中。

標註爲@MappedSuperclass的類不能再標註@Entity或@Table註解,也無需實現序列化接口。

允許多級繼承。

 

 **@Inheritance**

用於表結構複用。指定被該註解修飾的類被子類繼承後子類和父類的表結構的關係。通過strategy屬性指定關係,有三種策略:

  1. SINGLE_TABLE:適用於共同字段多獨有字段少的關聯關係定義。子類和父類對應同一個表且所有字段在一個表中,還會自動生成(也可通過@DiscriminatorColumn指定)一個字段 varchar 'dtype' 用來表示一條數據是屬於哪個實體的。爲默認值(未使用@Inheritance或使用了但沒指定strategy屬性時默認採用此策略)。
  2. JOINED:子類和父類對應不同表,父類屬性對應的列(除了主鍵)不會且無法再出現在子表中。子表自動產生與父表主鍵對應的外鍵與父表關聯。同樣地也可通過@DiscriminatorColumn爲父類指定一個字段用於標識一條記錄屬於哪個子類。
  3. TABLE_PER_CLASS:子類和父類對應不同表且各類自己的所有字段(包括繼承的)分別都出現在各自的表中;表間沒有任何外鍵關聯。此策略最終效果與@MappedSuperClass等同。

更多詳情可參閱:https://www.ibm.com/developerworks/cn/java/j-lo-hibernatejpa/index.html

@Inheritance與@MappedSuperclass的區別:後者子類與父類沒有外鍵關係、後者不會對應一個表等、前者適用於表關聯後者適用於定義公共字段。另:兩者是可以混合使用的。詳見:https://stackoverflow.com/questions/9667703/jpa-implementing-model-hierarchy-mappedsuperclass-vs-inheritance

 

總而言之,@Inheritance、@MappedSuperClass可用於定義Inheritance關係。詳情可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Inheritance。這些方式的一個缺點是子類中無法覆蓋從父類繼承的字段的定義(如父類中name是not null的但子類中允許爲null)。

除了 @Inheritance、@MappedSuperClass外,還有一種Inheritance方法(此法可解決上述不足):先定義一個Java POJO(乾淨的POJO,沒有任何對該類使用任何的ORM註解),然後不同子類繼承該父類並分別在不同子類中進行ORM定義即可。此法下不同子類擁有父類的公共字段且該字段在不同子類中對應的數據庫列定義可不同。

實踐示例:

 View Code

 

JPA對象屬性與數據庫列的映射

(attribute map between object model and data model)

基本類型(String、Integer等)、時間、枚舉、複雜對象如何自動映射到數據庫列,詳情可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Basic_Attributes

以下是基本類型的映射:

對於非基本類型的屬性,其映射:

法1:

By default in JPA any Serializable attribute that is not a relationship or a basic type (String, Number, temporal, primitive), will be serialized to a BLOB field.

法2:JPA 2.1起可通過 @Convert 指定屬性與數據庫列間的映射邏輯,其可將任意對象映射到數據庫的一個列(詳見後文)。在這之前沒有@Convert,可以通過get、set方法實現類似效果,示例:

 View Code

 

 

 

Spring Data JPA使用小記

指定對象與數據庫字段映射時註解的位置

如@Id、@Column等註解指定Entity的字段與數據庫字段對應關係時,註解的位置可以在Field(屬性)或Property(屬性的get方法上),兩者統一用其中一種,不能兩者均有。推薦用前者。

詳情可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Mapping

 

JPA命名查詢的原理

基本用法:通過方法名來指定查詢邏輯,而不需要自己實現查詢的SQL邏輯,示例:List<Student> getByName(String name)

方法名解析原理:對方法名中除了保留字(findBy、top、within等)外的部分以and爲分隔符提取出條件單詞,然後解析條件獲取各個單詞並看是否和Entity中的屬性對應(不區分大小寫進行比較)。get/find 與 by之間的會被忽略,所以getNameById與getById是等價的,會根據id查出整個Entity而不會只查name字段。(指定部分字段的查詢見後麪條目)

查詢條件解析原理:假設School和Student是一對多關係,Student中有個所屬的School school字段、School有個String addressCode屬性,以如下查詢爲例: 

Studetn getByNameAndSchoolAddressCode(String studentName, String addressCode)(先說結果:JPA會自動生成條件studentName和關聯條件student.school.addressCode進行查詢)

  1. 由And分割得到studentName、SchoolAddressCode;
  2. 分別看Student中是否有上述兩屬性,顯然前者有後者沒有,則後者需要進一步解析(見下步)
  3. JPA按駝峯命名格式從後往前嘗試分解SchoolAddressCode:先得到 [SchoolAdress、Code],由於Student沒有SchoolAddress屬性故繼續嘗試分解,得到[School、AdressCode];由於Student有School屬性且School有addressCode屬性故滿足,最終得到條件student.school.addressCode。注:但若Student中有個SchoolAdress schoolAddress屬性但schoolAddress中沒有code屬性,則會因找不到student.schoolAdress.code而報錯,所以可通過下劃線顯示指定分割關係,即寫成: getByNameAndSchool_AddressCode

查詢字段解析原理:默認會查出Entity的所有字段且返回類型爲該Entity類型,有兩種情況可查詢部分字段(除此外都會查出所有字段):

1、通過@Query寫的自定義查詢邏輯中只查部分字段。這種不屬於直接通過方法名指定查詢,這裏先不討論(見後面查詢指定部分字段的條目)。

2:返回類型爲自定義接口或該接口列表,接口中僅包含部分字段的get方法,此時會根據接口方法名查詢部分字段。示例:

複製代碼

//CourseRepository.java

    List<MyCustomColumns> findCustomColumnsByGroupId(String groupId);//find和By間的部分在解析時會被忽略。爲了見名知意,最好加上字段信息,如findVersionByGroupId
    public interface MyCustomColumns {//JPA生成查詢語句時只會查下面get方法中指定的字段名。需要確保Entity中有該字段名否則會報錯
        public String getId();

        public String getVersion();

        public String getGroupId();
    }

複製代碼

 

 

在查詢時,通常需要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大於某個值、在某個範圍等等),Spring Data JPA 爲此提供了一些表達條件查詢的關鍵字,大致如下:

複製代碼

And --- 等價於 SQL 中的 and 關鍵字,比如 findByUsernameAndPassword(String user, Striang pwd);
Or --- 等價於 SQL 中的 or 關鍵字,比如 findByUsernameOrAddress(String user, String addr);
Between --- 等價於 SQL 中的 between 關鍵字,比如 findBySalaryBetween(int max, int min);
LessThan --- 等價於 SQL 中的 "<",比如 findBySalaryLessThan(int max);
GreaterThan --- 等價於 SQL 中的">",比如 findBySalaryGreaterThan(int min);
IsNull --- 等價於 SQL 中的 "is null",比如 findByUsernameIsNull();
IsNotNull --- 等價於 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
NotNull --- 與 IsNotNull 等價;
Like --- 等價於 SQL 中的 "like",比如 findByUsernameLike(String user);
NotLike --- 等價於 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
OrderBy --- 等價於 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
Not --- 等價於 SQL 中的 "! =",比如 findByUsernameNot(String user);
In --- 等價於 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;
NotIn --- 等價於 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的參數可以是 Collection 類型,也可以是數組或者不定長參數;
Containing --- 包含指定字符串
StargingWith --- 以指定字符串開頭
EndingWith --- 以指定字符串結尾

複製代碼

 

更多見 官方文檔Spring Data JPA


JPA 集合類型查詢參數

 List<StudentEntity> getByIdInAndSchoolId(Collection<String> studentIdList, String schoolId);  ,關鍵在於 In 關鍵字。參數用Collection類型,當然也可以用List、Set等,但用Collection更通用,因爲此時實際調用可以傳List、Set等實參。

 

 

Repository nativeQuery返回Entity

使用nativeQuery時SQL語句查詢的字段名若沒as則是數據庫中的字段名,如school_id,而API返回值通常是schoolId,可以在SQL裏通過 school_id as schoolId取別名返回。然而若查詢很多個字段值則得一個個通過as取別名,很麻煩,可以直接將返回值指定爲數據庫表對應的Entity,不過此法要求查詢的是所有字段名,如:

    @Query(value = " select t.* from teacher t where t.school_id=?1  "// 以下爲搜索字段
            + "and (?4 is NULL or name like %?4% or job_number like %?4% or bz like %?4% or phone like %?4% or email like %?4%)  "
            + " order by job_number limit ?2,  x?3 ", nativeQuery = true)
    List<TeacherEntity> myGetBySchoolIdOrderByJobNumber(String schoolId, int startIndex, Integer size,
            String searchNameOrJobnumOrBzOrPhoneOrEmai);// nativeQuery返回類型可以聲明爲Entity,會自動進行匹配,要求查回與Entitydb中字段對應的所有db中的字段

 

延遲加載與立即加載(FetchType)

通常可以在@OneToMany中用LAZY、在@ManyToOne/Many中用EAGER,但不絕對,看具體需要。

FetchType.LAZY:延遲加載,在查詢實體A時,不查詢出關聯實體B,在調用getxxx方法時,才加載關聯實體,但是注意,查詢實體A時和getxxx必須在同一個Transaction中,不然會報錯:no session。即會表現爲兩次單獨的SQL查詢(非聯表查詢)

FetchType.EAGER:立即加載,在查詢實體A時,也查詢出關聯的實體B。即會表現爲一次查詢且是聯表查詢

默認情況下,@OneToOne、@ManyToOne是LAZY,@OneToMany、@ManyToMany是EAGER。

有兩個地方用到延遲加載:relationship(@OneToMany等)、attribute(@Basic)。後者一般少用,除非非常確定字段很少訪問到。

 

 JPA Join查詢

只有有在Entity內定義的關聯實體才能進行關聯查詢,示例:

 @Query("select cd, d from CourseDeveloperEntity cd join cd.developer d where d.nickName='stdeveloper'") 

該句實際上等價於:

 @Query("select cd, cd.developer from CourseDeveloperEntity cd where cd.developer.nickName='stdeveloper'") 

若將一個對象的關聯對象指定爲延遲加載LAZY,則每次通過該對象訪問關聯對象時(如courseDeveloper.developer)都會執行一次SQL來查出被關聯對象,顯然如果被關聯對象訪問頻繁則此時性能差。解決:法1是改爲EAGER加載;法2是使用join fetch查詢,其會立即查出被關聯對象。示例:

 @Query("select cd from CourseDeveloperEntity cd join fetch cd.developer where cd.id='80f78778-7b39-499c-a1b6-a906438452a9'") 

 Join Fetch背後是使用inner join,可以顯示指定用其他關聯方式如 left join fetch 

Join Fetch的缺點之一在於有可能導致“Duplicate Data and Huge Joins”,如多個實驗關聯同一課程,則查詢兩個實驗時都關聯查出所屬課程,後者重複查詢。

更多(join、join fetch等)可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining,_querying_on_a_OneToMany_relationship

 

 

 

JPA分頁或排序

靜態方式:直接在方法體現(如 getByNameOrderById),也可以在JPQL的@Query的邏輯中加上order by(此時字段名是Entity中的字段名)

動態方式:可以在Repository的方法的最後加一個Sort 或者 Pageable 類型的參數,便可動態生成排序或分頁語句(編譯後會自動在語句後加order by或limit語句)。

Repository中的一個方法myGetByCourseIdAndStudentId:

    @Query("select se from StudentExperimentEntity se where se.studentId= ?2 and se.experimentId in ( select e.id from ExperimentEntity e where e.courseId= ?1 ) ")
    List<StudentExperimentEntity> myGetByCourseIdAndStudentId(String courseId, String studentId, Pageable pageable);//沒有寫上述@Query語句也可以加Pageable。雖然實際傳值時傳PageRequest對象,但若這裏生命爲PageRequest則不會分頁,總是返回所有數據,why?

調用:

studentExperimentRepository.myGetByCourseIdAndStudentId(courseId, studentId, PageRequest.of(0, count, new Sort(Sort.Direction.DESC, "lastopertime")));

編譯後會在myGetByCourseIdAndStudentId所寫SQL後自動加上  order by studentexp0_.lastopertime desc limit ? 

注:上述用法也支持nativeQuery,示例:

    @Query(value = "select d.*, u.username from developer d inner join user u on d.id=u.id "
            + " where (?1 is null or d.nick_name like %?1% ) ", nativeQuery = true)
    List<DeveloperEntity> myGetByNicknameOrPhoneOrEmailOrBz(String searchNicknameOrPhoneOrEmailOrBz, Pageable pageable);

 

 

Repository中更新或創建並返回該Entity

如 UserEntity u=userRepository.save(userEntity) ,其中UserEntity包含成員變量private SchoolEntity schoolEntity。Repository的save方法會返回被save的entity,但若是第一次保存該entity(即新建一條記錄)時u.schoolEntity的值會爲null,解決:用saveAndFlush

 

查詢Entity中的部分字段

1、通過@Query註解

對於只返回一個字段的查詢:

@Query(value = "select languageType from CourseTranslationEntity where courseId=?1") Set<Locale> myGetLanguageTypeByCourseId(String courseId);

對於返回多個字段的查詢:

對於nativeQuery,直接select部分字段即可,結果默認會自動包裝爲Map。爲了便於理解可以直接將結果聲明爲Map。示例:

 View Code

其可以達到目的,但缺點是sql裏用的直接是數據庫字段名,導致耦合大,數據庫字段名一變,所有相關sql都得相應改變。

對於非nativeQuery:(sql裏的字段名是entity的字段名,數據庫字段名改動只要改變entity中對應屬性的column name即可,解決上述耦合大的問題)

當Repository返回類型爲XXEntity或List<XXEntity>時通常默認包含所有字段,若要去掉某些字段,可以去掉XXEntity中該字段的get方法。此法本質上還是查出來了只是spring在返回給調用者時去掉了。治標不治本。

也可以自定義一個bean,然後在Repository的sql中new該bean。此很死板,要求new時寫bean的全限定名,比較麻煩。

更好的辦法是與nativeQuery時類似直接在sql裏select部分字段,不過非nativeQuery默認會將結果包裝爲List而不是Map,故不同的是:這裏需要在sql裏new map,此'map'非jdk裏'Map';需要爲字段名取別名,否則返回的Map裏key爲數值0、1、2... 。示例:

//爲'map'不是'Map'    
@Query("select new map(g.name as name, count(s.id) as stuCount) from GradeEntity g, StudentEntity s where g.name=s.grade and g.schoolId=?1 group by g.id")
    List<Map<String, Object>> myGetBySchoolId(String schoolId);

這裏方法簽名中返回值指定爲Map不太好,更好的是一個類:我們可以自定義一個接口,然後將返回類型聲明爲接口列表即可。接口get方法名中的字段與上面as後的字段名對應。(當然,也可以不用接口而是用自定義包含部分字段的類,此時new時需要用類的全限定名)示例: 

複製代碼

    // JPQL查詢部分字段時,默認返回List類型,可通過new map指定返回map,此時map key默認爲順序0 1 2 等,可通過as指定key名
    @Query("select new map(id as idx, languageType as languageType) from CourseEntity where id in ?1 ")
    List<IdAndLanguageType> myGetLanguagesTypeByCourseIdIn(Collection<String> courseIdCollection);

    // 以下爲爲了查詢Entity部分字段而定義的返回類型
    public interface IdAndLanguageType {
        public String getIdx(); //get方法中的字段名須與上面通過as取的key別名對應

        public String getLanguageType();
    }

複製代碼

@Query用於查詢時返回值總結 :

若是查詢多個字段則返回時默認將這些字段包裝爲Object[]、若返回有多條記錄則包裝成List<Object[]>,若只查詢一個字段則不用數組而是直接用該字段。示例:

 

select中僅有一個字段時(可以是表中的一個列名、Entity中的一個字段名、或一個Entity對象名,如language_type、languageType、courseEntity),方法返回類型須聲明爲該字段的類型或類型列表(如String、CourseEntity、List<String>、List<CourseEntity>)

select中有至少兩個字段時,默認會將每條記錄的幾個字段結果包裝爲數組。可以手動指定包裝爲map,此時map的key爲字段序號,故最通過as指定key爲字段名。可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Querying#Query_Results

 

2、不通過@Query註解,直接通過方法名簽名指定部分字段查詢

實際上,在前面介紹JPA查詢時介紹了直接通過方法簽名實現只查詢部分字段的方法。上面的查詢不用@Query註解的等價寫法如下:

 List<IdAndLanguageType> getLanguagesTypeByCourseIdIn(Collection<String> courseIdCollection); 

其內部原理是根據接口的get方法解析出要查詢的字段,可以理解爲JPA內部將之轉成了用@Query註解的查詢,內部生成的大概過程如下:

獲得要查詢的字段名:idx、languageType

生成@Query查詢: @Query(" select new map(idx as idx, languageType as languageType) from CourseEntity where id in ?1 ") 。注意這裏第一個字段名爲idx而不是id,因爲是根據接口方法產生的,可見:如果使用者不用@Query則需要確保接口中get方法名中的字段與Entity中的一致,而如果使用@Query則不需要,因爲可以自己通過as取別名

 

 

 

 

JPA的update、delete

(需要加@Transactional、@Modefying)

@Transactional //也可以放在service方法上
@Modifying
@Query("delete from EngineerServices es where es.engineerId = ?1")//update與此類似
int deleteByEgId(String engineerId);

對於delete操作,可以與query的寫法類似,直接通過方法名聲明(注:update不支持這樣寫):

@Transactional
@Modifying
int deleteByEgId(String engineerId);

甚至更直接寫爲: int deleteByEgId(String engineerId);  ,但此時記得需要在上層調用者部分添加@Transactional

 

注:JPA中非nativeQuery的刪除操作(如deleteByName)內部實際上是先分析出方法名中的條件、接着按該條件查詢出Entity,然後根據這些Entity的id執行SQL刪除操作。

也正因爲這樣,軟刪除功能中指定 @SQLDelete("update student set is_delete='Y' where id=? ") 即可對所有非nativeQuery起作用

 

JPA的update操作:

法1:Repository中@Modifying、@Query組合

法2:通過Repository的save方法。

方式1:JPA會判根據Entity的主鍵判斷該執行insert還是update,若沒指定主鍵或數據庫中不存在該主鍵的記錄則執行update。此法在通過在Entity指定@Where實現了軟刪除的情況下行不通,因爲JPA通過內部執行查詢操作判斷是否是update時查詢操作也被加上了@Where,從而查不到數據而被,進而最終執行insert,此時顯然報主鍵衝突。

方式2:更好的做法是先通過Repository查出來,修改後再執行save,這樣能確保爲update操作

可參閱:https://stackoverflow.com/questions/11881479/how-do-i-update-an-entity-using-spring-data-jpa

 

 

JPA的count

Integer countByName(String name);

 

外鍵關聯

 通過@ManyToOne、@OneToMany、@OneToOne、@JoinColumn等實現,用法及區別見:https://www.cnblogs.com/chiangchou/p/mappedBy.html

  • (1)@JoinColumn用來指定外鍵,其name屬性指定該註解所在Entity對應的表的一個列名
  • (2)@ManyToOne等用來指定對應關係是多對一等數量對應關係

通過(2)指定數量對應關係時,須在多的一方標註(@ManyToOne),一的一方注不注均可。(以下以School、Student爲例,爲一對多關係)

  1. 若只用(2)沒用(1)則在生成表時會自動生成一張關聯表來關聯School、Student,表中包含School、Studeng的id
  2. 若在用了(2)的基礎上用了(1)則不會自動生成第三張表,而是會在多的一方生成一個外鍵列。列名默認爲  ${被引用的表名}_id (可以通過@JoinColumn的name屬性指定列名),引用了目標表的id。
  3. 上法的缺點是在insert多的一方後會再執行一次update操作來設置外鍵的值(即使在insert時已經指定了),避免額外update的方法:在一的一方不使用@JoinColumn,而是改爲指定@OneToMany的mappedBy屬性。(1)和(2)的mappedBy屬性不能同時存在,會報錯。

總結:通常用法示例如下

複製代碼

//StudentEntity

    @Column(name = "school_id")
    private String schoolId;
    //get set ...

    private SchoolEntity schoolBySchoolId;

    // 多個字段對應數據庫同一字段時會報錯,通過添加insertable = false, updatable = false即可
    @ManyToOne
    @JoinColumn(name = "school_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)//指定當前Entity的school_id引用SchoolEntity的id列
    public SchoolEntity getSchoolBySchoolId() {
        return schoolBySchoolId;
    }

    public void setSchoolBySchoolId(SchoolEntity schoolBySchoolId) {
        this.schoolBySchoolId = schoolBySchoolId;
    }

複製代碼

 

 

 

級聯操作(CASCADE)

Use of the cascade annotation element may be used to propagate the effect of an operation to associated entities. The cascade functionality is most typically used in parent-child relationships.

用於有依賴關係的實體間(@OneToMany、@ManyToOne、@OneToOne等)的級聯操作:當對一個實體進行某種操作時,若該實體加了與該操作相關的級聯標記,則該操作會傳播到與該實體關聯的實體(即對被級聯標記的實體施加某種與級聯標記對應的操作時,與該實體相關聯的其他實體也會被施加該操作)。包括:

CascadeType.PERSIST:持久化,即保存

CascadeType.REMOVE:刪除當前實體時,關聯實體也將被刪除

CascadeType.MERGE:更新或查詢

CascadeType.REFRESH:級聯刷新,即在保存前先更新別人的修改:如Order、Item被用戶A、B同時讀出做修改且B的先保存了,在A保存時會先更新Order、Item的信息再保存。

CascadeType.DETACH:級聯脫離,如果你要刪除一個實體,但是它有外鍵無法刪除,你就需要這個級聯權限了。它會撤銷所有相關的外鍵關聯。

CascadeType.ALL:上述所有

注:

級聯應該標記在One的一方。如對於 @OneToMany的Person 和 @ManyToOne的Phone,若將CascadeType.REMOVE標記在Phone則刪除Phone也會刪除Person,顯然是錯的。

慎用CascadeType.ALL,應該根據業務需求選擇所需的級聯關係,否則可能釀成大禍。

示例可參閱:http://westerly-lzh.github.io/cn/2014/12/JPA-CascadeType-Explaining/

 

 

like查詢

對於單字段的可以直接在方法名加Containing

    @Query("select s from SchoolEntity s where s.customerId=?1 "// 以下爲搜索條件
            + " and (?2 is null or s.name like %?2% or s.bz like %?2%  ) ")
    List<SchoolEntity> getByCustomerId(String customerId, String searchSchoolnameOrBz, Pageable pageable);

 

 

Entity中將任意對象映射爲一個數據庫字段

藉助JPA converter to map your Entity to the database.

在要被映射的字段上加上註解: @Convert(converter = JpaConverterJson.class) 

實現JpaConverterJson:

複製代碼

public class JpaConverterJson implements AttributeConverter<Object, String> {//or specialize the Object as your Column type

  private final static ObjectMapper objectMapper = new ObjectMapper();

  @Override
  public String convertToDatabaseColumn(Object meta) {
    try {
      return objectMapper.writeValueAsString(meta);
    } catch (JsonProcessingException ex) {
      return null;
      // or throw an error
    }
  }

  @Override
  public Object convertToEntityAttribute(String dbData) {
    try {
      return objectMapper.readValue(dbData, Object.class);
    } catch (IOException ex) {
      // logger.error("Unexpected IOEx decoding json from database: " + dbData);
      return null;
    }
  }

}

複製代碼

需要注意的是,若Entity字段是一個 JavaBean 或 JavaBean 列表(如 TimeSlice 或 List<TimeSlice> ),則反序列化時相應地會反序列化成 LinkedHashMap 或 List<LinkedHashMap>,故強轉成TimeSlice或List<TimeSlice>雖然編譯期不會報錯但運行時就出現類型轉換錯誤。故需要進一步轉換成JavaBean,示例:

 View Code

 

參考資料:https://stackoverflow.com/questions/25738569/jpa-map-json-column-to-java-object

 

將任意非基本數據類型(如java bean、list等)對應到數據庫字段

本質上就是將數據序列化成基本數據類型如String。如要把List<String> gradeIdList對應到數據庫中的字符串類型的courseSchedule字段。

法1:可以在業務層寫代碼將gradeIdList序列化成String: String res=objectMapper.writeValueAsString(gradeIdList);// 藉助objectMapper.writeValueAsString(data); ,之後保存即可。從數據庫中讀取時: List<String> gradeIdList=objectMapper.readValue(dbData, List<String>.class); 。此法可以解決問題,但每個字段都得自己手動寫此過程。

法2:實現一個AttributeConverter,並應用於Entity字段。此法相當於指定了AttributeConverter後讓框架去自動做轉換

 View Code

 

 

枚舉示例

複製代碼

    @Column(name = "sex")
    @Enumerated(EnumType.ORDINAL)//持久化爲0,1
    private Sex sex;

    @Column(name = "type")
    @Enumerated(EnumType.STRING)//持久化爲字符串
    private Role role;

複製代碼

 

示例:

 View Code

 

 

JPA Repository的save(xxx)方法

通過show-sql=true參數打印sql語句,可以發現其內部先是按被保存的Entity的主鍵查出該Entity,若存在則更新並保存、否則插入。

save方法以Entity爲參數用於保存。其效果可能是插入新數據或修改已有數據,在執行時會根據Entity參數自動判斷:如果該Entity參數中的主鍵值(可能是聯合主鍵)在DB中已存在則是更新、否則爲插入。因此,在某些場景下會有問題。

舉個例子,我們有這麼個場景:Entity爲CourseDeveloperEntity,該Entity中有單獨的id字段作爲主鍵、且有course_id、developer_id聯合唯一索引、還有個is_delete用於邏輯刪除。

在該場景下,若我們刪除了一個CourseDeveloperEntity,則該Entity被刪除了(is_delete標記爲true以邏輯刪除,數據庫中實際上還在,但上層業務查不到),因此若之後再插入(course_id、developer_id)一樣的Entity但沒指定id一樣,則會因違背聯合唯一索引而duplicate key的錯。相關代碼:

複製代碼

        // 保存
        CourseDeveloperEntity courseDeveloperEntity = courseDeveloperRepository.getByCourseIdAndDeveloperId(courseId, developerEntity.getId());//查不到被邏輯刪除的記錄
        if (null == courseDeveloperEntity) {
            courseDeveloperEntity = new CourseDeveloperEntity();
        }
        courseDeveloperEntity.setCourseId(courseId);
        courseDeveloperEntity.setDeveloperId(developerEntity.getId());
        courseDeveloperEntity.setRole(developerRoleInTheCourse);
        return courseDeveloperRepository.save(courseDeveloperEntity);//由於主鍵自動新生成故courseDeveloperEntity被當成新的,故是執行insert而不是update,從而報錯

複製代碼

 

JPA Repository的刪除操作

方法名包含條件的刪除操作,如 Integer deleteByNameAndSId(String name, String uuid); ,其執行時與save類似,也是先根據條件查出目標Entity再執行刪除操作。對於 void delete(T entity); 則直接根據Entity的主鍵操作而不用先查。

 

 

邏輯刪除

藉助org.hibernate.annotations(所以不是JPA的標準)旳 @Where、@SQLDelete、@SQLDeleteALL 這三個註解來實現。

1、定義一個字段用於標識記錄是否被邏輯刪除。這裏通過JPA的@MappedSuperclass定義各Entity共有的字段(該註解修飾的Entity不會對應數據庫表,但其內定義的字段會被繼承該Entity的子Entity對應到數據庫字段),包含is_delete:

 View Code

2、通過@Where、@SQLDelete、@SQLDeleteALL 三個註解修飾對應數據庫表的Entity來實現邏輯刪除: 

 View Code

需要注意的是:

  • @Where會自動在查詢語句後拼接@Where中指定的條件;該註解對所有的非nativeQuery的查詢其作用,如getByName、count、自己寫查詢語句但非nativeQuery的myGetByName等。
  • @SQLDelete會自動將刪除語句替換爲@SQLDelete中指定的sql操作;該註解對所有非nativeQuery的刪除操作其作用,如delete(StudenEntity entity)、deleteBySId、deleteByName等,但由於指定的sql操作中條件不得不寫死,所以要按期望起作用的話@SQLDelete中的sql操作應以Entity的主鍵爲條件,且刪除語句按上述前兩者寫法寫(對於delete(StudenEntity entity)會自動取entity的主鍵給sid),而不能用第三種(會將name參數值傳給sid)
  • 通過JPQL的方法名指定刪除操作(如 Integer deleteByName(String name))時背後是先根據條件查出Entity然後根據Entity的主鍵刪除該Entity。所以通過@SQLDelete、@SQLDeleteALL實現邏輯刪除時,由於其語句是寫死的,故:
    • @SQLDelete、@SQLDeleteALL同時存在時會按後者來執行軟刪除邏輯
    • @SQLDeleteALL並不會批量執行軟刪除邏輯(因爲一來不知具體有幾個數據,二來in中只指定了一個元素)而是一個個刪,即有多條待刪除時會一條條執行軟刪除邏輯,每條語句中in中只有一個元素。故其效果與@SQLDelete的一樣,然而in操作效率比=低,故綜上,最好用前者。

 

指定時間或日期字段的返回格式

通過jackson的@JsonFormat註解指定,示例:(也可以注在get方法上)

    @Basic
    @Column(name = "customer_expiretime")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = CommonUtil.DATETIME_PATTERN_WITH_TIMEZONE)
    private Timestamp customerExpireTime;

 

指定關係數據庫的存儲引擎

SpringBoot 2.0後使用JPA、Hibernate來操作MySQL,Hibernate默認使用MyISM存儲引擎而非InnoDB,前者不支持外鍵故會忽略外鍵定義,可通過如下SpringBoot配置指定用InnoDB: 

spring:
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect  #不加這句則默認爲myisam引擎

 

複雜Query 

注:軟刪除對Criteria API不起作用,但對Specification、QBE查詢會起作用

1. 複雜的操作(複雜查詢、批量更新、批量刪除、SQL函數使用等)

Criteria API用於動態查詢(在運行時才確定查詢條件等,動態產生查詢邏輯)。更多可參閱:見Criteria API:https://en.wikibooks.org/wiki/Java_Persistence/Criteria#CriteriaUpdate_.28JPA_2.1.29 

示例:

 View Code

2. 複雜條件(多條件和多表)查詢和分頁:Specification(Specification是Spring對Criteria的封裝)

 View Code

Spring還提供了QBE(query by example)查詢,示例:

 View Code

可參閱:https://blog.wuwii.com/jpa-specification.html

 

定義公共Repository

可以將業務中用到的公共方法抽離到公共Repository中,示例:

@NoRepositoryBean //避免Spring容器爲此接口創建實例。不被Service層直接用到的Repository(如base repository)均應加此聲明
public interface BaseRepository<T, ID> {
    @Modifying
    @Query("update #{#entityName} set isDelete='N' where id in ?1 ")
    Integer myUpdateAsNotDeleted(Collection<String> ids);
}

 

通過JPA定義表結構的關聯關係(如共用部分字段等)

這裏以實際項目中課程、實驗、步驟與其翻譯數據的表結構關聯方案設計爲例:

多語言表(翻譯表)與原表(主表)關聯方案設計,需求:字段(列)複用以免重複代碼定義、同一個列的定義如是否爲空在不同表中可不一樣(如有些字段主表中非空但翻譯表中可空),有如下方案:

  1. 無關聯,重複定義。pass
  2. 有關聯
    1. 通過@MappeSuperclass,不同子類可以完全繼承父類列定義且分別對應不同表,表結構完全相同,但不能覆蓋父類的定義。pass
    2. 通過@Inheritance,三種策略:
      1. SINGLE_TABLE:父、子類對應同一張表。源課程和翻譯課程id一樣,違背主鍵唯一約束。pass
      2. JOINED:父、子類對應不同表且子類自動加與父類主鍵一樣的字段與父類主鍵關聯,但父表中除主鍵之外的所有字段無法在子表中再出現。pass
      3. TABLE_PER_CLASS:父、子類對應不同表且表定義完全相同,無外鍵,但同一字段在不同表中字段定義無法不同。pass
    3. 定義個普通父類,子類繼承父類並分別進行@Column定義:不同子類對應不同表,不同表含有的字段及定義可不一樣。selected

 更多可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Inheritance

 

Map類型的關聯屬性

JPA1.0起就支持Map類型的關聯屬性,可通過 @MapKey 定義。示例:

 View Code

In JPA 1.0 the map key must be a mapped attribute of the collection values. The @MapKey annotation or <map-key> XML element is used to define a map relationship. If the MapKey is not specified it defaults to the target object's Id.

更多可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Maps

 

JPA Repository save方法

具體實現如下:(SimpleJpaRepository)

複製代碼

@Transactional
public <S extends T> S save(S paraEntity) {

    if (entityInformation.isNew(paraEntity)) {//根據id是否存在判斷是否爲new。這裏的new是相對於Persistence Context而言的,而非針對db是否有該記錄
        em.persist(paraEntity); //將該entity變爲managed的entity
        return entity;
    } else {
        return em.merge(paraEntity);//將該entity的數據更新到Persistence Context。若Persistence Context裏尚未有同id的該entity,則會根據id執行一次db查詢
    }
}

複製代碼

要明確下,這裏的save的語義並不是保存到db,而是保存到Persistence Context(保存到db會在事務提交時或Persistence Context flush時做)。save時會根據paraEntity的id是否存在來判斷paraEntity在Persistence Context中是否爲新的:

1、若是新的,則persist:paraEntity直接保存到Persistence Context(即entity變爲managed狀態)。flush時產生sql insert操作

2、若否,則merge:認爲Persistence Context中已有同id的entity,假設爲existEntity(這裏的'同id'當然不僅是值,還有entity類型,爲便於表述只說id):

2.1、若Persistence Context中確有同id entity,則將paraEntity的數據更新到對應existEntity。flush時產生sql update操作(且字段值有發生變化纔會產生sql update操作!)

此時,paraEntity與existEntity可能是同一個對象,也可能不是。

2.2、若Persistence Context中實際上尚未有同id的entity,則會根據id執行一次db查詢:若查不到則同1,否則同2.1

可見:

1、save的執行實際上是將entity的數據和狀態更新到Persist Context,只有當flush或事務提交時,纔會將entity的數據insert或update到數據庫。"At flush-time the entity state transition is materialized into a database DML statement."

2、調用save方法並不一定會產生db操作:數據字段值未發生改變時。如查出來再直接保存

3、另外,執行JPQL時(即下面說的結論對nativeQuery無效),只要JPA認爲最後是insert或update操作,被@UpdateTimestamp修飾的字段就會自動更新爲當前時間。同理只要認爲需要執行insert,@CreationTimestamp修飾的字段會更新爲當前值。

4、jpa persist操作對應sql insert 操作、jpa merge操作對應sql insert或sql update操作。

While a save method might be convenient in some situations, in practice, you should never call merge for entities that are either new or already managed. As a rule of thumb, you shouldn’t be using save with JPA. For new entities, you should always use persist, while for detached entities you need to call merge. For managed entities, you don’t need any save method because Hibernate automatically synchronizes the entity state with the underlying database record. 

 

JPA事務內Entity變更自動更新到數據庫(自動提交)

automatic dirty checking mechanism

若啓用了事務,則對於managed狀態的entity,若在事務內該entity有字段的值發生了變化,則即使未調save方法,該entity的變化最後也會被自動同步到數據庫,即sql update操作。即相當於在Persist Context flush時自動對各engity執行 save 方法。(org.hibernate.event.internal.AbstractFlushingEventListener中)

詳情可參閱:https://vladmihalcea.com/the-anatomy-of-hibernate-dirty-checking/

 

Spring Data JPA官方文檔閱讀note

Defining Repository Interfaces

Null Handling of Repository Method's Return:返回單實例時可能爲null(可以用各種Optional包裝類作爲返回類型以避免null),爲collections, collection alternatives, wrappers, and streams are guaranteed never to return null but rather the corresponding empty representation

 

Using Repositories with Multiple Spring Data Modules:Spring Data modules accept either third-party annotations (such as JPA’s @Entity) or provide their own annotations (such as @Document for Spring Data MongoDB and Spring Data Elasticsearch).

Defining Query Methods

 

 

進階

EntityManager

 

Entity生命週期及相關Event

Entity的生命週期由EntityManager管理,其生命週期在persistence context內。EntityManager的生命週期(三種): a transaction、request、a users session

兩個相關概念:persistence context(操作:persist、merge、detach、remove、clear、flush等)、transaction(操作:commit、rollback等)

Persistence Context:Acts as a Map of entities,the Map key is formed of the entity type (its class) and the entity identifier.

Entity生命週期的四個狀態:new、managed、detached、removed。其間轉換關係如下:

Entity生命週期的四個基本操作(CRUD):(更多詳情可參閱:https://en.wikibooks.org/wiki/Java_Persistence/Persistinghttps://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/https://vladmihalcea.com/jpa-persist-and-merge/

  • persist(Insert)
  • removed(Delete)
  • merge(Update)
  • find(Select)

這些操作爲EntityManager的方法,所有的操作都先在persistence context進行,除非persistence context flush或transaction commit了

相關事件:

  • @PrePersist/@PostPersist
  • @PreRemove/@PostRemove
  • @PreUpdate/@PostUpdate
  • @PostLoad

The pre- and post-persist methods are useful for setting timestamps for auditing or setting default values.

詳情參閱:https://dzone.com/articles/jpa-entity-lifecycle

實踐踩坑:

實際使用過程中常會遇到“identifier of an instance of com.sensetime.sensestudy.common.entity.CourseEntity was altered from to” 之類的錯

復現:在一個事務內修改entity的主鍵,此後執行其他任意查詢或更新操作就會產生該錯

原因:在entity處於managed生命週期時修改了entity主鍵導致的

解決:修改前先將該entity detach

 

 

Transaction

 JPA提供兩種transaction機制:Resource Local Transactions、JTA Transactions。詳情參閱:https://en.wikibooks.org/wiki/Java_Persistence/Transactions

When a transaction commit fails, the transaction is automatically rolled back, and the persistence context cleared, and all managed objects detached. 

Not only is there no way to handle a commit failure, but if any error occurs in an query before the commit, the transaction will be marked for rollback, so there is no real way to handle any error. 

 

Cache

參閱:https://en.wikibooks.org/wiki/Java_Persistence/Caching

 

更多參考資料:

Spring Data JPA 官方文檔

Java Persistenc-wikibooks

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章