ธุรกรรมที่ทำเครื่องหมายว่าย้อนกลับเท่านั้น: ฉันจะหาสาเหตุได้อย่างไร


95

ฉันมีปัญหาในการทำธุรกรรมภายในวิธี @Transactional ของฉัน:

methodA() {
    methodB()
}

@Transactional
methodB() {
    ...
    em.persist();
    ...
    em.flush();
    log("OK");
}

เมื่อฉันเรียก methodB () จาก methodA () เมธอดจะผ่านไปได้สำเร็จและฉันสามารถเห็น "ตกลง" ในบันทึกของฉัน แต่แล้วฉันก็เข้าใจ

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
    at methodA()...
  1. บริบทของ methodB ขาดหายไปอย่างสมบูรณ์ในข้อยกเว้น - ฉันคิดว่าอะไรดี?
  2. มีบางอย่างภายใน methodB () ทำเครื่องหมายว่าธุรกรรมเป็นการย้อนกลับเท่านั้น? ฉันจะหามันได้อย่างไร? ตัวอย่างเช่นมีวิธีตรวจสอบบางอย่างgetCurrentTransaction().isRollbackOnly()?เช่นนี้หรือไม่ฉันสามารถทำตามขั้นตอนและค้นหาสาเหตุได้


ที่เกี่ยวข้อง: stackoverflow.com/q/25322658/697313
Yaroslav Stavnichiy

สิ่งที่น่าสนใจที่ควรทราบคือหากไม่มีตารางฐานข้อมูลของคุณบางครั้งข้อผิดพลาดนี้ก็จะแสดงขึ้นด้วย
อึ้งเสกนาน

คำตอบ:


101

เมื่อคุณทำเครื่องหมายวิธีการของคุณการ@Transactionalเกิดข้อยกเว้นใด ๆ ภายในวิธีการของคุณจะทำเครื่องหมาย TX รอบข้างเป็นย้อนกลับเท่านั้น (แม้ว่าคุณจะจับได้ก็ตาม) คุณสามารถใช้แอตทริบิวต์อื่น ๆ ของ@Transactionalคำอธิบายประกอบเพื่อป้องกันไม่ให้ย้อนกลับได้เช่น:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)

6
ฉันพยายามใช้noRollbackFor=Exception.classแต่ดูเหมือนว่าจะไม่มีผล - มันใช้ได้กับข้อยกเว้นที่สืบทอดมาหรือไม่?
Vojtěch

6
ใช่. เมื่อมองจากคำตอบของคุณเองนั้นถูกต้อง (คุณไม่ได้ระบุmethodCไว้ในโพสต์แรกของคุณ) ทั้งสองmethodBและmethodCใช้ TX เดียวกันและใช้@Transactionalคำอธิบายประกอบที่เฉพาะเจาะจงที่สุดเสมอดังนั้นเมื่อmethodCโยนข้อยกเว้น TX รอบ ๆ จะถูกทำเครื่องหมายเป็นย้อนกลับเท่านั้น คุณยังสามารถใช้เครื่องหมายการขยายพันธุ์ต่างๆเพื่อป้องกันปัญหานี้ได้
Ean V

1
@lolotron @Ean ฉันสามารถยืนยันได้ว่าจะใช้กับธุรกรรมแบบอ่านอย่างเดียว วิธีการของฉันมีEmptyResultDataAccessExceptionข้อยกเว้นสำหรับธุรกรรมแบบอ่านอย่างเดียวและฉันได้รับข้อผิดพลาดเดียวกัน การเปลี่ยนคำอธิบายประกอบของฉันเพื่อ@Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)แก้ไขปัญหา
cbmeeks

5
คำตอบนี้ผิด ฤดูใบไม้ผลิเท่านั้นที่รู้เกี่ยวกับข้อยกเว้นที่ผ่าน@Transactionalเสื้อคลุมพร็อกซี่คือuncaught ดูคำตอบอื่น ๆ จากVojtěchสำหรับเรื่องเต็ม อาจมี@Transactionalวิธีการซ้อนกันที่สามารถทำเครื่องหมายการย้อนกลับธุรกรรมของคุณเท่านั้น
Yaroslav Stavnichiy

1
noRollbackForใช้ได้เฉพาะในกรณีที่globalRollbackOnParticipationFailure=false
amir110

69

ในที่สุดฉันก็เข้าใจปัญหา:

methodA() {
    methodB()
}

@Transactional(noRollbackFor = Exception.class)
methodB() {
    ...
    try {
        methodC()
    } catch (...) {...}
    log("OK");
}

@Transactional
methodC() {
    throw new ...();
}

สิ่งที่เกิดขึ้นคือแม้ว่าจะmethodBมีคำอธิบายประกอบที่ถูกต้อง แต่methodCก็ไม่มี เมื่อเกิดข้อยกเว้นขึ้นครั้งที่สองจะ@Transactionalทำเครื่องหมายธุรกรรมแรกเป็นย้อนกลับเท่านั้น


5
สถานะของธุรกรรมจะถูกเก็บไว้ในตัวแปรโลคัลเธรดเมื่อสปริงสกัดกั้น methodC และตั้งค่าแฟล็กเป็นย้อนกลับธุรกรรมของคุณจะถูกทำเครื่องหมายสำหรับการย้อนกลับแล้ว การระงับข้อยกเว้นเพิ่มเติมจะไม่ช่วยเพราะเมื่อการกระทำสุดท้ายเกิดขึ้นคุณจะได้รับข้อผิดพลาด
ชีวิตอยู่

@ Vojtěchวิธีใดก็ตามถ้าmethodCมีpropagation=requires_newแล้วmethodBจะไม่ย้อนกลับ?
deFreitas

4
methodCต้องอยู่ใน Spring bean / service ที่แตกต่างกันหรือเข้าถึงผ่าน Spring proxy มิฉะนั้น Spring จะไม่มีความเป็นไปได้ที่จะทราบเกี่ยวกับข้อยกเว้นของคุณ เฉพาะข้อยกเว้นที่ผ่าน@Transactionalคำอธิบายประกอบเท่านั้นที่สามารถทำเครื่องหมายธุรกรรมเป็นการย้อนกลับเท่านั้น
Yaroslav Stavnichiy

นั่นไม่ใช่วิธีแก้ปัญหา เป็นเพียงความเข้าใจผิดและการใช้กลไก Spring Transaction AOP อย่างไม่ถูกต้อง คุณควรใช้คำอธิบายประกอบเกี่ยวกับธุรกรรมเฉพาะเมื่อคุณแน่ใจว่าคุณต้องการบริบทธุรกรรมหรือการเผยแพร่แยกต่างหากสำหรับการดำเนินการที่คุณใช้ในสถานที่นั้น ในกรณีอื่น ๆ คุณสามารถตั้งค่าการเผยแพร่ธุรกรรมได้อย่างถูกต้อง -1
Mykyta Chelombitko

43

หากต้องการดึงข้อยกเว้นที่เป็นสาเหตุอย่างรวดเร็วโดยไม่จำเป็นต้องโค้ดใหม่หรือสร้างใหม่ให้ตั้งค่าเบรกพอยต์

org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3

และขึ้นไปในสแต็กโดยปกติจะเป็น Interceptor บางตัว คุณสามารถอ่านข้อยกเว้นที่เป็นสาเหตุจากบล็อกจับบางส่วนได้


6
ใน Hibernate 4.3.11 คือorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe

ดีมากเพื่อน!
Rafael Andrade

ขอบคุณ! ในรุ่นใหม่ของ Hibernate (5.4.17) ชั้นเรียนเป็นและวิธีการที่org.hibernate.engine.transaction.internal.TransactionImpl setRollbackOnly
Peter Catalin

11

ฉันต่อสู้กับข้อยกเว้นนี้ขณะเรียกใช้แอปพลิเคชันของฉัน

ในที่สุดปัญหาคือในแบบสอบถาม SQL ฉันหมายความว่าแบบสอบถามไม่ถูกต้อง

โปรดตรวจสอบข้อสงสัยของคุณ นี่คือคำแนะนำของฉัน


1
เพื่อชี้แจง: หากคุณ 1. มีข้อผิดพลาดในไวยากรณ์ sql ของคุณ 2. ถูกตั้งค่าให้ย้อนกลับในข้อยกเว้น 3. มีธุรกรรมแบบอ่านอย่างเดียวคุณจะได้รับข้อผิดพลาดนี้เนื่องจากไวยากรณ์ sql ทำให้เกิดข้อยกเว้นที่ทำให้เกิดการย้อนกลับซึ่งล้มเหลวเนื่องจากคุณอยู่ใน " อ่านอย่างเดียว "โหมด
Dave

7

มองหาข้อยกเว้นที่ถูกโยนและจับได้ใน...ส่วนของโค้ดของคุณ ข้อยกเว้นของแอปพลิเคชันรันไทม์และการย้อนกลับทำให้เกิดการย้อนกลับเมื่อโยนออกจากวิธีการทางธุรกิจแม้ว่าจะถูกจับในที่อื่นก็ตาม

คุณสามารถใช้บริบทเพื่อดูว่าธุรกรรมถูกทำเครื่องหมายสำหรับการย้อนกลับหรือไม่

@Resource
private SessionContext context;

context.getRollbackOnly();

1
สำหรับฉันฉันพบสาเหตุแล้ว แต่ฉันไม่เข้าใจว่าเหตุใดจึงเกิดขึ้น วิธีการภายในโยนข้อยกเว้นซึ่งฉันจับได้บันทึกและเพิกเฉย แต่ธุรกรรมจะถูกทำเครื่องหมายว่าเป็นการย้อนกลับเท่านั้น ฉันจะป้องกันได้อย่างไร? ฉันไม่ต้องการให้ธุรกรรมได้รับอิทธิพลจากข้อยกเว้นที่ฉันจับได้อย่างถูกต้อง
Vojtěch

คือSessionContextชั้นมาตรฐานในฤดูใบไม้ผลิ? สำหรับฉันแล้วมันค่อนข้างเป็น EJB3 และไม่มีอยู่ในแอปพลิเคชัน Spring ของฉัน
Vojtěch

3
ฉันไม่ดีฉันพลาดความจริงที่ว่ามันเกี่ยวกับฤดูใบไม้ผลิ อย่างไรก็ตามควรมีบางอย่างเช่นTransactionAspectSupport.currentTransactionStatus().isRollbackOnly()ใช้ได้
Mareen

3

พบคำอธิบายที่ดีพร้อมโซลูชัน: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) ลบ @Transacional ออกจากเมธอดที่ซ้อนกันหากไม่ต้องการการควบคุมธุรกรรมจริงๆ แม้ว่าจะมีข้อยกเว้น แต่ก็เป็นเพียงฟองสบู่และไม่ส่งผลกระทบต่อสิ่งที่ทำธุรกรรม

หรือ:

2) หากเมธอดที่ซ้อนกันต้องการการควบคุมธุรกรรมให้กำหนดเป็น REQUIRE_NEW สำหรับนโยบายการเผยแพร่ด้วยวิธีนั้นแม้ว่าจะมีการยกเว้นและทำเครื่องหมายเป็นย้อนกลับเท่านั้นผู้โทรจะไม่ได้รับผลกระทบ


1

ปิดการใช้งาน transactionmanager ใน Bean.xml ของคุณ

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

แสดงความคิดเห็นในบรรทัดเหล่านี้และคุณจะเห็นข้อยกเว้นที่ทำให้เกิดการย้อนกลับ;)


0

ใช้รหัสด้านล่างใน productRepository

@Query("update Product set prodName=:name where prodId=:id ") @Transactional @Modifying int updateMyData(@Param("name")String name, @Param("id") Integer id);

ขณะอยู่ในการทดสอบจูนิทให้ใช้รหัสด้านล่าง

@Test
public void updateData()
{
  int i=productRepository.updateMyData("Iphone",102);

  System.out.println("successfully updated ... ");
  assertTrue(i!=0);

}

มันใช้งานได้ดีสำหรับรหัสของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.