【Spring連載】使用Spring Data----對象映射基礎Object Mapping Fundamentals

【Spring連載】使用Spring Data----對象映射基礎Object Mapping Fundamentals

  • 一、對象創建
    • 1.1 對象創建內部機制Object creation internals
  • 二、屬性填充Property population
    • 2.1 屬性填充內部機制Property population internals
  • 三、一般建議
    • 3.1 覆蓋屬性
  • 四、Kotlin支持
    • 4.1 Kotlin 對象創建
    • 4.2 Kotlin data 類的屬性填充
    • 4.3 Kotlin 覆蓋屬性
    • 4.4 Kotlin Value 類

本節介紹Spring Data對象映射、對象創建、字段和屬性訪問、可變性和不變性的基本原理。請注意,本節僅適用于不使用底層數據存儲(如JPA)的對象映射的Spring Data模塊。此外,請務必了解特定于存儲對象的映射,如索引、自定義列名或字段名等。
SpringData對象映射的核心職責是創建域對象的實例,并將store-native數據結構映射到這些實例上。這意味著我們需要兩個基本步驟:

  1. 使用公開的構造函數之一創建實例。
  2. 實例填充以物化(materialize)所有公開的屬性。

一、對象創建

Spring Data會自動嘗試檢測用于物化該類型對象的持久實體的構造函數。解析算法的工作原理如下:

  1. 如果有一個用@PersistenceCreator注解的靜態工廠方法,那么就使用它。
  2. 如果存在單個構造函數,則使用它。
  3. 如果有多個構造函數,并且恰好有一個構造函數是用@PersistenceCreator注解的,則使用它。
  4. 如果類型是Java Record,則使用規范構造函數。
  5. 如果存在無參數構造函數,則使用它。其他構造函數將被忽略。

值解析假定構造函數/工廠方法參數名稱與實體的屬性名稱匹配,即解析將像填充屬性一樣執行,包括映射中的所有自定義項(不同的數據存儲列或字段名等)。這還需要類文件中可用的參數名稱信息或構造函數上存在的@ConstructorProperties注解。
值解析可以通過使用Spring Framework的@Value值注解(使用特定于存儲的SpEL表達式)進行自定義。有關更多詳細信息,請參閱有關特定于存儲的映射的部分。

1.1 對象創建內部機制Object creation internals

為了避免反射的開銷,Spring Data對象創建默認使用運行時生成的工廠類,它將直接調用域類構造函數。例如,對于這個示例類型:

class Person {Person(String firstname, String lastname) {}
}

框架將在運行時創建一個語義上等同于這個的工廠類:

class PersonObjectInstantiator implements ObjectInstantiator {Object newInstance(Object... args) {return new Person((String) args[0], (String) args[1]);}
}

這使我們的性能比反射提高了10%。對于有資格進行此類優化的域類,它需要遵守一組約束:

  1. 它不能是private class
  2. 它不能是非靜態的內部類
  3. 它不能是CGLib代理類
  4. Spring Data要使用的構造函數不能是私有的

如果這些條件中沒有任何一個匹配,Spring Data將返回到通過反射實例化實體。

二、屬性填充Property population

一旦創建了實體的實例,Spring Data就會填充該類的所有剩余持久屬性。除非已經由實體的構造函數填充(即使用其構造函數參數列表),否則將首先填充標識符屬性,以允許解析循環對象引用。之后,在實體實例上設置所有尚未由構造函數填充的非瞬態(non-transient)屬性。為此,框架使用以下算法:

  1. 如果屬性是不可變的,但公開了with…方法(見下文),框架將使用with…方法創建一個具有新屬性值的新實體實例。
  2. 如果定義了屬性訪問(即通過getters和setters進行訪問),框架將調用setter方法。
  3. 如果屬性是可變的,框架直接設置字段。
  4. 如果屬性是不可變的,框架將使用持久性操作(請參見5.1對象創建)使用的構造函數來創建實例的副本。
  5. 默認情況下,框架直接設置字段值。

2.1 屬性填充內部機制Property population internals

與對象構造方面的優化(5.1.1章節)類似,框架也使用Spring Data運行時生成的訪問器類與實體實例進行交互。

class Person {private final Long id;private String firstname;private @AccessType(Type.PROPERTY) String lastname;Person() {this.id = null;}Person(Long id, String firstname, String lastname) {// Field assignments}Person withId(Long id) {return new Person(id, this.firstname, this.lastame);}void setLastname(String lastname) {this.lastname = lastname;}
}

生成的屬性訪問器

class PersonPropertyAccessor implements PersistentPropertyAccessor {private static final MethodHandle firstname;   --------2           private Person person;                         --------1  public void setProperty(PersistentProperty property, Object value) {String name = property.getName();if ("firstname".equals(name)) {firstname.invoke(person, (String) value);  --------2          } else if ("id".equals(name)) {this.person = person.withId((Long) value); --------3           } else if ("lastname".equals(name)) {this.person.setLastname((String) value);   --------4           }}
}1. PropertyAccessor持有基礎對象的可變實例。這是為了實現其他不可變屬性的變化。
2. 默認情況下,Spring Data使用字段訪問來讀取和寫入屬性值。根據私有字段的可見性規則,MethodHandles用于與字段交互。
3. 該類公開了一個用于設置標識符的withId()方法,例如,當一個實例插入到數據存儲中并生成了標識符時。調用withId()將創建一個新的Person對象。所有后續的變化(mutations)都將發生在新的實例中,而不影響先前的實例。
4. 使用屬性訪問允許在不使用MethodHandles的情況下直接調用方法。

這使我們的性能比反射提高了25%。對于有資格進行此類優化的域類,它需要遵守一組約束:

  • 類型不能位于默認包中或java包下。
  • 類型及其構造函數必須是public
  • 作為內部類的類型必須是static。
  • 所使用的Java運行時必須允許在原始ClassLoader中聲明類。Java 9和更新版本會帶來某些限制。

默認情況下,Spring Data會嘗試使用生成的屬性訪問器,如果檢測到限制,則會返回到基于反射的訪問器。
讓我們來看看以下實體:
一個示例實體

class Person {private final @Id Long id;                            --------1                    private final String firstname, lastname;             --------2                    private final LocalDate birthday;                     private final int age;                                --------3                    private String comment;                               --------4                    private @AccessType(Type.PROPERTY) String remarks;    --------5                    static Person of(String firstname, String lastname, LocalDate birthday) { --------6return new Person(null, firstname, lastname, birthday,Period.between(birthday, LocalDate.now()).getYears());}Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { --------6this.id = id;this.firstname = firstname;this.lastname = lastname;this.birthday = birthday;this.age = age;}Person withId(Long id) {                               --------1                   return new Person(id, this.firstname, this.lastname, this.birthday, this.age);}void setRemarks(String remarks) {                      --------5                   this.remarks = remarks;}
}1. identifier屬性是final,但在構造函數中設置為null。該類公開了一個用于設置標識符的withId()方法,例如,當一個實例插入到數據存儲中并生成了標識符時。原始Person實例在創建新實例時保持不變。同樣的模式通常應用于存儲管理的其他屬性,但可能必須更改這些屬性才能進行持久性操作。wither方法是可選的,因為持久性構造函數(請參見6)實際上是一個復制構造函數,設置屬性將轉化為創建一個應用了新標識符值的新實例。
2. firstname和lastname屬性是通過getter公開的普通不可變屬性。
3. age屬性是不可變的,但派生自birthday屬性。在顯示的設計中,數據庫值將勝過默認值,因為Spring Data使用了唯一聲明的構造函數。即使目的是首選(preferred)計算,重要的是該構造函數也要將年齡作為參數(可能會忽略它),否則屬性填充步驟將試圖設置年齡字段,但由于它是不可變的,并且不存在with…方法,因此失敗。
4. comment屬性是可變的,可以通過直接設置其字段來填充。
5. remarks屬性是可變的,并且通過調用setter方法來填充。
6. 該類公開了一個工廠方法和一個用于創建對象的構造函數。這里的核心思想是使用工廠方法而不是額外的構造函數,以避免通過@PersistenceCreator消除構造函數的歧義。相反,屬性的默認設置是在工廠方法中處理的。如果你希望Spring Data使用工廠方法進行對象實例化,請使用@PersistenceCreator對其進行注解。

三、一般建議

  • 盡量堅持使用不可變的對象——創建不可變對象很簡單,因為物化(materializing )對象只需調用其構造函數。此外,這避免了域對象中充斥著允許客戶端代碼操作對象狀態的setter方法。如果你需要這些,最好讓它們受到包保護,這樣它們只能由有限數量的共存(co-located)類型調用。僅構造函數的物化比屬性填充快30%。
  • 提供一個包含全部參數的構造函數——即使你不能或不想將實體塑造(model)為不可變的值,提供一個將實體的所有屬性(包括可變屬性)作為參數的構造函數仍然有價值,因為這允許對象映射跳過屬性填充以獲得最佳性能。
  • 使用工廠方法而不是重載構造函數來避免@PersistenceCreator——對于優化性能所需的全參數構造函數,我們通常希望公開更多特定于應用程序用例的構造函數,這些構造函數省略了自動生成的標識符等。使用靜態工廠方法來公開全參數構造函數的這些變體是一種既定模式。
  • 確保遵守允許使用生成的實例化器和屬性訪問器類的約束——
  • 對于要生成的標識符,仍然將final字段與all-arguments持久性構造函數(首選)或with…方法結合使用——
  • 使用Lombok避免樣板(boilerplate)代碼?—?由于持久性操作通常需要構造函數接受所有參數,因此它們的聲明變成了樣板參數到字段賦值的乏味重復,最好使用Lombok的@AllArgsConstructor來避免這種情況。

3.1 覆蓋屬性

Java允許靈活地設計域類,其中子類可以定義已經在其超類中以相同名稱聲明的屬性。參見下面的例子:

public class SuperType {private CharSequence field;public SuperType(CharSequence field) {this.field = field;}public CharSequence getField() {return this.field;}public void setField(CharSequence field) {this.field = field;}
}public class SubType extends SuperType {private String field;public SubType(String field) {super(field);this.field = field;}@Overridepublic String getField() {return this.field;}public void setField(String field) {this.field = field;// optionalsuper.setField(field);}
}

這兩個類都使用可賦值類型定義字段。然而,SubType隱藏了SuperType.field。根據類設計,使用構造函數可能是設置SuperType.field的唯一默認方法。或者,在setter中調用super.setField(…)可以在SuperType中設置字段。所有這些機制都會在一定程度上造成沖突,因為屬性共享相同的名稱,但可能表示兩個不同的值。如果類型不可賦值,則Spring Data將跳過super-type屬性。也就是說,被重寫屬性的類型必須可分配給要注冊為重寫的super-type屬性類型,否則該超類型屬性被視為瞬態屬性。我們通常建議使用不同的屬性名稱。
Spring Data模塊通常支持具有不同值的重寫屬性。從編程模型的角度來看,需要考慮以下幾點:

  1. 應該持久化哪個屬性(默認為所有聲明的屬性)?可以通過使用@Transient注解特性來排除屬性(properties)。
  2. 如何在數據存儲中表示屬性?對不同的值使用相同的字段/列名通常會導致數據損壞,因此應使用顯式字段/列名對至少一個屬性進行注解。
  3. 不能使用@AccessType(PROPERTY),因為在不對setter實現進行任何進一步假設的情況下,通常不能設置super-property。

四、Kotlin支持

Spring Data適配了Kotlin的特性,允許對象創建和變化(mutation)。

4.1 Kotlin 對象創建

Kotlin類支持實例化,默認情況下所有類都是不可變的,并且需要顯式屬性聲明來定義可變屬性。
Spring Data會自動嘗試檢測用于物化(materialize)該類型對象的持久實體的構造函數。解析算法的工作原理如下:

  1. 如果有一個構造函數是用@PersistenceCreator注解的,那么就會使用它。
  2. 如果類型是Kotlin data cass,則使用主構造函數。
  3. 如果有一個用@PersistenceCreator注解的靜態工廠方法,那么就使用它。
  4. 如果存在單個構造函數,則使用它。
  5. 如果有多個構造函數,并且恰好有一個構造函數是用@PersistenceCreator注解的,則使用它。
  6. 如果類型是Java Record,則使用規范構造函數。
  7. 如果存在無參數構造函數,則使用它。其他構造函數將被忽略。

考慮以下data類Person:

data class Person(val id: String, val name: String)

上面的類編譯為帶有顯式構造函數的典型類。我們可以通過添加另一個構造函數來定制這個類,并用@PersistenceCreator注解它來指示構造函數的首選項:

data class Person(var id: String, val name: String) {@PersistenceCreatorconstructor(id: String) : this(id, "unknown")
}

Kotlin通過允許在未提供參數的情況下使用默認值來支持參數可選性。當Spring Data檢測到具有參數默認值的構造函數時,如果數據存儲不提供值(或只是返回null),則它將忽略這些參數,因此Kotlin可以應用參數默認值。參見以下類,該類將參數默認值應用于name

data class Person(var id: String, val name: String = "unknown")

每當name參數不是結果的一部分或其值為空時,則name默認為unknown。

4.2 Kotlin data 類的屬性填充

在Kotlin中,所有的類在默認情況下都是不可變的,并且需要顯式的屬性聲明來定義可變屬性。參見以下data class Person:

data class Person(val id: String, val name: String)

這個類實際上是不可變的。它允許創建新實例,因為Kotlin生成一個copy(…)方法,該方法創建新對象實例,從現有對象復制所有屬性值,并應用提供的屬性值作為方法的參數。

4.3 Kotlin 覆蓋屬性

Kotlin允許聲明屬性覆蓋來改變子類中的屬性。

open class SuperType(open var field: Int)class SubType(override var field: Int = 1) :SuperType(field) {
}

這樣的安排呈現帶有名稱為field的兩個屬性。Kotlin為每個類中的每個屬性生成屬性訪問器(getter和setter)。有效代碼如下所示:

public class SuperType {private int field;public SuperType(int field) {this.field = field;}public int getField() {return this.field;}public void setField(int field) {this.field = field;}
}public final class SubType extends SuperType {private int field;public SubType(int field) {super(field);this.field = field;}public int getField() {return this.field;}public void setField(int field) {this.field = field;}
}

SubType上的Getters和setters只設置SubType.field,而不設置SuperType.field。在這種安排中,使用構造函數是設置SuperType.field的唯一默認方法。可以通過“this.SuperType.field=…”將方法添加到SubType以設置“SuperType.field”,但不在支持的約定范圍內。屬性重寫在一定程度上會造成沖突,因為屬性共享相同的名稱,但可能表示兩個不同的值。我們通常建議使用不同的屬性名稱。
Spring Data模塊通常支持具有不同值的重寫屬性。從編程模型的角度來看,需要考慮以下幾點:

  1. 應該持久化哪個屬性(默認為所有聲明的屬性)?可以通過使用@Transient注解特性來排除這些屬性。
  2. 如何在數據存儲中表示屬性?對不同的值使用相同的字段/列名通常會導致數據損壞,因此應使用顯式字段/列名對至少一個屬性進行注解。
  3. 不能使用@AccessType(PROPERTY),因為不能設置super-property。

4.4 Kotlin Value 類

Kotlin Value類是為更具表現力的領域模型(domain model)而設計的,以使底層概念易于理解。Spring Data可以讀取和寫入使用Value類定義屬性的類型。
參見以下領域模型:

@JvmInline
value class EmailAddress(val theAddress: String)                               --------1     data class Contact(val id: String, val name:String, val emailAddress: EmailAddress) ---21. 具有不可為null的值類型的簡單value類。
2. 使用EmailAddress值類定義屬性的Data class

使用非基本值類型的非空屬性在編譯類中被展平(flattened)為value類型。可空的原始值類型或可空的value-in-value類型用其包裝器類型表示,這會影響值類型在數據庫中的表示方式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/710737.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/710737.shtml
英文地址,請注明出處:http://en.pswp.cn/news/710737.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

libigl 網格中點細分(網格上采樣)

文章目錄 一、簡介二、實現代碼三、實現效果參考資料一、簡介 在網格細分中,我們可以將每個三角形分成若干個更小的三角形。其中最簡單的情況就是通過計算每個三角形每條邊的中點,這樣就可以將三角形分成四個更小的三角形。不過該方法并不會使得網格的表面和面積發生變化,而…

Python:練習:編寫一個程序,寫入一個美金數量,然后顯示出如何用最少的20美元、10美元、5美元和1美元來付款

案例: python編寫一個程序,寫入一個美金數量,然后顯示出如何用最少的20美元、10美元、5美元和1美元來付款: Enter a dollar amout:93 $20 bills: 4 $10 bills: 1 $5 bills:0 $1 bills:3 思考: 寫入一個美金數量&…

數據分析Pandas專欄---第十一章<Pandas數據聚合與分組(1)>

前言: 數據聚合和分組操作是數據處理過程中不可或缺的一部分。它們允許我們根據特定的條件對數據進行分組,并對每個組進行聚合計算。這對于統計分析、匯總數據以及生成報告和可視化非常有用。無論是市場營銷數據分析、銷售業績評估還是金融數據建模,數據…

【數據分享】2000~2023年MOD15A2H 061 葉面積指數LAI數據集

各位同學們好,今天和大伙兒交流的是2000~2013年MOD15A2H 061 LAI數據集。如果大家有下載處理數據等方面的問題,您可以私信或評論。 Myneni, R., Y. Knyazikhin, T. Park. MODIS/Terra Leaf Area Index/FPAR 8-Day L4 Global 500m SIN Grid V061. 2021, d…

在原有項目進行業務邏輯開發:同一用戶短時間不得提交多次申請,以及更新主表時數據刷新掉了角色權限以及密碼重置的問題,詳細思路及代碼

開發背景: 用戶提交表單后,插入到對應數據庫表的字段中去,因需要保存是哪一個用戶提交的,所以需要拿到主表的user_id,更新功能為記錄提交時間,短時間不得再次提交 在對一個已有角色權限分配,登錄…

【Spring連載】使用Spring Data訪問 MongoDB----對象映射之對象引用

【Spring連載】使用Spring Data訪問 MongoDB----對象映射之對象引用 一、使用DBRefs 一、使用DBRefs

layui中,父頁面與子頁面,函數方法的相互調用、傳參

<%--父頁面--%> <script type"text/javascript">var KaoHaoType 0; // 考號類型 自定義參數1// 選取考號類型function SelectKaoHaoType(callBack) {KaoHaoType 0; // 默認選擇填涂考號layer.open({type: 2, title: 請選擇 考號區類型, ar…

職場中被小人欺負了,應該一笑了之嗎?還是怎么辦?

在職場中遇到不公正的待遇或被欺負&#xff0c;確實是一個讓人困擾的問題。處理這類問題&#xff0c;首先要保持冷靜和理性&#xff0c;避免情緒化的反應&#xff0c;這樣有助于找到最合適的解決方案。以下是一些建議&#xff0c;您可以根據具體情況考慮&#xff1a; 1. **保持…

如何使用 Socket.IO、Angular 和 Node.js 創建實時應用程序

介紹 WebSocket 是一種允許服務器和客戶端之間進行全雙工通信的互聯網協議。該協議超越了典型的 HTTP 請求和響應范式。通過 WebSocket&#xff0c;服務器可以向客戶端發送數據&#xff0c;而無需客戶端發起請求&#xff0c;因此可以實現一些非常有趣的應用程序。 在本教程中…

網絡編程作業day2

1.將TPC和UDP通信模型各敲兩遍 &#xff08;1&#xff09;TPC通信模型&#xff1a; 服務器代碼&#xff1a; #include <myhead.h> #define SERVER_IP "192.168.125.136" #define SERVER_PORT 1314 int main(int argc, const char *argv[]) {//1、創建用于監…

CLion 2023:專注于C和C++編程的智能IDE mac/win版

JetBrains CLion 2023是一款專為C和C開發者設計的集成開發環境&#xff08;IDE&#xff09;&#xff0c;它集成了許多先進的功能&#xff0c;旨在提高開發效率和生產力。 CLion 2023軟件獲取 CLion 2023的智能代碼編輯器提供了豐富的代碼補全和提示功能&#xff0c;使您能夠更…

統計業務流量的毫秒級峰值 - 華為機試真題題解

考試平臺&#xff1a; 時習知 分值&#xff1a; 200分&#xff08;第二題&#xff09; 考試時間&#xff1a; 兩小時&#xff08;共3題&#xff09; 題目描述 業務模塊往外發送報文時&#xff0c;有時會出現網卡隊列滿而丟包問題&#xff0c;但從常規的秒級流量統計結果看&…

Mybatis-Plus介紹

目錄 一、Mybatis-Plus簡介 1.1、介紹 1.2、特性 1.3、架構 1.4、Mybatis-Plus與Mybatis的區別 二、快速入門 2.1、首先創建數據庫mybatis-plus 2.2、創建user表 2.3、插入數據 2.4、創建Spring-Boot項目 2.5、添加依賴 2.6、連接數據庫 一、Mybatis-Plus簡介 1.1、…

代碼隨想錄第46天|139.單詞拆分 多重背包理論基礎 背包總結

文章目錄 單詞拆分思路&#xff1a;代碼 多重背包≈0-1背包題目代碼 背包總結 單詞拆分 3 思路&#xff1a; 代碼 class Solution {public boolean wordBreak(String s, List<String> wordDict) {HashSet<String> set new HashSet<>(wordDict);boolean[]…

15個非常實用的JavaScript技巧,提高你的開發效率

本文我們將探討15個實用的JavaScript技巧&#xff0c;希望它們可以幫你提高開發效率&#xff0c;有用的話點贊收藏~。 1. 反轉字符串 你有時候可能需要將字符串顛倒過來。在JavaScript中&#xff0c;有一個巧妙的一行代碼可以實現這個目標&#xff1a; const reversedString…

sheng的學習筆記-卷積神經網絡經典架構-LeNet-5、AlexNet、VGGNet-16

目錄&#xff1a;目錄 看本文章之前&#xff0c;需要學習卷積神經網絡基礎&#xff0c;可參考 sheng的學習筆記-卷積神經網絡-CSDN博客 目錄 LeNet-5 架構圖 層級解析 1、輸入層&#xff08;Input layer&#xff09; 2、卷積層C1&#xff08;Convolutional layer C1&…

Dockerfile(5) - CMD 指令詳解

CMD 指定容器默認執行的命令 # exec 形式&#xff0c;推薦 CMD ["executable","param1","param2"] CMD ["可執行命令", "參數1", "參數2"...]# 作為ENTRYPOINT的默認參數 CMD ["param1","param…

VUE3自定義文章排行榜的簡單界面

文章目錄 一、代碼展示二、代碼解讀三、結果展示 一、代碼展示 <template><div class"article-ranking"><div class"header"><h2 class"title">{{ title }}</h2></div><div class"ranking-list&qu…

根據A(String)字段去重,并且選擇B(Ingter)字段最大的值

數據格式&#xff1a; [SkillDTO(Job電線工, rankGrade高級工, r4), SkillDTO(Job監察員, rankGrade技師, r5), SkillDTO(Job監察員, rankGrade高級工, r4), SkillDTO(skillJob監察員, rankGrade中級工, r3)] List<SkillDTO> resultList SkillDTOList.stream().coll…

電子技術——PN結電流關系方程

電子技術——PN結電流關系方程 平衡狀態下的PN結 平衡狀態下的PN結界面總共有兩種電流&#xff0c;一種為 擴散電流 另一種為 漂移電流 。兩種電流形成的平衡區域稱為 耗散區 。 在平衡狀態擴散電流等于漂移電流&#xff0c;此時靜電流為0&#xff0c;PN結外部沒有電流&…