Spring-managed Transactions Explained - Part 2 (JPA)
source link: https://lorenzo-dee.blogspot.com/2015/12/spring-managed-transactions-part2.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
In the first part of the series, I showed how transactions work in plain-vanilla JDBC. And then I showed how Spring manages JDBC-based transactions. In this second part of the series, I'll show how transactions work in plain-vanilla JPA first. And then show how Spring manages JPA-based transactions.
Funds Transfer
To help illustrate transactions, I'll be using the same case study of transferring funds from one bank account to another. Here, we show code snippets of debit, credit, and transfer methods.
...
class
BankAccountService {
public
void
transfer(MonetaryAmount amount, ...) {
debit(amount, ...);
credit(amount, ...);
...
}
public
void
credit(MonetaryAmount amount, AccountId accountId) {
...
}
public
void
debit(MonetaryAmount amount, AccountId accountId) {
...
}
...
}
JPA Transactions
In plain-vanilla JPA, transactions are started by calling getTransaction().begin()
on the EntityManager
. The code snippet below illustrates this.
import
javax.persistence.*;
...
EntityManagerFactory emf = ...;
EntityManager em = emf.createEntityManager();
try
{
em.getTransaction().begin();
// make changes through entities
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
throw
e;
}
finally
{
em.close();
}
Technically, the EntityManager
is in a transaction from the point it is created. So calling begin()
is somewhat redundant. Until begin()
is called, certain operations such as persist
, merge
, remove
cannot be called. Queries can still be performed (e.g. find()
).
Objects that were returned from queries can be changed. Although the JPA specification is somewhat unclear on what will happen to these changes when no transaction has been started.
Now, let's apply JPA to the funds transfer case study.
We defined a BankAccount
entity to handle debit()
and credit()
behavior.
import
javax.persistence.*;
@Entity
...
class
BankAccount {
@Id
...;
...
public
void
debit(MonetaryAmount amount) {...}
public
void
credit(MonetaryAmount amount) {...}
...
}
We add an EntityManagerFactory
to BankAccountService
to enable the creation of EntityManager
s when needed.
import
javax.persistence.*;
...
class
BankAccountService {
private
EntityManagerFactory emf;
// injected via constructor
...
public
void
transfer(MonetaryAmount amount, ...) ... {
EntityManager em = emf.createEntityManager();
try
{
em.getTransaction().begin();
BankAccount fromAccount = em.find(BankAccount.
class
, ...);
BankAccount toAccount = em.find(BankAccount.
class
, ...);
fromAccount.debit(amount);
toAccount.credit(amount);
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
// handle exception (possibly rethrowing it)
}
finally
{
em.close();
}
}
public
void
credit(MonetaryAmount amount, AccountId ...) ... {
EntityManager em = emf.createEntityManager();
try
{
em.getTransaction().begin();
BankAccount theAccount = em.find(BankAccount.
class
, ...);
theAccount.credit(amount);
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
// handle exception (possibly rethrowing it)
}
finally
{
em.close();
}
}
public
void
debit(MonetaryAmount amount, AccountId ...) ... {
EntityManager em = emf.createEntityManager();
try
{
em.getTransaction().begin();
BankAccount theAccount = em.find(BankAccount.
class
, ...);
theAccount.debit(amount);
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
// handle exception (possibly rethrowing it)
}
finally
{
em.close();
}
}
}
Spring-managed JPA Transactions
The transfer
, credit
, and debit
methods could sure use a template class (something like a JdbcTemplate
) to remove all the boilerplate code. Spring previously provided a JpaTemplate
class, but was deprecated as of Spring 3.1, in favor of native EntityManager
usage (typically obtained through @PersistenceContext
).
So, let's do just that — use EntityManager
obtained through @PersistenceContext
.
import
javax.persistence.*;
...
class
BankAccountService {
@PersistenceContext
private
EntityManager em;
...
public
void
transfer(MonetaryAmount amount, ...) ... {
try
{
em.getTransaction().begin();
BankAccount fromAccount = em.find(BankAccount.
class
, ...);
BankAccount toAccount = em.find(BankAccount.
class
, ...);
fromAccount.debit(amount);
toAccount.credit(amount);
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
// handle exception (possibly rethrowing it)
}
}
public
void
credit(MonetaryAmount amount, AccountId ...) ... {
try
{
em.getTransaction().begin();
BankAccount theAccount = em.find(BankAccount.
class
, ...);
theAccount.credit(amount);
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
// handle exception (possibly rethrowing it)
}
}
public
void
debit(MonetaryAmount amount, AccountId ...) ... {
try
{
em.getTransaction().begin();
BankAccount theAccount = em.find(BankAccount.
class
, ...);
theAccount.debit(amount);
em.getTransaction().commit();
...
}
catch
(Exception e) {
em.getTransaction().rollback();
// handle exception (possibly rethrowing it)
}
}
}
Our code is a little bit simpler. Since we didn't create an EntityManager
, we don't have to close it. But we are still calling getTransaction().begin()
. Is there a better way? And how does an EntityManager
get injected into the object in the first place?
From my previous post in this series, the astute reader is probably already thinking of having Spring do the work for us. And rightfully so!
EntityManager
and @PersistenceContext
We tell Spring to inject an EntityManager
from the EntityManagerFactory
by adding a PersistenceAnnotationBeanPostProcessor
(either through XML <bean>
, or simply using a Java-based configuration via @Configuration
classes loaded via AnnotationConfigApplicationContext
).
- When using XML-based configuration, a
PersistenceAnnotationBeanPostProcessor
is transparently activated by the<context:annotation-config />
element. And this element also gets transparently activated by<context:component-scan />
. - When using Java-based
@Configuration
, theAnnotationConfigApplicationContext
is used. And with it, annotation config processors are always registered (one of which is the aforementionedPersistenceAnnotationBeanPostProcessor
).
By adding a single bean definition, the Spring container will act as a JPA container and inject an EnitityManager
from your EntityManagerFactory
.
JPA and @Transactional
Now that we have an EntityManager
, how can we tell Spring to begin transactions for us?
We tell Spring to start transactions by marking methods as @Transactional
(or mark the class as @Transactional
which makes all public methods transactional). This is consistent with the way Spring enables transactions with JDBC.
import
javax.persistence.*;
import
org.springframework.transaction.annotation.Transactional;
@Transactional
...
class
BankAccountService {
@PersistenceContext
private
EntityManager em;
...
public
void
transfer(MonetaryAmount amount, ...) ... {
BankAccount fromAccount = em.find(BankAccount.
class
, ...);
BankAccount toAccount = em.find(BankAccount.
class
, ...);
fromAccount.debit(amount);
toAccount.credit(amount);
}
public
void
credit(MonetaryAmount amount, AccountId ...) ... {
BankAccount theAccount = em.find(BankAccount.
class
, ...);
theAccount.credit(amount);
}
public
void
debit(MonetaryAmount amount, AccountId ...) ... {
BankAccount theAccount = em.find(BankAccount.
class
, ...);
theAccount.debit(amount);
}
}
Wow, that was nice! Our code just got a lot shorter.
And just as explained in the first part of this series, when Spring encounters this annotation, it proxies the object (usually referred to as a Spring-managed bean). The proxy starts a transaction (if there is no on-going transaction) for methods that are marked as @Transactional
, and ends the transaction when the method returns successfully.
A call to debit()
will use a transaction. A separate call to credit()
will use a transaction. But what happens when a call to transfer()
is made?
Since the transfer()
method is marked as @Transactional
, Spring will start a transaction. This same transaction will be used for calls to debit()
and credit()
. In other words, debit(amount)
and credit(amount)
will not start a new transaction. It will use the on-going transaction (since there is one).
But wait! How does Spring know when to inject a proper entity manager? Is it only injected when a transactional method is invoked?
Shared EntityManager
In one of my training classes, I tried the following to better understand how Spring injects an EntityManager
via @PersistenceContext
. And I believe it will help others too. So, here's what I tried:
import
javax.persistence.*;
import
org.springframework.transaction.annotation.Transactional;
import
org.springframework.beans.factory.InitializingBean;
@Transactional
...
class
BankAccountService
implements
InitializingBean {
@PersistenceContext
private
EntityManager em;
...
@Override
public
void
afterPropertiesSet() {
System.out.println(em.toString());
}
...
}
An output of something like this was displayed on the console after the application context started.
Shared EntityManager proxy for target factory [...]
So what is this shared entity manager?
When the application context starts, Spring injects a shared entity manager. The shared EntityManager
will behave just like an EntityManager
fetched from an application server's JNDI environment, as defined by the JPA specification. It will delegate all calls to the current transactional EntityManager
, if any; otherwise, it will fall back to a newly created EntityManager
per operation.
Going back to our question. Spring doesn't inject the right entity manager at the right time. It always injects a shared entity manager. But this shared entity manager is transaction-aware. It delegates to the current transactional EntityManager
, if there is an on-going transaction.
Conclusion
This concludes the two-part series. I hope that by starting off with the plain-vanilla versions of JDBC and JPA (sans DAOs and repositories), I was able to make it clearer as to how Spring is able to manage transactions behind the scenes. And that by having a clearer idea as to what Spring is doing behind the scenes, you can troubleshoot better, understand why you get an TransactionRequiredException
saying "No transactional EntityManager available", and add better fixes to your applications.
Now, it's time for a cold one.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK