Giao dịch được đánh dấu là chỉ khôi phục: Làm cách nào để tìm ra nguyên nhân


93

Tôi đang gặp sự cố khi thực hiện giao dịch trong phương thức @Transactional của mình:

methodA() {
    methodB()
}

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

Khi tôi gọi methodB () từ methodA (), phương thức sẽ chuyển thành công và tôi có thể thấy "OK" trong nhật ký của mình. Nhưng sau đó tôi nhận được

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. Bối cảnh của methodB hoàn toàn bị thiếu trong trường hợp ngoại lệ - tôi cho là ổn chứ?
  2. Một cái gì đó trong methodB () đã đánh dấu giao dịch là chỉ khôi phục? Làm thế nào tôi có thể tìm ra nó? Chẳng hạn, có cách nào để kiểm tra một cái gì đó như thế này không getCurrentTransaction().isRollbackOnly()?- như thế này, tôi có thể xem qua phương pháp và tìm nguyên nhân.



Điều thú vị cần lưu ý là, nếu bảng cơ sở dữ liệu của bạn không tồn tại, đôi khi lỗi này cũng sẽ được hiển thị.
Ng Sek Long

Câu trả lời:


101

Khi bạn đánh dấu phương thức của mình là @Transactional, sự xuất hiện của bất kỳ ngoại lệ nào bên trong phương thức của bạn sẽ đánh dấu TX xung quanh là chỉ quay lại (ngay cả khi bạn bắt được chúng). Bạn có thể sử dụng các thuộc tính khác của @Transactionalchú thích để ngăn chú thích quay trở lại như:

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

6
Chà, tôi đã cố gắng sử dụng noRollbackFor=Exception.class, nhưng nó dường như không có tác dụng - nó có hoạt động đối với các trường hợp ngoại lệ kế thừa không?
Vojtěch

6
Đúng vậy. Nhìn vào câu trả lời của riêng bạn, điều đó đúng (bạn đã không cung cấp methodCtrong bài đăng đầu tiên của mình). Cả hai methodBmethodCsử dụng cùng một TX và luôn @Transactionalsử dụng chú thích cụ thể nhất , vì vậy khi methodCném ngoại lệ, TX xung quanh sẽ được đánh dấu là chỉ khôi phục. Bạn cũng có thể sử dụng các dấu hiệu lan truyền khác nhau để ngăn chặn điều này.
Ean V

@ Có thể có bất kỳ ngoại lệ nào bên trong phương thức của bạn sẽ đánh dấu TX xung quanh là chỉ quay trở lại Điều này cũng áp dụng cho các giao dịch readOnly không?
Marko Vranjkovic

1
@lolotron @Ean Tôi có thể xác nhận rằng nó thực sự sẽ áp dụng cho giao dịch chỉ đọc. Phương pháp của tôi đã đưa ra một EmptyResultDataAccessExceptionngoại lệ trên một giao dịch chỉ đọc và tôi gặp lỗi tương tự. Thay đổi chú thích của tôi để @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class)khắc phục sự cố.
cbmeeks

5
Câu trả lời này là sai. Mùa xuân chỉ biết về trường hợp ngoại lệ mà đi qua @Transactionalwrapper proxy, tức là còn tự do . Xem câu trả lời khác từ Vojtěch để biết toàn bộ câu chuyện. Có thể có các @Transactionalphương thức lồng nhau có thể đánh dấu chỉ khôi phục giao dịch của bạn.
Yaroslav Stavnichiy

68

Cuối cùng tôi cũng hiểu ra vấn đề:

methodA() {
    methodB()
}

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

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

Điều xảy ra là mặc dù methodBcó chú thích phù hợp, methodCnhưng không. Khi ngoại lệ được ném ra, @Transactionaldù sao thì giao dịch thứ hai cũng đánh dấu giao dịch đầu tiên là Rollback.


5
Trạng thái của giao dịch được lưu trữ trong một biến cục bộ của chuỗi. Khi spring chặn methodC và đặt cờ là khôi phục, giao dịch của bạn đã được đánh dấu để khôi phục. Bất kỳ đàn áp hơn nữa ngoại lệ sẽ không thể giúp ích bởi vì khi trận chung kết cam kết xảy ra, bạn sẽ nhận được lỗi
sống

@ Vojtěch Bất kỳ cách nào theo giả thuyết nếu methodCpropagation=requires_newthì methodB sẽ không khôi phục?
deFreitas

4
methodCphải ở trong Spring bean / dịch vụ khác hoặc bằng cách nào đó được truy cập qua Spring proxy. Nếu không, Spring sẽ không có khả năng biết về ngoại lệ của bạn. Chỉ có ngoại lệ đi qua @Transactionalchú thích mới có thể đánh dấu giao dịch là chỉ khôi phục.
Yaroslav Stavnichiy

43

Để nhanh chóng tìm nạp ngoại lệ gây ra mà không cần mã lại hoặc xây dựng lại , hãy đặt điểm ngắt trên

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

và đi lên trong ngăn xếp, thường là một số Interceptor. Ở đó bạn có thể đọc ngoại lệ gây ra từ một số khối bắt.


6
Trong Hibernate 4.3.11, đó làorg.hibernate.jpa.internal.TransactionImpl
Wim Deblauwe

Rất tốt bạn của tôi!
Rafael Andrade

Cảm ơn! Trong các phiên bản mới hơn của Hibernate (5.4.17), lớp org.hibernate.engine.transaction.internal.TransactionImplvà phương thức là setRollbackOnly.
Peter Catalin

11

Tôi đã đấu tranh với ngoại lệ này trong khi chạy ứng dụng của mình.

Cuối cùng , vấn đề nằm ở truy vấn sql . ý tôi là truy vấn sai.

vui lòng xác minh truy vấn của bạn. Đây là gợi ý của tôi


1
Để làm rõ: nếu bạn 1. có lỗi trong cú pháp sql của bạn 2. được thiết lập để khôi phục trên ngoại lệ 3. có các giao dịch readOnly, bạn sẽ gặp lỗi này vì cú pháp sql gây ra một ngoại lệ kích hoạt khôi phục không thành công vì bạn đang ở trong " chế độ chỉ đọc ".
Dave

7

Tìm kiếm các trường hợp ngoại lệ bị ném và mắc vào ... phần của mã của bạn. Các ngoại lệ về thời gian chạy và ứng dụng sao lưu gây ra việc khôi phục khi bị loại bỏ khỏi một phương thức kinh doanh ngay cả khi bị bắt ở một số nơi khác.

Bạn có thể sử dụng ngữ cảnh để tìm hiểu xem liệu giao dịch có được đánh dấu để khôi phục hay không.

@Resource
private SessionContext context;

context.getRollbackOnly();

1
Đối với tôi, dường như tôi đã tìm ra nguyên nhân, nhưng tôi không hiểu tại sao điều này lại xảy ra. Một phương thức bên trong ném ra một ngoại lệ, mà tôi bắt, ghi lại và bỏ qua. Nhưng dù sao thì Giao dịch cũng chỉ được đánh dấu là Phục hồi. Làm thế nào tôi có thể ngăn chặn nó? Tôi không muốn các Giao dịch bị ảnh hưởng bởi các ngoại lệ mà tôi nắm bắt được.
Vojtěch

SessionContextmột lớp học tiêu chuẩn vào mùa xuân? Đối với tôi, có vẻ như nó là EJB3 và nó không có trong Ứng dụng Mùa xuân của tôi.
Vojtěch

3
Thật tệ là tôi đã bỏ lỡ sự thật rằng đó là về mùa Xuân. Dù sao cũng nên có một cái gì đó giống như TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()có sẵn.
Mareen

2

Đã tìm thấy lời giải thích hay với các giải pháp: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) xóa @Transacional khỏi phương thức lồng nhau nếu nó không thực sự yêu cầu kiểm soát giao dịch. Vì vậy, ngay cả khi nó có ngoại lệ, nó chỉ nổi lên và không ảnh hưởng đến nội dung giao dịch.

HOẶC LÀ:

2) nếu phương thức lồng nhau cần kiểm soát giao dịch, hãy đặt nó thành REQUIRE_NEW cho chính sách truyền bá theo cách đó ngay cả khi ném ngoại lệ và được đánh dấu là chỉ khôi phục, người gọi sẽ không bị ảnh hưởng.


1

vô hiệu hóa giao dịch trong Bean.xml của bạn

<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>

nhận xét những dòng này, và bạn sẽ thấy ngoại lệ gây ra việc khôi phục;)


0

áp dụng mã dưới đây trong productRepository

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

trong khi thử nghiệm tháng sáu áp dụng mã dưới đây

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

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

}

nó đang hoạt động tốt cho mã của tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.