object references an unsaved transient instance - save the transient instance before flushing 解决过程。
前言
本篇博客記錄一個Spring JPA報錯的解決方案。這個報錯源自于一個需求的的改動,由于原始的接口執(zhí)行過于緩慢,所以要改造成為異步通知模式,也因此遇到了這個報錯,浪費了一上午時間。
情景還原
由于涉及到公司內(nèi)部代碼不便展示,以偽碼還原整個過程。首先先把原本的結(jié)構(gòu)模擬出來,以下類、方法皆為虛構(gòu),用于描述問題:
@Entity public class Student{@Idprivate long id;private String name;@ManyToOnepriavte School school;//...略... } @Entity public class School{ @Idprivate long id;private String description;//...略... } @Transactional public void mockLogical(){mockStudent();//運算邏輯School school = new School();//運算邏輯:計算stu的值school.setters();jpaSchoolRepository.save(stu); } @Transactional public void mockStudent(){Student stu = new Student();//運算邏輯:計算stu的值stu.setters();jpaStudentRepository.save(stu); }public void mockLoop(){foreach(){mockLogical();} }這一段大概的邏輯就是在某一個Service里面有一個循環(huán)方法mockLoop(),循環(huán)調(diào)用邏輯方法mockLogical()。在mockLogical()方法內(nèi)對Student和School的值進行計算,并賦值存儲到庫。而這個依賴關(guān)系則是Student依賴School且是ManyToOne的定義,單線程下相當于一個事務mockLogical中又開啟了一個事務mockStudent,其中的依賴Spring JPA會幫助處理合并,因此程序會有序的執(zhí)行下去并不會有什么錯誤。但是由于業(yè)務需求改成了多線程,這就出問題了,如下:
public void mockLoop(){foreach(){//采用多線程執(zhí)行mockLogical()內(nèi)的邏輯executor.submit(new MockLogicalThread());} }就直接導致了報錯:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.*.domain.Student.school -> com.*.domain.School; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.*.domain.Student.school -> com.*.domain.School分析原因:由于為了保存Student,必須要有School這個實例對象,但是由于我們創(chuàng)建并賦值的School此時還沒有被創(chuàng)建出來,Spring就嘗試給我們創(chuàng)建了一個出來,并嘗試及聯(lián)插入。但是到后面發(fā)現(xiàn)我們也有一個新創(chuàng)建的School對象并且進行了存儲操作,于是Spring搞不清楚到底應該用哪個School去執(zhí)行插入數(shù)據(jù)的操作于是報了這樣的錯誤。怎么解決呢?通過網(wǎng)絡上各種帖子可以很容易查到通過添加注解屬性@ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE})可以解決:前一個PERSIST是持久化依賴類,后一個MERGE是合并依賴類,如果有多個的話。
@Entity public class Student{@Idprivate long id;private String name;@ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE})priavte School school;//...略... }一致性約束
到這里筆者以為可以解決的時候,運行結(jié)果就又報錯了,這次報錯的結(jié)果是Mysql返回的:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'description' cannot be null由于School表里字段description有不為null的約束,導致了Spring JAP無法進行落地。因為當設置依賴為PERSIST的時候,遇到save()方法就必然要對依賴類進行實例化落地,但是Spring并不知道School里面要填充什么內(nèi)容,于是除了id可以自增以外,其余的屬性全部是null,這樣就和Mysql表中的一致性約束起了沖突于是報了這個錯誤。分析可以得出報錯的根本原因就是處理依賴的時候沒有賦值,因此解決方案也就出來了:想辦法把依賴放到前面去實例化并進行持久化,如下。這樣也不需要Spring幫助我們做持久化了。
@Transactional public void mockLogical(){//運算邏輯School school = new School();//運算邏輯:計算stu的值school.setters();jpaSchoolRepository.save(stu);mockStudent();//把Student放到School持久化后面 } @Entity public class Student{@Idprivate long id;private String name;@ManyToOne //這里就不再需要Spring給我們做持久化和合并了。priavte School school;//...略... }一點思考
如果上面筆者的分析成立,理論上說單線程情況下應該也會出現(xiàn)這種報錯才對。但是單線程運行良好大概是Spring的@Transactional注解可以對串行程序造成的依賴進行合并寫入,但是在多線程下@Transactional并不能很好的運行,需要手動去處理這些依賴關(guān)系。當然這些僅僅是筆者根據(jù)表象進行的推測,如果有哪位同仁知道,還請不吝賜教。
總結(jié)
以上是生活随笔為你收集整理的object references an unsaved transient instance - save the transient instance before flushing 解决过程。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 汇编环境搭建
- 下一篇: Java:打包成jar包