Reputation: 5544
What happens when the following programmatic transaction and session idiom is used with within CMT (EJB3) and Hibernate Core is set up to use CMT?
Assumed a current CMT transaction is required and started using default @TransactionAttribute(REQUIRED)
beginTransaction()
?commit()
try to commit the hibernate transaction immediately or wait until the current CMT commits?B. Does the behavior depends if the current-session is bound to CMT using getCurrentSession()
?
// A: openSession()
// B: getCurrentSession();
Session session = sessionFactory.openSession();
Transaction tx = null;
try
{
tx = session.beginTransaction();
// do some work
tx.commit();
}
catch (final RuntimeException e)
{
try
{
tx.rollback();
}
catch (final RuntimeException e)
{
// log error
}
throw e;
}
finally
{
session.close();
}
In my application currently i am using a single database and it worked fine using programmatic JDBC transactions with Hibernate. Now the application also uses a JMS-Queue for Mail messaging and would like to merge it into the global CMT transaction.
Edit:
At the moment i am not using EntityManager in the application at all and also would like to keep code portable to non-managed environments.
Hibernate configuration hibernate.cfg.xml
to enable CMT:
Hibernate 4.2.6 and Glassfish 3.1.2
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.autocommit">false</property>
<property name="hibernate.connection.datasource">jdbc/datasource</property>
<property name="hibernate.current_session_context_class">jta</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.CMTTransactionFactory</property>
<property name="hibernate.transaction.jta.platform">org.hibernate.service.jta.platform.internal.SunOneJtaPlatform</property>
SessionFactory retrieval
SessionFactory is build within an singleton EJB. Stripped unnecessary stuff.
@Startup
@Singleton
public class SessionManager
{
private SessionFactory sessionFactory;
public SessionManager()
{
final Configuration configuration = new Configuration().configure();
this.sessionFactory = configuration.buildSessionFactory();
}
}
Upvotes: 6
Views: 3327
Reputation: 3343
As Luk has pointed out, this is not the way to code it in CMT enviroment. Anyway, the session.beginTransaction()
part is safe here according to
http://docs.jboss.org/hibernate/annotations/3.5/api/org/hibernate/Session.html#beginTransaction%28%29 which says
If a new underlying transaction is required, begin the transaction. Otherwise continue the new work in the context of the existing underlying transaction
tx.rollback()
is also safe. It's not stated in the doc, but a CMTTransaction actually executes getTransaction().setRollbackOnly()
, i.e. it simply marks the TX for rollback. The commit actually does not commit the TX, but may flush the session. A real commit would violate the transaction semantics if more than one resource is involved.
Upvotes: 1
Reputation: 8323
A session (in JPA persistence context
, which is tied to an EntityManager
instance) is an "in-memory" snapshot of the state of a database schema subset.
Depending of your configuration the scope of your session will vary. In a standard web application you will have one session per request.
You can have many instances of session with different state at same time, session are isolated each others (operation performed on a session are not visible in another one)
A transaction is a unit of work (in theory a session too). It's bound to underlying RDBMS transaction system and tied to the session it was open on.
In a "container managed entity manager" context (what you're calling CMT), the container will be in charge of binding your session to the defined scope and propagate the transactions according to encountered @Transactional
annotation along methods invocation.
Actually what is happening :
your container maintains an instance of session somewhere and would be able to provide it to your ejb instance using a @PersistenceContext
annotation.
You are manually building a new session instance using a sessionFactory.openSession()
, opening a transaction on it and performing your operations. The managed session instance can't see any of those modification until you commit the transaction, manually flushed or closed your custom session and manually trigger a refresh on the container one.
The getCurrentSession()
method is an hibernate specific mechanism which act as the container session scope management mechanism in a Java SE context (no container). I suppose (but I've no idea of the hibernate JPA implementation) that it won't return the container managed session, but I may be wrong on this point. (edit I am)
Here a proper solution will be to retrieve the current container managed session instance using @PersistenceContext
and to manage transaction propagation using @Transactional
annotation.
See https://community.jboss.org/wiki/SessionsAndTransactions
See Luk anwser below
FYI See Container-Managed Transactions
EDIT (according to question edition)
See Difference between a "jta-datasource" and a " resource-local " datasource?
In fact it seems I was effectively wrong and that you are not required to use persistence context injection from container but you MUST use JTA Transactions.
From the EJB 3.0 spec, section 13.3.4 Enterprise Beans Using Container-Managed Transaction Demarcation:
The enterprise bean’s business methods [...] must not attempt to obtain or use the javax.transaction.UserTransaction interface.
It means you can use
sessionFactory.getCurrentSession()
but you mustn't use tx = session.beginTransaction() but instead
@TransactionAttribute(TransactionAttributeType.REQUIRED)
See Transaction demarcation with EJB/CMT section in the jboss doc above
Upvotes: 0
Reputation: 19002
I learned something new from your question, as I didn't know Hibernate can be configured this way (although it was clear that it supports JTA). Anyway according to the documentation it seems that you are not forced to configure it in order to use JTA, as it is described here :
If your persistence layer runs in an application server (for example, behind EJB session beans), every datasource connection obtained by Hibernate will automatically be part of the global JTA transaction. You can also install a standalone JTA implementation and use it without EJB. Hibernate offers two strategies for JTA integration.
Also see the examples in the documentation, as you do not need to open any transactions in a CMT context. However, if you want to control the transaction demarcations, check those BMT examples.
Upvotes: 0
Reputation: 431
With CMT (Container Managed Transaction) you don't declare anything like tx = session.beginTransaction(); you let the container do the work for you. You would only specify when and if the container supports transactions. Check out the oracle doc Java EE 5 Tutorial
Lets say you have an EJB, its default transaction scope is Required. So hibernate will actually be bound to that transaction scope.
following example with first ejb with no transaction which calls another one with a cmt:
@TransactionAttribute(NOT_SUPPORTED)
@Stateful
public class TransactionBean implements TransactionInterface{
@EJB BusinessBean businessBean;
public method1(){
businessBean.doSomething();
}
}
@TransactionAttribute(REQUIRED)
@Stateful
public class BusinessBean implements BusinessInterface{
@PersistenceContext(unitName = "some-persistence-unit")
private EntityManager entityManager;
public void doSomething(){
Someclass entity = entityManager.finde(Someclass.class, 1) // Find entity with id 1
entity.setData("somedata");
}
}
when the methode doSomething() is done, the container will flush and commit the update to the databese since the outer ejb doesn't have a running transaction. This only works if the datasource is also provided by the container
Upvotes: 1