. Binds a JPA
EntityManager from the specified factory to the thread, potentially allowing for
one thread-bound EntityManager per factory.
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.
This transaction manager is appropriate for applications that use a single
JPA EntityManagerFactory for transactional data access. JTA (usually through
org.springframework.transaction.jta.JtaTransactionManager ) is necessary
for accessing multiple transactional resources within the same transaction.
Note that you need to configure your JPA provider accordingly in order to make
it participate in JTA transactions.
Note: To be able to register a DataSource's Connection for plain JDBC code,
this instance needs to be aware of the DataSource (#setDataSource ).
The given DataSource should obviously match the one used by the given
EntityManagerFactory. This transaction manager will autodetect the DataSource
used as known connection factory of the EntityManagerFactory, so you usually
don't need to explicitly specify the "dataSource" property.
On JDBC 3.0, this transaction manager supports nested transactions via JDBC 3.0
Savepoints. The #setNestedTransactionAllowed "nestedTransactionAllowed"}
flag defaults to "false", though, as nested transactions will just apply to the
JDBC Connection, not to the JPA EntityManager and its cached objects.
You can manually set the flag to "true" if you want to use nested transactions
for JDBC access code which participates in JPA transactions (provided that your
JDBC driver supports Savepoints). Note that JPA itself does not support
nested transactions! Hence, do not expect JPA access code to semantically
participate in a nested transaction.
| Method from org.springframework.orm.jpa.JpaTransactionManager Detail: |
public void afterPropertiesSet() {
if (getEntityManagerFactory() == null) {
throw new IllegalArgumentException("Property 'entityManagerFactory' is required");
}
if (getEntityManagerFactory() instanceof EntityManagerFactoryInfo) {
EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) getEntityManagerFactory();
DataSource dataSource = emfInfo.getDataSource();
if (dataSource != null) {
setDataSource(dataSource);
}
JpaDialect jpaDialect = emfInfo.getJpaDialect();
if (jpaDialect != null) {
setJpaDialect(jpaDialect);
}
}
}
Eagerly initialize the JPA dialect, creating a default one
for the specified EntityManagerFactory if none set.
Auto-detect the EntityManagerFactory's DataSource, if any. |
protected void closeEntityManagerAfterFailedBegin(JpaTransactionManager.JpaTransactionObject txObject) {
if (txObject.isNewEntityManagerHolder()) {
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
try {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
catch (Throwable ex) {
logger.debug("Could not rollback EntityManager after failed transaction begin", ex);
}
finally {
EntityManagerFactoryUtils.closeEntityManager(em);
}
}
}
Close the current transaction's EntityManager.
Called after a transaction begin attempt failed. |
protected EntityManager createEntityManagerForTransaction() {
EntityManagerFactory emf = getEntityManagerFactory();
if (emf instanceof EntityManagerFactoryInfo) {
emf = ((EntityManagerFactoryInfo) emf).getNativeEntityManagerFactory();
}
Map properties = getJpaPropertyMap();
return (!CollectionUtils.isEmpty(properties) ?
emf.createEntityManager(properties) : emf.createEntityManager());
}
Create a JPA EntityManager to be used for a transaction.
The default implementation checks whether the EntityManagerFactory
is a Spring proxy and unwraps it first. |
protected void doBegin(Object transaction,
TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
throw new IllegalTransactionStateException(
"Pre-bound JDBC Connection found! JpaTransactionManager does not support " +
"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
"It is recommended to use a single JpaTransactionManager for all transactions " +
"on a single DataSource, no matter whether JPA or JDBC access.");
}
EntityManager em = null;
try {
if (txObject.getEntityManagerHolder() == null ||
txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
EntityManager newEm = createEntityManagerForTransaction();
if (logger.isDebugEnabled()) {
logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
}
txObject.setEntityManagerHolder(new EntityManagerHolder(newEm), true);
}
em = txObject.getEntityManagerHolder().getEntityManager();
// Delegate to JpaDialect for actual transaction begin.
Object transactionData = getJpaDialect().beginTransaction(em, definition);
txObject.setTransactionData(transactionData);
// Register transaction timeout.
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getEntityManagerHolder().setTimeoutInSeconds(timeout);
}
// Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
if (getDataSource() != null) {
ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
if (conHandle != null) {
ConnectionHolder conHolder = new ConnectionHolder(conHandle);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
conHolder.setTimeoutInSeconds(timeout);
}
if (logger.isDebugEnabled()) {
logger.debug("Exposing JPA transaction as JDBC transaction [" + conHolder.getConnectionHandle() + "]");
}
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
txObject.setConnectionHolder(conHolder);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because JpaDialect [" +
getJpaDialect() + "] does not support JDBC Connection retrieval");
}
}
}
// Bind the entity manager holder to the thread.
if (txObject.isNewEntityManagerHolder()) {
TransactionSynchronizationManager.bindResource(
getEntityManagerFactory(), txObject.getEntityManagerHolder());
}
txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
}
catch (TransactionException ex) {
closeEntityManagerAfterFailedBegin(txObject);
throw ex;
}
catch (Exception ex) {
closeEntityManagerAfterFailedBegin(txObject);
throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
}
}
|
protected void doCleanupAfterCompletion(Object transaction) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
// Remove the entity manager holder from the thread.
if (txObject.isNewEntityManagerHolder()) {
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
}
txObject.getEntityManagerHolder().clear();
// Remove the JDBC connection holder from the thread, if exposed.
if (txObject.hasConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(getDataSource());
try {
getJpaDialect().releaseJdbcConnection(txObject.getConnectionHolder().getConnectionHandle(),
txObject.getEntityManagerHolder().getEntityManager());
}
catch (Exception ex) {
// Just log it, to keep a transaction-related exception.
logger.error("Could not close JDBC connection after transaction", ex);
}
}
getJpaDialect().cleanupTransaction(txObject.getTransactionData());
// Remove the entity manager holder from the thread.
if (txObject.isNewEntityManagerHolder()) {
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
if (logger.isDebugEnabled()) {
logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
}
EntityManagerFactoryUtils.closeEntityManager(em);
}
else {
logger.debug("Not closing pre-bound JPA EntityManager after transaction");
}
}
|
protected void doCommit(DefaultTransactionStatus status) {
JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Committing JPA transaction on EntityManager [" +
txObject.getEntityManagerHolder().getEntityManager() + "]");
}
try {
EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
tx.commit();
}
catch (RollbackException ex) {
if (ex.getCause() instanceof RuntimeException) {
DataAccessException dex = getJpaDialect().translateExceptionIfPossible((RuntimeException) ex.getCause());
if (dex != null) {
throw dex;
}
}
throw new TransactionSystemException("Could not commit JPA transaction", ex);
}
catch (RuntimeException ex) {
// Assumably failed to flush changes to database.
throw DataAccessUtils.translateIfNecessary(ex, getJpaDialect());
}
}
|
protected Object doGetTransaction() {
JpaTransactionObject txObject = new JpaTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
EntityManagerHolder emHolder = (EntityManagerHolder)
TransactionSynchronizationManager.getResource(getEntityManagerFactory());
if (emHolder != null) {
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound EntityManager [" +
emHolder.getEntityManager() + "] for JPA transaction");
}
txObject.setEntityManagerHolder(emHolder, false);
}
if (getDataSource() != null) {
ConnectionHolder conHolder = (ConnectionHolder)
TransactionSynchronizationManager.getResource(getDataSource());
txObject.setConnectionHolder(conHolder);
}
return txObject;
}
|
protected void doResume(Object transaction,
Object suspendedResources) {
SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
TransactionSynchronizationManager.bindResource(
getEntityManagerFactory(), resourcesHolder.getEntityManagerHolder());
if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) {
TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
}
}
|
protected void doRollback(DefaultTransactionStatus status) {
JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Rolling back JPA transaction on EntityManager [" +
txObject.getEntityManagerHolder().getEntityManager() + "]");
}
try {
EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
if (tx.isActive()) {
tx.rollback();
}
}
catch (PersistenceException ex) {
throw new TransactionSystemException("Could not roll back JPA transaction", ex);
}
finally {
if (!txObject.isNewEntityManagerHolder()) {
// Clear all pending inserts/updates/deletes in the EntityManager.
// Necessary for pre-bound EntityManagers, to avoid inconsistent state.
txObject.getEntityManagerHolder().getEntityManager().clear();
}
}
}
|
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JPA transaction on EntityManager [" +
txObject.getEntityManagerHolder().getEntityManager() + "] rollback-only");
}
txObject.setRollbackOnly();
}
|
protected Object doSuspend(Object transaction) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
txObject.setEntityManagerHolder(null, false);
EntityManagerHolder entityManagerHolder = (EntityManagerHolder)
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
txObject.setConnectionHolder(null);
ConnectionHolder connectionHolder = null;
if (getDataSource() != null && TransactionSynchronizationManager.hasResource(getDataSource())) {
connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
}
return new SuspendedResourcesHolder(entityManagerHolder, connectionHolder);
}
|
public DataSource getDataSource() {
return this.dataSource;
}
Return the JDBC DataSource that this instance manages transactions for. |
public EntityManagerFactory getEntityManagerFactory() {
return this.entityManagerFactory;
}
Return the EntityManagerFactory that this instance should manage transactions for. |
public JpaDialect getJpaDialect() {
return this.jpaDialect;
}
Return the JPA dialect to use for this transaction manager. |
public Map getJpaPropertyMap() {
return this.jpaPropertyMap;
}
Allow Map access to the JPA properties to be passed to the persistence
provider, with the option to add or override specific entries.
Useful for specifying entries directly, for example via "jpaPropertyMap[myKey]". |
public Object getResourceFactory() {
return getEntityManagerFactory();
}
|
protected boolean isExistingTransaction(Object transaction) {
return ((JpaTransactionObject) transaction).hasTransaction();
}
|
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
// If we got a TransactionAwareDataSourceProxy, we need to perform transactions
// for its underlying target DataSource, else data access code won't see
// properly exposed transactions (i.e. transactions for the target DataSource).
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
}
else {
this.dataSource = dataSource;
}
}
Set the JDBC DataSource that this instance should manage transactions for.
The DataSource should match the one used by the JPA EntityManagerFactory:
for example, you could specify the same JNDI DataSource for both.
If the EntityManagerFactory uses a known DataSource as connection factory,
the DataSource will be autodetected: You can still explictly specify the
DataSource, but you don't need to in this case.
A transactional JDBC Connection for this DataSource will be provided to
application code accessing this DataSource directly via DataSourceUtils
or JdbcTemplate. The Connection will be taken from the JPA EntityManager.
Note that you need to use a JPA dialect for a specific JPA implementation
to allow for exposing JPA transactions as JDBC transactions.
The DataSource specified here should be the target DataSource to manage
transactions for, not a TransactionAwareDataSourceProxy. Only data access
code may work with TransactionAwareDataSourceProxy, while the transaction
manager needs to work on the underlying target DataSource. If there's
nevertheless a TransactionAwareDataSourceProxy passed in, it will be
unwrapped to extract its target DataSource. |
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.entityManagerFactory = emf;
}
Set the EntityManagerFactory that this instance should manage transactions for. |
public void setJpaDialect(JpaDialect jpaDialect) {
this.jpaDialect = (jpaDialect != null ? jpaDialect : new DefaultJpaDialect());
}
Set the JPA dialect to use for this transaction manager.
Used for vendor-specific transaction management and JDBC connection exposure.
If the EntityManagerFactory uses a known JpaDialect, it will be autodetected:
You can still explictly specify the DataSource, but you don't need to in this case.
The dialect object can be used to retrieve the underlying JDBC connection
and thus allows for exposing JPA transactions as JDBC transactions. |
public void setJpaProperties(Properties jpaProperties) {
CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap);
}
Specify JPA properties, to be passed into
EntityManagerFactory.createEntityManager(Map) (if any).
Can be populated with a String "value" (parsed via PropertiesEditor)
or a "props" element in XML bean definitions. |
public void setJpaPropertyMap(Map jpaProperties) {
if (jpaProperties != null) {
this.jpaPropertyMap.putAll(jpaProperties);
}
}
|
protected boolean shouldCommitOnGlobalRollbackOnly() {
return true;
}
This implementation returns "true": a JPA commit will properly handle
transactions that have been marked rollback-only at a global level. |