如何实现容器管理事务(CMT)?

nyx*_*yxz 8 transactions java-ee cdi

我想将一个object(ReportBean)持久化到数据库,但是我收到了错误消息:

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

这是一些代码:

实体

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

    // fields (@Column, etc.)
    // setters/getters methods
    // toString , hashCode, equals methods
}
Run Code Online (Sandbox Code Playgroud)

允许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 {
}
Run Code Online (Sandbox Code Playgroud)

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;

}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

Q:createReport()执行em.persist时的方法中出现错误.我认为事务是由container(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> 
Run Code Online (Sandbox Code Playgroud)

如果我的问题不明确,请告诉我.

Arj*_*jms 19

我哪里弄错了?

您似乎认为@TransactionManagement(TransactionManagementType.CONTAINER)启用容器管理的事务,@TransactionAttribute(TransactionAttributeType.REQUIRED)然后为非EJB bean 启用方法上的事务.

然而,这在Java EE中是不可能的.

@TransactionManagement注释仅用于切换的EJB豆已经得到CMT从容器到BMT(Bean管理事务).该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)
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将此bean(使用@EJB@Inject)注入您的命名bean并使用它.或者,这个bean也可以使用,@Named因此可以直接在EL中使用,但并不总是建议这样做.

@Stateless豆不允许作用域(它基本上是"调用范围的"),但该@Stateful模型可以会话范围为原来的豆了.但是,使用给定的功能,它不需要是会话作用域.如果您只为实体经理执行此操作,请记住:

  • 实体经理创建起来非常便宜
  • 它不一定是线程安全的(官方它不是,但在某些实现中它是)
  • 无状态bean通常是池化的,因此需要在http会话中自行缓存EM.

有一些方法可以使用CDI和JTA实现看起来有点像CMT的东西,但如果你想要真正的CMT,那么目前这是唯一的方法.有计划打破固定组件模型,如无状态,有状态,单一和消息驱动到单个(CDI)注释(请参阅http://java.net/jira/browse/EJB_SPEC,特别是您的问题解耦@TransactionAttribute来自EJB组件模型的注释),但这还没有发生.