主頁 > 軟體工程 > 事務在將物體從多到一側持久化時回滾

事務在將物體從多到一側持久化時回滾

2022-11-21 23:06:26 軟體工程

我在資料庫中有這個關聯 - 事務在將物體從多到一側持久化時回滾

我希望資料像這樣保存在表中 - 事務在將物體從多到一側持久化時回滾

相應的 JPA 物體已經以這種方式建模(為簡單起見省略了 getter/setter)-

學生物體 -

@Entity 
@Table(name = "student")
public class Student {
    @Id
    @SequenceGenerator(name = "student_pk_generator", sequenceName = 
                              "student_pk_sequence", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = 
                               "student_pk_generator")
    @Column(name = "student_id", nullable = false)
    private Long studentId;

    @Column(name = "name", nullable = false)
    private String studentName;

    @OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
    private Set<StudentSubscription> studentSubscription;
}

STUDENT_SUBSCRIPTION 物體 -

@Entity
@Table(name = "student_subscription")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class StudentSubscription {
    @Id
    private Long studentId;

    @ManyToOne(optional = false)
    @JoinColumn(name = "student_id", referencedColumnName = "student_id")
    @MapsId
    private Student student;

    @Column(name = "valid_from")
    private Date validFrom;

    @Column(name = "valid_to")
    private Date validTo;
}

LIBRARY_SUBSCRIPTION 物體 -

@Entity
@Table(name = "library_subscription", 
       uniqueConstraints = {@UniqueConstraint(columnNames = {"library_code"})})
@PrimaryKeyJoinColumn(name = "student_id")
public class LibrarySubscription extends StudentSubscription {

    @Column(name = "library_code", nullable = false)
    private String libraryCode;

    @PrePersist
    private void generateLibraryCode() {
        this.libraryCode = // some logic to generate unique libraryCode
    }

}

COURSE_SUBSCRIPTION 物體 -

@Entity
@Table(name = "course_subscription", 
       uniqueConstraints = {@UniqueConstraint(columnNames = {"course_code"})})
@PrimaryKeyJoinColumn(name = "student_id")
public class CourseSubscription extends StudentSubscription {

    @Column(name = "course_code", nullable = false)
    private String courseCode;

    @PrePersist
    private void generateCourseCode() {
        this.courseCode = // some logic to generate unique courseCode
    }
}

現在,已經存在一個學生物體,其 id 比方說 - 100。現在我想保留該學生的圖書館訂閱。為此,我使用 Spring DATA JPA 存盤庫創建了一個簡單的測驗 -

@Test
public void testLibrarySubscriptionPersist() {
    Student student = studentRepository.findById(100L).get();
    StudentSubscription librarySubscription = new LibrarySubscription();
    librarySubscription.setValidFrom(//some date);
    librarySubscription.setValidTo(//some date);
    librarySubscription.setStudent(student);
    studentSubscriptionRepository.save(librarySubscription);
}

在運行這個測驗時我得到了例外 -

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.springboot.data.jpa.entity.Student; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.springboot.data.jpa.entity.Student

為了解決這個問題,我在測驗中附加了一個@Transactional。這修復了分離物體的上述例外,但物體 StudentSubscription 和 LibrarySubscription 沒有持久保存到資料庫中。事實上,交易正在回滾。

在日志中獲取此例外 -

INFO 3515 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@35390ee3 testClass = SpringDataJpaApplicationTests, testInstance = com.springboot.data.jpa.SpringDataJpaApplicationTests@48a12036, testMethod = testLibrarySubscriptionPersist@SpringDataJpaApplicationTests, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@5e01a982 testClass = SpringDataJpaApplicationTests, locations = '{}', classes = '{class com.springboot.data.jpa.SpringDataJpaApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@18ece7f4, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@264f218, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2462cb01, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@928763c, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7c3fdb62, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@1ad282e0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

現在我有幾個問題 -

  1. 為什么我得到分離的物體例外。當我們從資料庫中獲取物體時,Spring Data JPA 必須使用 entityManager 來獲取物體。獲取的物體會自動附加到持久性背景關系嗎?

  2. 在測驗中附加 @Transactional 時,為什么事務被回滾,并且沒有物體被持久化。我期待這兩個物體 - StudentSubscription 和 LibrarySubscription 應該使用連接表繼承方法來保留。

我嘗試了很多東西但沒有運氣。尋求 JPA 和 Spring DATA 專家的幫助:-)

提前致謝。

uj5u.com熱心網友回復:

讓我添加一些細節,這些細節概述了您的代碼的一些設計問題,這些問題使圖片變得非常復雜。通常,在使用 Spring Data 時,您不能簡單地查看您的表,為它們創建千篇一律的物體和存盤庫,并期望事情能簡單地作業。您至少需要花一點時間來了解領域驅動設計構建塊物體、聚合和存盤庫。

存盤庫管理聚合

在您的情況下,StudentStudentSubscriptions 視為一個物體(完整的物件參考、級聯持久性操作),但同時存在一個用于保存…Subscriptions 的存盤庫。這從根本上打破了保持Student聚合一致性的責任,因為您可以簡單地…Subscription通過存盤庫從存盤中洗掉 a 而聚合沒有機會干預。假設…Subscriptions 本身是聚合,并且您希望在那個方向上保持依賴關系,那么只能通過識別符號參考它們,而不是通過完整的物件表示。

這種安排還增加了認知負擔,因為現在有兩種添加訂閱的方法:

  1. 創建一個…Subscription實體,分配Student,通過存盤庫持久化訂閱。
  2. 加載一個Student,創建一個…Subscription,將其添加到學生,Student通過它的存盤庫持久化。

…Subscription雖然這已經是一種味道,但和之間的雙向關系Student強加了在代碼中手動管理它們的需要。此外,這些關系在概念之間建立了一個依賴回圈,這使得整個安排很難改變。您已經看到您已經為一個相當簡單的示例積累了大量(映射)復雜性。

更好的替代品會是什么樣子?

選項 1(不太可能):Students 和…Subscriptions 是“一個”

如果您想將概念放在一起并且不需要自己查詢訂閱,您可以避免將它們聚合并洗掉它們的存盤庫。這將允許您洗掉 to 的反向參考…SubscriptionStudent只剩下一種添加訂閱的方法:加載Student,添加…Subscription實體,保存Student,完成。這也賦予了Student聚合其核心責任:在其狀態上強制執行不變數(…Subscription必須遵循某些規則的集合,例如至少選擇一個等)

選項 2(更有可能):Students 和…Subscriptions 是單獨的聚合(可能來自單獨的邏輯模塊)

在這種情況下,我會完全洗掉…Subscriptions Student如果您需要查找Students …Subscriptions,您可以向…SubscriptionRepository(eg List<…Subscription> findByStudentId(…)) 添加查詢。作為這樣做的副作用,您洗掉了回圈并且Student不再(必須)知道任何關于…Subscriptions 的資訊,這簡化了映射。無需與急切/延遲加載等進行角力。如果應用任何交叉聚合規則,這些規則將應用于SubscriptionRepository.

啟發式總結

  • 明確區分什么是聚合和什么不是(前者獲得相應的存盤庫,后者則沒有)
  • 僅通過識別符號參考聚合。
  • 避免雙向關系。通常,關系的一側可以替換為存盤庫上的查詢方法。
  • 嘗試對從高級概念到低級概念的依賴關系進行建模(Students 與Subscriptionss 可能有意義,a…Subscription沒有 aStudent最有可能沒有意義。因此,后者是與模型更好的關系并且只能與之合作。)

uj5u.com熱心網友回復:

事務正在回滾,因為測驗正在測驗方法中進行資料庫更新。如果事務包含任何更新資料庫,@Transactional 會自動回滾。這也是強制使用事務的原因,因為 EntityManager 會在檢索到 Student 物體后立即關閉,因此要保持打開狀態,測驗必須在事務背景關系中。

可能如果我為我的測驗用例使用了 testDB,那么 spring 可能不會回滾此更新。

將設定一個 H2 testDb 并在那里執行相同的操作并將發布結果。

感謝您的快速幫助。:-)

uj5u.com熱心網友回復:

  1. 為什么我得到分離的物體例外。當我們從資料庫中獲取物體時,Spring Data JPA 必須使用 entityManager 來獲取物體。獲取的物體會自動附加到持久背景關系,對嗎?

是的,但只要 entityManager 保持打開狀態。如果沒有事務,一旦您從 回傳studentRepository.findById(100L).get();,entityManager 就會關閉并且物件會分離。

當您呼叫保存時,將創建一個新的 entityManager,它不包含對先前物件的參考。所以你有錯誤。

@Trannsaction使得物體管理器在該方法的持續時間內保持打開狀態。

至少,這就是我認為正在發生的事情。

  1. 在測驗中附加 @Transactional 時,為什么事務正在回滾,

對于雙向關聯,您需要確保關聯在雙方都得到更新。代碼應如下所示:

@Test
@Transactional
public void testLibrarySubscriptionPersist() {
    Student student = studentRepository.findById(100L).get();
    StudentSubscription librarySubscription = new LibrarySubscription();
    librarySubscription.setValidFrom(//some date);
    librarySubscription.setValidTo(//some date);

    // Update both sides:
    librarySubscription.setStudent(student);
    student.getStudentSubscription().add(librarySubscription);

    // Because of the cascade, saving student should also save librarySubscription.
    // Maybe it's not necessary because student is managed
    // and the db will be updated anyway at the end
    // of the transaction.
    studentSubscriptionRepository.save(student);
}

在這種情況下,您還可以使用EntityManager#getReference

@Test
@Transactional
public void testLibrarySubscriptionPersist() {
    EntityManager em = ... 
    StudentSubscription librarySubscription = new LibrarySubscription();
    librarySubscription.setValidFrom(//some date);
    librarySubscription.setValidTo(//some date);

    // Doesn't actually load the student
    Student student = em.getReference(Student.class, 100L);
    librarySubscription.setStudent(student);

    studentSubscriptionRepository.save(librarySubscription);
}

我認為這些解決方案中的任何一個都可以解決問題。沒有整個堆疊跟蹤很難說。

轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/537614.html

標籤:弹簧靴jpa弹簧数据-jpa弹簧数据

上一篇:如何在JPA中僅選擇一對多關系中的特定子項

下一篇:Hibernate/Spring-org.hibernate.engine.internal.StatefulPersistenceContext記憶體使用過多

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • Git本地庫既關聯GitHub又關聯Gitee

    創建代碼倉庫 使用gitee舉例(github和gitee差不多) 1.在gitee右上角點擊+,選擇新建倉庫 ? 2.選擇填寫倉庫資訊,然后進行創建 ? 3.服務端已經準備好了,本地開始作準備 (1)Git 全域設定 git config --global user.name "成鈺" git c ......

    uj5u.com 2020-09-10 05:04:14 more
  • CODING DevOps 代碼質量實戰系列第二課,相約周三

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。**《DevOps 代碼質量實戰(PHP 版)》**為 CODING DevOps 代碼質量實戰系列的第二課,同時也是本系列的 PHP ......

    uj5u.com 2020-09-10 05:07:43 more
  • 推薦Scrum書籍

    推薦Scrum書籍 直接上干貨,推薦書籍清單如下(推薦有順序的哦) Scrum指南 Scrum精髓 Scrum敏捷軟體開發 Scrum捷徑 硝煙中的Scrum和XP : 我們如何實施Scrum 敏捷軟體開發:Scrum實戰指南 Scrum要素 大規模Scrum:大規模敏捷組織的設計 用戶故事地圖 用 ......

    uj5u.com 2020-09-10 05:07:45 more
  • CODING DevOps 代碼質量實戰系列最后一課,周四發車

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。 **《DevOps 代碼質量實戰(Java 版)》**為 CODING DevOps 代碼質量實戰系列的最后一課,同時也是本系列的 ......

    uj5u.com 2020-09-10 05:07:52 more
  • 敏捷軟體工程實踐書籍

    Scrum轉型想要做好,第一步先了解并真正落實Scrum,那么我推薦的Scrum書籍是要看懂并實踐的。第二步是團隊的工程實踐要做扎實。 下面推薦工程實踐書單: 重構:改善既有代碼的設計 決議極限編程 : 擁抱變化 代碼整潔代碼 程式員的職業素養 修改代碼的藝術 撰寫可讀代碼的藝術 測驗驅動開發 : ......

    uj5u.com 2020-09-10 05:07:55 more
  • Jenkins+svn+nginx實作windows環境自動部署vue前端專案

    前面文章介紹了Jenkins+svn+tomcat實作自動化部署,現在終于有空抽時間出來寫下Jenkins+svn+nginx實作自動部署vue前端專案。 jenkins的安裝和配置已經在前面文章進行介紹,下面介紹實作vue前端專案需要進行的哪些額外的步驟。 注意:在安裝jenkins和nginx的 ......

    uj5u.com 2020-09-10 05:08:49 more
  • CODING DevOps 微服務專案實戰系列第一課,明天等你

    CODING DevOps 微服務專案實戰系列第一課**《DevOps 微服務專案實戰:DevOps 初體驗》**將由 CODING DevOps 開發工程師 王寬老師 向大家介紹 DevOps 的基本理念,并探討為什么現代開發活動需要 DevOps,同時將以 eShopOnContainers 項 ......

    uj5u.com 2020-09-10 05:09:14 more
  • CODING DevOps 微服務專案實戰系列第二課來啦!

    近年來,工程專案的結構越來越復雜,需要接入合適的持續集成流水線形式,才能滿足更多變的需求,那么如何優雅地使用 CI 能力提升生產效率呢?CODING DevOps 微服務專案實戰系列第二課 《DevOps 微服務專案實戰:CI 進階用法》 將由 CODING DevOps 全堆疊工程師 何晨哲老師 向 ......

    uj5u.com 2020-09-10 05:09:33 more
  • CODING DevOps 微服務專案實戰系列最后一課,周四開講!

    隨著軟體工程越來越復雜化,如何在 Kubernetes 集群進行灰度發布成為了生產部署的”必修課“,而如何實作安全可控、自動化的灰度發布也成為了持續部署重點關注的問題。CODING DevOps 微服務專案實戰系列最后一課:**《DevOps 微服務專案實戰:基于 Nginx-ingress 的自動 ......

    uj5u.com 2020-09-10 05:10:00 more
  • CODING 儀表盤功能正式推出,實作作業資料可視化!

    CODING 儀表盤功能現已正式推出!該功能旨在用一張張統計卡片的形式,統計并展示使用 CODING 中所產生的資料。這意味著無需額外的設定,就可以收集歸納寶貴的作業資料并予之量化分析。這些海量的資料皆會以圖表或串列的方式躍然紙上,方便團隊成員隨時查看各專案的進度、狀態和指標,云端協作迎來真正意義上 ......

    uj5u.com 2020-09-10 05:11:01 more
最新发布
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:41:12 more
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:35:34 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:05:44 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:00:18 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:20:31 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:55 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:18:51 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:00 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:17:55 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:12:06 more