Hibernate JTA multiuser session-transaction mapping - java

I am referring to https://developer.jboss.org/wiki/SessionsAndTransactions and currently trying to understand demarcation with JTA. It states that within a particular transaction using getCurrentSession() always gives the same current session. Does it mean:
If another user is executing the same piece of code (which fetches a transaction by lookup, then uses getCurrentSession() and then closes the transaction) in another thread - that user will have his own transaction and his own current session i.e. the current sessions of 2 users are same for themselves but different from each other?
If 1 is true and based on the code shown in the link for JTA demarcation - how does the system (read Hibernate) understand which session to respond to which user when getCurrentSession() is used? After all we don't pass the transaction as argument to getCurrentSession().
Any pointers/help is much appreciated.
Thanks

According to javadoc SessionFactory.getCurrentSession()
The definition of what exactly "current" means controlled by the {#link org.hibernate.context.spi.CurrentSessionContext} impl configured
for use.
JTA is configured this will default to the {#link org.hibernate.context.internal.JTASessionContext} impl.
Then you can see JTASessionContext javadoc and implementation.
If a session is not already associated with the current JTA transaction at the time {#link #currentSession()} is called, a new session will be opened and it will be associated with that JTA transaction.
public Session currentSession() throws HibernateException {
...
final TransactionManager transactionManager = jtaPlatform.retrieveTransactionManager();
...
txn = transactionManager.getTransaction();
...
final Object txnIdentifier = jtaPlatform.getTransactionIdentifier( txn );
...
Session currentSession = currentSessionMap.get( txnIdentifier );
...
}
TransactionManager javadoc
Internally, the transaction manager associates transactions with threads,
and the methods here operate on the transaction associated with the
calling thread.
So, it's similar (but more clearly) to
Sessions and Transactions/Transaction demarcation with plain JDBC:
In other words, the session is bound to the thread behind the scenes, but scoped to a transaction, just like in a JTA environment.

Related

Is em.persist() or em.merge() not needed or not for update?

I have the code from below and wondering how does JPA know to persist this update. I expected an em.merge() to be needed in order to perform the update. Is this actually safe ?
#Stateless
class User {
...
public void generateRandomNicknames() {
List<UserEntity> users = em.createNamedQuery("UserEntity.getAllUsers", UserEntity.class)
.getResultList();
for (UserEntity user : users) {
user.setNickname(generateRandomNickname());
}
// em.merge(user) or em.persist(user) not needed ?
}
}
In short: Managed entities are generally synchronized with the database on transaction commit. The JPA implementation is responsible for tracking changed managed entities and update the database. In your case, calling user.setNickname(...) will notify JPA that this specific user object is dirty. And a transaction is by default active on calling business methods of a Session EJB.
Note that the above is the default behavior that can be altered by configuration!
Longer story, with references from the JEE 8 sub-specs (the question is about JEE 6, these still apply, although in different section numbers):
(JPA 2.1) Section 3.2.4: Synchronization to the Database
The state of persistent entities is synchronized to the database at transaction commit. This synchronization
involves writing to the database any updates to persistent entities and their relationships as specified
above.
[...]The persistence provider runtime is permitted to perform synchronization to the database at other times
as well when a transaction is active and the persistence context is joined to the transaction. The flush
method can be used by the application to force synchronization.
There are other interesting details in this chapter, but note that merge() does NOT have to be called, for a managed entity to be saved at the end of the transaction.
Which brings us to the next detail, the transaction. Since this is a method of a Session EJB, it is by default run in the context of a transaction:
(EJB core 3.2) Section 8.3.6: Specification of a Bean’s Transaction Management Type
By default, a session bean or message-driven bean has container managed transaction demarcation if the transaction management type is not specified. [...]
(EJB core 3.2) Section 8.3.7: Specification of the Transaction Attributes for a Bean’s Methods
The Bean Provider of an enterprise bean with container-managed transaction demarcation may specify the transaction attributes for the enterprise bean’s methods. By default, the value of the transaction attribute for a method of a bean with container-managed transaction demarcation is the REQUIRED transaction attribute, and the transaction attribute does not need to be explicitly specified in this case.
An important detail is that the entities are actually managed. This is, in this case, because they are returned from JPA itself and because of how the JPQL query "UserEntity.getAllUsers" is structured. In general entities could be unmanaged or detached, in which case calling merge() or persist() would have been necessary.

Rollback Spring JDBC operation when not in transaction

I am using annotation driven transaction management with Spring JDBC.
I would like to have Spring throw an exception when by mistake I forgot to annotate with #Transactional a service method that inserts/updates/deletes.
By default data can be inserted/updated/deleted even not within a transaction.
You can use Propagation.MANDATORY in your DAO layer.
Propagation.MANDATORY will not start a transaction. It will check whether perticular method is attached to a transaction or not, if not container will throw an exception.
According to the documentation (Spring docs) it's just metadata to give an indication that the method or interface can be configured by something that is 'transactionally aware' (ie
With just tx:annotation-driven and no #Transactional attributes I believe you get the "default" transactionality applied:
Propagation setting is REQUIRED.
Isolation level is DEFAULT.
Transaction is read/write.
Transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported.
ny RuntimeException triggers rollback, and any checked Exception does not.
Assuming you're using the tx:annotation to drive it via a transaction manager then missing out the #Transactional attribute means you can't apply such properties as readOnly, isolation, propagation,rollbackFor, noRollbackFor etc.
I believe that MVC is slightly different - the hibernate session is tied directly to the MVC request - ie when the request is received the transaction starts.
Back to your example, the code for getSession() in HibernateDAOSupport is as follows:
protected final Session getSession()
throws DataAccessResourceFailureException, IllegalStateException
{
return getSession(this.hibernateTemplate.isAllowCreate());
}
Which in turn calls to:
/**
* Obtain a Hibernate Session, either from the current transaction or
* a new one. The latter is only allowed if "allowCreate" is true.
*.......
protected final Session getSession()
throws DataAccessResourceFailureException, IllegalStateException {
return getSession(this.hibernateTemplate.isAllowCreate());
}
which ultimately calls to :
/**
* ....
* #param allowCreate whether a non-transactional Session should be created
* when no transactional Session can be found for the current thread
* ....
*/
private static Session doGetSession(
SessionFactory sessionFactory, Interceptor entityInterceptor,
SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)
Fundamentally, a Transaction:Session is tied 1:1 afaik, and the only way to run without a transaction is by using say JBoss which has a 'baked in' persistence layer which provides the transactionality for you (under the covers). Even if you call getQuery() after getSession() you still effectively have a transaction occuring as it's a JDBC/Hibernate connection.

"suspend" hibernate session managed by spring transaction manager

Is there any way to remove/suspend a current spring managed hibernate session from a thread so a new one can be used, to then place the original session back onto the thread? Both are working on the same datasource.
To describe the problem in more detail. I'm trying to create a plugin for a tool who has it's own spring hibernate transaction management. In this plugin I would like to do some of my own database stuff which is done on our own spring transaction manager. When I currently try to perform the database actions our transaction manager starts complaining about an incompatibly transactionmanager already being used
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! HibernateTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single HibernateTransactionManager for all transactions on a single DataSource, no matter whether Hibernate or JDBC access.
A workaround that seems to do the trick is running my own code in a different thread and waiting for it to complete before I continue with the rest of the code.
Is there a better way then that, seems a bit stupid/overkill? Some way to suspend the current hibernate session, then open a new one and afterworths restoring the original session.
Is there any reason you can't have the current transaction manager injected into your plugin code? Two tx managers sounds like too many cooks in the kitchen. If you have it injected, then you should be able to require a new session before doing your work using the #transactional annotation's propagation REQUIRES_NEW attribute see the documentation for an example set-up
e.g.
#transactional(propogation = Propogation.REQUIRES_NEW)
public void addXXX(Some class) {
...
}
But this would use spring's PlatformTransactionManager rather than leaving it up to hibernate to manage the session / transaction.

How to get Session or EntityManager bound to thread?

As I understand when I use hibernate with Spring transactions, a session is bound to thread using ThreadLocal. My questions are:
How can I access the session or session factory (without injecting it to the bean) (thus by thread locale)?
2.How can I do the same in terms of JPA, thus using EnityManager / EntityManagerFactory?
You should use the SessionFactory.getCurrentSession() to get your Session. What the session returned from this method depends on the configuration parameter hibernate.current_session_context_class in the hibernate.cfg.xml . If it is set to thread , the returned session is get from the ThreadLocal , which means that if it is called for the first time in the current Java thread, a new Session is opened and returned . If it is called again in the same thread , the same session will be returned.
AFAIK , there are no equivalent SessionFactory.getCurrentSession() in JPA . I think you have manually set and get the EntityManager to the ThreadLocal
Reference
Hibernate Contextual sessions
Sessions and transactions
I was trying to figure this out today and ended up doing it this way:
#PersistenceContext(unitName = "jpaSessionFactoryName")
private EntityManager jpaSession;
I found this documentation very helpful:
https://docs.spring.io/spring/docs/4.3.2.RELEASE/spring-framework-reference/htmlsingle/#orm-jpa-straight
When you use #PersistenceContext, Spring injects a proxy that gives you an EntityManager bound to the current transaction (or a new one, if there is not any). This looks like a direct substitute for hibernate's:
sessionFactory.getCurrentSession();
which is exactly what I was looking for.

Reuse Hibernate session in thread

I've read somewhere that when a session is flushed or a transaction is committed, the session itself is closed by Hibernate. So, how can i reuse an Hibernate Session, in the same thread, that has been previously closed?
Thanks
I've read somewhere that when a session is flushed or a transaction is committed, the session itself is closed by Hibernate.
A flush does not close the session. However, starting from Hibernate 3.1, a commit will close the session if you configured current_session_context_class to "thread" or "jta", or if you are using a TransactionManagerLookup (mandatory JTA) and getCurrentSession().
The following code illustrates this (with current_session_context_class set to thead here):
Session session = factory.getCurrentSession();
Transaction tx = session.beginTransaction();
Product p = new Product();
session.persist(p);
session.flush();
System.out.println(session.isOpen()); // prints true
p.setName("foo");
session.merge(p);
tx.commit();
System.out.println(session.isOpen()); // prints false
See this thread and the section 2.5. Contextual sessions in the documentation for background on this.
So, how can i reuse an Hibernate Session, in the same thread, that has been previously closed?
Either use the built-in "managed" strategy (set the current_session_context_class property to managed) or use a custom CurrentSessionContext derived from ThreadLocalSessionContext and override ThreadLocalSessionContet.isAutoCloseEnabled().
Again, see the above links and also What about the extended Session pattern for long Conversations?
Wrong. The session is stays open, just a new transaction begins. The main thing is that all objects currently attached to the session STAY attached, so if you don't clear the session you have a memory leak here.

Categories

Resources