2012-01-07 50 views
7

我想堅持一個對象(ReportBean)到數據庫中,但我得到的錯誤信息:如何實現容器管理事務(CMT)?

javax.persistence.TransactionRequiredException: Transaction is required to perform this operation (either use a transaction or extended persistence context) 

這是一個有點代碼:

實體

@Entity 
@Table(name="t_report") 
@Access(AccessType.FIELD) 
public class ReportBean implements Serializable { 

    // fields (@Column, etc.) 
    // setters/getters methods 
    // toString , hashCode, equals methods 
} 

定製註釋允許EntityManager注入(與@Inject

import javax.inject.Qualifier; 
import static java.lang.annotation.ElementType.*; 
import java.lang.annotation.Target; 

@Qualifier 
@Target({TYPE, METHOD, FIELD, PARAMETER}) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface MyEm { 
} 

的EntityManager提供商

import javax.enterprise.inject.Produces; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 
import javax.persistence.PersistenceContextType; 

public class EntityManagerProvider { 

    private final String PERSISTENCE_UNIT = "MyPersistenceUnit"; 

    @SuppressWarnings("unused") 
    @Produces 
    @MyEm 
    @PersistenceContext(unitName=PERSISTENCE_UNIT, type=PersistenceContextType.TRANSACTION) 
    private EntityManager em; 

} 

ValidateReportAction類 - 有堅持報告到數據庫的方法。
我正在努力堅持最重要的進口。
如果我想用EntityManager來創建一個查詢(或者像在這個例子中的NamedQuery),那麼一切正常。

import javax.inject.Inject; 
import javax.inject.Named; 
import javax.persistence.EntityManager; 
import javax.ejb.TransactionAttribute; 
import javax.ejb.TransactionAttributeType; 
import javax.ejb.TransactionManagement; 
import javax.ejb.TransactionManagementType; 
import javax.enterprise.context.SessionScoped; 


@Named("validateReportAction") 
@SessionScoped 
@TransactionManagement(TransactionManagementType.CONTAINER) 
public class ValidateReportAction extends ReportAction implements Serializable { 

    private static final long serialVersionUID = -2456544897212149335L; 

    @Inject @MyEm 
    private EntityManager em; 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public synchronized String createReport() { 
     ReportBean report = new Report(); 
     // set report properties 
     // em.createNamedQuery("queryName").getResultList(); ---- works 
     em.persist(report) 
    } 
} 

Q:在這裏,在執行em.persist當createReport()方法是其中所述錯誤出現。我認爲交易是由集裝箱管理的(CMT),但現在我認爲我錯了。我在哪裏犯了一個錯誤?什麼是實施CMT的正確方法?

這裏也是我的persistence.xml配置:

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.0" 
    xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> 

    <persistence-unit name="MyPersistenceUnit" transaction-type="JTA"> 

     <provider>org.hibernate.ejb.HibernatePersistence</provider> 
     <jta-data-source>java:jboss/TimeReportDS</jta-data-source> 
     <mapping-file>META-INF/orm.xml</mapping-file> 

     <class>....</class> 
     <class>....</class> 
     <class>....</class> 

     <properties> 

      <property name="jboss.entity.manager.factory.jndi.name" 
       value="java:/modelEntityManagerFactory" /> 

      <!-- PostgreSQL Configuration File --> 
      <property name="hibernate.connection.driver_class" value="org.postgresql.Driver" /> 
      <property name="hibernate.connection.password" value="password" /> 
      <property name="hibernate.connection.url" value="jdbc:postgresql://192.168.2.125:5432/t_report" /> 
      <property name="hibernate.connection.username" value="username" /> 

      <!-- Specifying DB Driver, providing hibernate cfg lookup 
       and providing transaction manager configuration --> 
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" /> 
      <property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/> 
      <property name="hibernate.transaction.manager_lookup_class" 
       value="org.hibernate.transaction.JBossTransactionManagerLookup" /> 
      <property name="hibernate.archive.autodetection" value="class" /> 

      <!-- Useful configuration during development - developer can see structured SQL queries --> 
      <property name="hibernate.show_sql" value="true" /> 
      <property name="hibernate.format_sql" value="false" /> 

     </properties> 
    </persistence-unit> 
</persistence> 

請讓我知道如果事情在我的問題是不明確的。

回答

18

我在哪裏犯了一個錯誤?

你似乎認爲@TransactionManagement(TransactionManagementType.CONTAINER)使容器管理的事務和@TransactionAttribute(TransactionAttributeType.REQUIRED)然後使上一個方法的交易,對於非EJB豆。但是,這在Java EE中尚不可行(但尚未可行)。

@TransactionManagement註釋僅用於將已經從容器獲取CMT的EJB bean切換到BMT(Bean Managed Transactions)。 CONTAINER常量更多是爲了完整性,它是完全忽略註釋時得到的結果。

同樣,@TransactionAttribute不會爲非EJB bean上的方法啓用事務。註釋本身用於將事務切換爲另一種類型(如REQUIRES_NEW)。對於一個EJB,它甚至不是通常需要的,因爲這也是默認的,它也主要是爲了完整性而存在的,但是如果事務在類級別上改變,也可以用來將單個方法切換回REQUIRES。

什麼是實施CMT的正確方法?

正確的方法是使用一個組件模型,已經從容器中得到CMT,像一個無狀態會話bean:

@Stateless 
public class ValidateReportAction extends ReportAction { 

    @PersistenceContext(unitName = "MyPersistenceUnit") 
    private EntityManager em; 

    public String createReport() { 
     ReportBean report = new Report(); 
     // set report properties   
     em.persist(report) 
    } 
} 

再注入這個bean(使用@EJB@Inject)到您的命名豆並使用它。或者,這個bean也可以使用@Named來命名,所以它可以直接在EL中使用,但這並不總是被推薦。

@Stateless bean不允許作用域(它基本上是'調用作用域'),但@Stateful模型可以作爲原始bean的會話作用域。但是,對於給定的功能,它不需要是會話範圍的。如果你只是這樣做的實體管理器,然後記住:

  • 實體管理器是非常便宜的創建
  • 它不一定是線程安全的(正式它不是,但在一些實現它)
  • 無狀態bean通常是合併的,因此需要將自己的EM緩存在http會話模擬中。

有實現的東西,看起來使用CDI和JTA有點像CMT方式,但如果你想真正的CMT那麼對於目前這是唯一的辦法。有計劃打破像無國籍,有狀態,單身和消息驅動成固定組件模型成個人(CDI)註釋(見http://java.net/jira/browse/EJB_SPEC,特別是對於您的問題Decoupling the @TransactionAttribute annotation from the EJB component model),但這還沒有發生。

+0

另一個完美的答案,謝謝! – nyxz 2012-01-08 14:49:59

+0

@Arjan Tijms,這個答案仍然有效嗎? – Ced 2016-02-04 08:17:47

+0

@Ced好吧,「@Transactional」現在可作爲單獨的註釋使用,所以「尚未發生」現在已經發生;) – 2016-03-01 21:29:48

1

你是對的,但在你的解決方案中,你創建一個嵌套的 事務,它從調用上下文中運行。 不幸的是,我沒能找到一個解決方案通過 這樣

@Stateless 
public class ValidateReportAction extends ReportAction { 
... 

    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    public synchronized String createReport() {