The behavior is correct – Hibernate will always create a session (how else would you expect it to perform any operation?), and by loading the entity you have associated it with that session. Since withoutTransaction
is not participating in a transaction, the changes made within withTransaction
will happen within a new transaction and shouldn’t be visible unless you call refresh
, which will force a re-load from the database.
I’m quoting Hibernate’s official documentation:
The main function of the Session is to offer create, read and delete operations for instances of mapped entity classes. Instances may exist in one of three states:
- transient: never persistent, not associated with any Session
- persistent: associated with a unique Session detached: previously
- persistent, not associated with any Session
Transient instances may be made persistent by calling
save()
,persist()
orsaveOrUpdate()
. Persistent instances may be made transient by callingdelete()
. Any instance returned by aget()
orload()
method is persistent.
Taken from Java Persistence With Hibernate, Second Edition by Christian Bauer, Gavin King, and Gary Gregory:
The persistence context acts as a first-level cache; it remembers all entity instances you’ve handled in a particular unit of work. For example, if you ask Hibernate to load an entity instance using a primary key value (a lookup by identifier), Hibernate can first check the current unit of work in the persistence context. If Hibernate finds the entity instance in the persistence context, no database hit occurs—this is a repeatable read for an application. Consecutive
em.find(Item.class, ITEM_ID)
calls with the same persistence context will yield the same result.
Also from Java Persistence With Hibernate, Second Edition:
The persistence context cache is always on—it can’t be turned off. It ensures the following:
- The persistence layer isn’t vulnerable to stack overflows in the case of circular references in an object graph.
- There can never be conflicting representations of the same database row at the end of a unit of work. The provider can safely write all changes made to an entity instance to the database.
- Likewise, changes made in a particular persistence context are always immediately visible to all other code executed inside that unit of work and its persistence context. JPA guarantees repeatable entity-instance reads.
Concerning transactions, here’s an excerpt taken from official Hibernate’s documentation:
Defines the contract for abstracting applications from the configured underlying means of transaction management. Allows the application to define units of work, while maintaining abstraction from the underlying transaction implementation (eg. JTA, JDBC).
So, to sum it up, withTransaction
and withoutTransaction
will not share UnitOfWork and therefore will not share the first-level cache, which is why the second load returns the original value.
As to the reasons why these two methods do not share the unit of work, you can refer to Shailendra’s answer.
EDIT:
You seem to misunderstand something. A session must always be created – that’s how Hibernate works, period. Your expectation of no sessions being created is equal to expecting to execute a JDBC query without having a JDBC connection 🙂
The difference between your two examples is that with @Transactional(propagation = Propagation.NEVER)
your method is intercepted and proxied by Spring and only a single session is created for the queries in withoutTransaction
. When you remove the annotation you exclude your method from Spring’s transactional interceptor so a new session will be created for each DB-related operation. I repeat again, and I cannot stress this enough – you must have an open session to perform any queries.
As far as guarding goes – try swapping the annotations on the two methods by making withTransaction
use Propagation.NEVER and withoutTransaction
use the default @Transactional
annotation and see what happens (spoiler: you’ll get an IllegalTransactionStateException
).
EDIT2:
As for why the session is shared between two loads in the outer bean – that’s just what JpaTransactionManager
is supposed to do, and by annotating your method with @Transactional
you’ve notified Spring that it should use the configured transaction manager to wrap your method. Here’s what the official documentation says about JpaTransactionManager
‘s expected behavior:
PlatformTransactionManager implementation for a single JPA EntityManagerFactory. Binds a JPA EntityManager from the specified factory to the thread, potentially allowing for one thread-bound EntityManager per factory. SharedEntityManagerCreator and @PersistenceContext are aware of thread-bound entity managers and participate in such transactions automatically. Using either is required for JPA access code supporting this transaction management mechanism.
Also, to know how Spring is handling declarative transaction management (i.e. @Transactional
annotations on methods), refer to the official documentation. For ease of navigation, I’ll include a quote:
The most important concepts to grasp with regard to the Spring Framework’s declarative transaction support are that this support is enabled via AOP proxies, and that the transactional advice is driven by metadata (currently XML- or annotation-based). The combination of AOP with transactional metadata yields an AOP proxy that uses a
TransactionInterceptor
in conjunction with an appropriatePlatformTransactionManager
implementation to drive transactions around method invocations.