2012-01-07 16 views
7

yo quería que persista un objeto (ReportBean) a la base de datos, pero me dio el mensaje de error:¿Cómo implementar la transacción administrada por contenedor (CMT)?

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

Aquí es un poco de un código:

entidad

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

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

anotación personalizada para permitir la inyección de EntityManager (con @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 { 
} 

proveedor de 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; 

} 

clase ValidateReportAction - tiene un método para persistir informe a la base de datos.
Estoy tratando de seguir con las importaciones más importantes.
Si quiero usar el EntityManager para crear una consulta (o NamedQuery como en el ejemplo) todo funciona.

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: Aquí, en el método createReport() cuando el em.persist ejecuta es donde aparece el error. Pensé que la transacción está gestionada por el contenedor (CMT), pero ahora creo que estoy equivocado. ¿Dónde he cometido un error? ¿Cuál es la forma correcta de implementar CMT?

Aquí es también la configuración de mi 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> 

Por favor, hágamelo saber si hay algo en mi pregunta no es clara.

Respuesta

18

¿Dónde he cometido un error?

Se parecen pensar que @TransactionManagement(TransactionManagementType.CONTAINER) permite transacciones de contenedores gestionado y que @TransactionAttribute(TransactionAttributeType.REQUIRED) permite entonces una transacción en un método, para un frijol no EJB.

Sin embargo, esto aún no es posible en Java EE.

La anotación @TransactionManagement solo se usa para cambiar un bean EJB que ya obtiene CMT del contenedor en BMT (Transacciones administradas por frijoles). La constante CONTAINER es más para completar, es lo que obtienes cuando omites la anotación por completo.

Del mismo modo, @TransactionAttribute no habilitará transacciones para un método en un bean no EJB. La anotación en sí existe para cambiar la transacción a otro tipo (como REQUIRES_NEW). Para un EJB ni siquiera sería normalmente necesario, ya que este también es el predeterminado y también existe principalmente para la integridad, pero también se puede utilizar para volver a un único método a REQUIERE si las transacciones se cambian en el nivel de clase.

¿Cuál es la forma correcta de implementar CMT?

La forma correcta es utilizar un modelo de componentes que ya se CMT del recipiente, como un bean de sesión sin estado:

@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) 
    } 
} 

Luego se inyecte esta frijol (usando @EJB o @Inject) en sus granos nombradas y usarlo. Alternativamente, este bean también se puede nombrar usando @Named para que pueda usarse directamente en EL, pero esto no siempre se recomienda.

El bean @Stateless no permite el alcance (básicamente tiene un "alcance de invocación"), pero el modelo @Stateful puede tener una sesión de ámbito como el bean original. Sin embargo, con la funcionalidad dada no es necesario que tenga un ámbito de sesión. Si sólo hizo esto por el gestor de la entidad, a continuación, recordar:

  • El gestor de la entidad es muy barato para crear
  • No es necesariamente seguro para subprocesos (oficialmente no lo es, pero en algunas implementaciones es)
  • Los beans sin estado normalmente se agrupan, por lo que es necesario almacenar el EM en caché en un debate de sesión http.

Hay formas de implementar algo que se parece un poco a CMT usando CDI y JTA, pero si quieres CMT verdadero, por el momento esta es la única manera. Hay planes para romper los modelos de componentes fijos como sin estado, con estado, singleton y mensajes dirigidos a anotaciones individuales (CDI) (consulte http://java.net/jira/browse/EJB_SPEC, y específicamente para su pregunta Decoupling the @TransactionAttribute annotation from the EJB component model), pero esto aún no ha sucedido.

+0

Otra respuesta perfecta, gracias! – nyxz

+0

@Arjan Tijms, ¿esta respuesta sigue siendo válida? – Ced

+0

@Ced Bueno, "@Transactional" ahora está disponible como una anotación separada, por lo que el "no ha sucedido todavía" ahora ha sucedido;) –

1

Tiene razón, pero en su solución crea una transacción anidada , que se ejecuta aislada del contexto de llamada. Por desgracia, yo no era capaz de encontrar una solución para pasar un contexto como éste transctions

@Stateless 
public class ValidateReportAction extends ReportAction { 
... 

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