我在資料庫中有這個關聯 -

我希望資料像這樣保存在表中 -

相應的 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]]
現在我有幾個問題 -
為什么我得到分離的物體例外。當我們從資料庫中獲取物體時,Spring Data JPA 必須使用 entityManager 來獲取物體。獲取的物體會自動附加到持久性背景關系嗎?
在測驗中附加 @Transactional 時,為什么事務被回滾,并且沒有物體被持久化。我期待這兩個物體 - StudentSubscription 和 LibrarySubscription 應該使用連接表繼承方法來保留。
我嘗試了很多東西但沒有運氣。尋求 JPA 和 Spring DATA 專家的幫助:-)
提前致謝。
uj5u.com熱心網友回復:
讓我添加一些細節,這些細節概述了您的代碼的一些設計問題,這些問題使圖片變得非常復雜。通常,在使用 Spring Data 時,您不能簡單地查看您的表,為它們創建千篇一律的物體和存盤庫,并期望事情能簡單地作業。您至少需要花一點時間來了解領域驅動設計構建塊物體、聚合和存盤庫。
存盤庫管理聚合
在您的情況下,Student將StudentSubscriptions 視為一個物體(完整的物件參考、級聯持久性操作),但同時存在一個用于保存…Subscriptions 的存盤庫。這從根本上打破了保持Student聚合一致性的責任,因為您可以簡單地…Subscription通過存盤庫從存盤中洗掉 a 而聚合沒有機會干預。假設…Subscriptions 本身是聚合,并且您希望在那個方向上保持依賴關系,那么只能通過識別符號參考它們,而不是通過完整的物件表示。
這種安排還增加了認知負擔,因為現在有兩種添加訂閱的方法:
- 創建一個
…Subscription實體,分配Student,通過存盤庫持久化訂閱。 - 加載一個
Student,創建一個…Subscription,將其添加到學生,Student通過它的存盤庫持久化。
…Subscription雖然這已經是一種味道,但和之間的雙向關系Student強加了在代碼中手動管理它們的需要。此外,這些關系在概念之間建立了一個依賴回圈,這使得整個安排很難改變。您已經看到您已經為一個相當簡單的示例積累了大量(映射)復雜性。
更好的替代品會是什么樣子?
選項 1(不太可能):Students 和…Subscriptions 是“一個”
如果您想將概念放在一起并且不需要自己查詢訂閱,您可以避免將它們聚合并洗掉它們的存盤庫。這將允許您洗掉 to 的反向參考…Subscription,Student只剩下一種添加訂閱的方法:加載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熱心網友回復:
- 為什么我得到分離的物體例外。當我們從資料庫中獲取物體時,Spring Data JPA 必須使用 entityManager 來獲取物體。獲取的物體會自動附加到持久背景關系,對嗎?
是的,但只要 entityManager 保持打開狀態。如果沒有事務,一旦您從 回傳studentRepository.findById(100L).get();,entityManager 就會關閉并且物件會分離。
當您呼叫保存時,將創建一個新的 entityManager,它不包含對先前物件的參考。所以你有錯誤。
這@Trannsaction使得物體管理器在該方法的持續時間內保持打開狀態。
至少,這就是我認為正在發生的事情。
- 在測驗中附加 @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
下一篇:Hibernate/Spring-org.hibernate.engine.internal.StatefulPersistenceContext記憶體使用過多
