天天看點

解決Could not commit JPA transaction RollbackException: Transaction marked as rollbackOnly

解決Could not commit JPA transaction RollbackException: Transaction marked as rollbackOnly

Could not commit JPA transaction RollbackException: Transaction marked as rollbackOnly:出現該錯誤的原因是使用@Transactional事務托管注解的方法或類事務已經被标記為值復原,且不能再設定為不會滾。

首先了解下@Transactional注解:

@Transactional注解

@Transactional屬性

屬性 類型 描述
value String 可選的限定描述符,指定使用的事務管理器
propagation enum: Propagation 可選的事務傳播行為設定
isolation enum: Isolation 可選的事務隔離級别設定
readOnly boolean 讀寫或隻讀事務,預設讀寫
timeout int (in seconds granularity) 事務逾時時間設定
rollbackFor Class對象數組,必須繼承自Throwable 導緻事務復原的異常類數組
rollbackFor ClassName 類名數組,必須繼承自Throwable 導緻事務復原的異常類名字數組
noRollbackFor Class對象數組,必須繼承自Throwable 不會導緻事務復原的異常類數組
noRollbackFor ClassName 類名數組,必須繼承自Throwable 不會導緻事務復原的異常類名字數組

用法

@Transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法将都具有該類型的事務屬性,同時,我們也可以在方法級别使用該标注來覆寫類級别的定義。

雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這隻有在使用基于接口的代理時它才會生效。另外, @Transactional 注解應該隻被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 注解,這将被忽略,也不會抛出任何異常。

預設情況下,隻有來自外部的方法調用才會被AOP代理捕獲,也就是,類内部方法調用本類内部的其他方法并不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

   public Foo getFoo(String fooName) {
     // do something
   }

   // these settings have precedence for this method
   //方法上注解屬性會覆寫類注解上的相同屬性
   @Transactional(readOnly = false, propagation=
   Propagation.REQUIRES_NEW)
   public void updateFoo(Foo foo) {
     // do something
   }
 }
           

那麼我們的問題就是如果使用@Transactional注解,将事務交給Spring處理的話,因為@Transactional預設設定為復原,無論如何設定rollbackFor、noRollbackFor都會将事務復原。但是代碼執行的時候,似乎SQL語句沒有執行復原就結束了。是以接抛出該異常。

解決方法是從entityManager中得到EntityManagerFactory再建立一個新的EntityManager,然後開始執行事務:

@PersistenceContext
private EntityManager entityManager;

/**
 * 擷取 entityManager 
 * @return the entityManager
 */
public EntityManager getEntityManager()
{
    return entityManager;
}

/**
 * 設定 entityManager 
 * @param entityManager the entityManager to set
 */
public void setEntityManager(EntityManager entityManager)
{
    this.entityManager = entityManager;
}

private int execute(String sql,EntityManager em) 
{
    EntityManager em = getEntityManager().getEntityManagerFactory().createEntityManager();
    em.getTransaction().begin();
    .......
    em.getTransaction().commit();
    return ;
}
           

同時還能解決:javax.persistence.TransactionRequiredException: Executing an update/delete query異常,因為此處建立了一個事務處理。而該異常是沒有事務處理Spring抛出來的異常,一舉兩得!