Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
432 views
in Technique[技术] by (71.8m points)

jakarta ee - How to implement container managed transaction (CMT)?

I wanted to persist an object(ReportBean) to the database, but I got error message:

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

Here is a bit of a code:

entity

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

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

custom annotation for allowing EntityManager injection (with @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 provider

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 class - has a method to persist report to the database.
I am trying to stick with the most important imports.
If I want to use the EntityManager to create a query (or NamedQuery as in the example) everything works.

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: Here in the createReport() method when the em.persist executes is where the error appear. I thought that the transaction is managed by the container (CMT), but now I think I am wrong. Where have I made a mistake? What is the right way to implement CMT?

Here is also my persistence.xml configuration:

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

Please let me know if something in my question is not clear.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Where have I made a mistake?

You seem to think that @TransactionManagement(TransactionManagementType.CONTAINER) enables container managed transactions and that @TransactionAttribute(TransactionAttributeType.REQUIRED) then enables a transaction on a method, for a non EJB bean.

This is however not (yet) possible in Java EE.

The @TransactionManagement annotation is only used to switch an EJB bean that already gets CMT from the container into BMT (Bean Managed Transactions). The CONTAINER constant is more for completeness, it's what you get when you omit the annotation altogether.

Likewise, the @TransactionAttribute will not enable transactions for a method on a non-EJB bean. The annotation itself exists to switch the transaction into another type (like REQUIRES_NEW). For an EJB it would not even be normally needed, since this too is the default and it too mainly exists for completeness, but can also be used to switch a single method back to REQUIRES if transactions are changed on the class level.

What is the right way to implement CMT?

The right way is to use a component model that already gets CMT from the container, like a stateless session 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)
    }
}

Then inject this bean (using @EJB or @Inject) into your named beans and use it. Alternatively this bean can be named too using @Named so it can be used directly in EL, but this is not always recommended.

The @Stateless bean does not allow scoping (it's basically 'invocation-scoped'), but the @Stateful model can be session scoped as your original bean was. However, with the given functionality it doesn't need to be session scoped. If you only did this for the entity manager, then remember:

  • The entity manager is very cheap to create
  • It is not necessarily thread-safe (officially it's not, but in some implementations it is)
  • Stateless beans are typically pooled, making the need to cache the EM yourself in an http session moot.

There are ways to implement something that looks a bit like CMT using CDI and JTA, but if you want true CMT then for the moment this is the only way. There are plans to break the fixed component models like stateless, stateful, singleton and message driven up into individual (CDI) annotations (see http://java.net/jira/browse/EJB_SPEC, and specifically for your question Decoupling the @TransactionAttribute annotation from the EJB component model), but this hasn't happened yet.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...