.
Binds a Hibernate Session from the specified factory to the thread, potentially
allowing for one thread-bound Session per factory.
are aware of thread-bound Sessions and participate
in such transactions automatically. Using either of those or going through
is required for Hibernate
access code that needs to support this transaction handling mechanism.
Supports custom isolation levels, and timeouts that get applied as
Hibernate transaction timeouts.
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
SessionFactory. To achieve this, configure both to the same JNDI DataSource,
or preferably create the SessionFactory with LocalSessionFactoryBean and
a local DataSource (which will be autodetected by this transaction manager).
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 Hibernate Session 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 Hibernate transactions (provided that
your JDBC driver supports Savepoints). Note that Hibernate itself does not
support nested transactions! Hence, do not expect Hibernate access code to
semantically participate in a nested transaction.
Requires Hibernate 3.1 or later, as of Spring 2.5.
| Method from org.springframework.orm.hibernate3.HibernateTransactionManager Detail: |
public void afterPropertiesSet() {
if (getSessionFactory() == null) {
throw new IllegalArgumentException("Property 'sessionFactory' is required");
}
if (this.entityInterceptor instanceof String && this.beanFactory == null) {
throw new IllegalArgumentException("Property 'beanFactory' is required for 'entityInterceptorBeanName'");
}
// Check for SessionFactory's DataSource.
if (this.autodetectDataSource && getDataSource() == null) {
DataSource sfds = SessionFactoryUtils.getDataSource(getSessionFactory());
if (sfds != null) {
// Use the SessionFactory's DataSource for exposing transactions to JDBC code.
if (logger.isInfoEnabled()) {
logger.info("Using DataSource [" + sfds +
"] of Hibernate SessionFactory for HibernateTransactionManager");
}
setDataSource(sfds);
}
}
}
|
protected DataAccessException convertHibernateAccessException(HibernateException ex) {
if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
}
else if (GenericJDBCException.class.equals(ex.getClass())) {
return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
}
return SessionFactoryUtils.convertHibernateAccessException(ex);
}
Convert the given HibernateException to an appropriate exception
from the org.springframework.dao hierarchy.
Will automatically apply a specified SQLExceptionTranslator to a
Hibernate JDBCException, else rely on Hibernate's default translation. |
protected DataAccessException convertJdbcAccessException(JDBCException ex,
SQLExceptionTranslator translator) {
return translator.translate("Hibernate flushing: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
}
Convert the given Hibernate JDBCException to an appropriate exception
from the org.springframework.dao hierarchy, using the
given SQLExceptionTranslator. |
protected void doBegin(Object transaction,
TransactionDefinition definition) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
throw new 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.");
}
Session session = null;
try {
if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
Interceptor entityInterceptor = getEntityInterceptor();
Session newSession = (entityInterceptor != null ?
getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession());
if (logger.isDebugEnabled()) {
logger.debug("Opened new Session [" + SessionFactoryUtils.toString(newSession) +
"] for Hibernate transaction");
}
txObject.setSession(newSession);
}
session = txObject.getSessionHolder().getSession();
if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
// We're allowed to change the transaction settings of the JDBC Connection.
if (logger.isDebugEnabled()) {
logger.debug(
"Preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]");
}
Connection con = session.connection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
}
else {
// Not allowed to change the transaction settings of the JDBC Connection.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
// We should set a specific isolation level but are not allowed to...
throw new InvalidIsolationLevelException(
"HibernateTransactionManager is not allowed to support custom isolation levels: " +
"make sure that its 'prepareConnection' flag is on (the default) and that the " +
"Hibernate connection release mode is set to 'on_close' (SpringTransactionFactory's default). " +
"Make sure that your LocalSessionFactoryBean actually uses SpringTransactionFactory: Your " +
"Hibernate properties should *not* include a 'hibernate.transaction.factory_class' property!");
}
if (logger.isDebugEnabled()) {
logger.debug(
"Not preparing JDBC Connection of Hibernate Session [" + SessionFactoryUtils.toString(session) + "]");
}
}
if (definition.isReadOnly() && txObject.isNewSession()) {
// Just set to NEVER in case of a new Session for this transaction.
session.setFlushMode(FlushMode.NEVER);
}
if (!definition.isReadOnly() && !txObject.isNewSession()) {
// We need AUTO or COMMIT for a non-read-only transaction.
FlushMode flushMode = session.getFlushMode();
if (flushMode.lessThan(FlushMode.COMMIT)) {
session.setFlushMode(FlushMode.AUTO);
txObject.getSessionHolder().setPreviousFlushMode(flushMode);
}
}
Transaction hibTx = null;
// Register transaction timeout.
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
// Use Hibernate's own transaction timeout mechanism on Hibernate 3.1
// Applies to all statements, also to inserts, updates and deletes!
hibTx = session.getTransaction();
hibTx.setTimeout(timeout);
hibTx.begin();
}
else {
// Open a plain Hibernate transaction without specified timeout.
hibTx = session.beginTransaction();
}
// Add the Hibernate transaction to the session holder.
txObject.getSessionHolder().setTransaction(hibTx);
// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
if (getDataSource() != null) {
Connection con = session.connection();
ConnectionHolder conHolder = new ConnectionHolder(con);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
conHolder.setTimeoutInSeconds(timeout);
}
if (logger.isDebugEnabled()) {
logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]");
}
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
txObject.setConnectionHolder(conHolder);
}
// Bind the session holder to the thread.
if (txObject.isNewSessionHolder()) {
TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
}
txObject.getSessionHolder().setSynchronizedWithTransaction(true);
}
catch (Exception ex) {
if (txObject.isNewSession()) {
try {
if (session.getTransaction().isActive()) {
session.getTransaction().rollback();
}
}
catch (Throwable ex2) {
logger.debug("Could not rollback Session after failed transaction begin", ex);
}
finally {
SessionFactoryUtils.closeSession(session);
}
}
throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
}
}
|
protected void doCleanupAfterCompletion(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
// Remove the session holder from the thread.
if (txObject.isNewSessionHolder()) {
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
// Remove the JDBC connection holder from the thread, if exposed.
if (getDataSource() != null) {
TransactionSynchronizationManager.unbindResource(getDataSource());
}
Session session = txObject.getSessionHolder().getSession();
if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
// We're running with connection release mode "on_close": We're able to reset
// the isolation level and/or read-only flag of the JDBC Connection here.
// Else, we need to rely on the connection pool to perform proper cleanup.
try {
Connection con = session.connection();
DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
}
catch (HibernateException ex) {
logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
}
}
if (txObject.isNewSession()) {
if (logger.isDebugEnabled()) {
logger.debug("Closing Hibernate Session [" + SessionFactoryUtils.toString(session) +
"] after transaction");
}
SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not closing pre-bound Hibernate Session [" +
SessionFactoryUtils.toString(session) + "] after transaction");
}
if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
}
if (!this.hibernateManagedSession) {
session.disconnect();
}
}
txObject.getSessionHolder().clear();
}
|
protected void doCommit(DefaultTransactionStatus status) {
HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Committing Hibernate transaction on Session [" +
SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "]");
}
try {
txObject.getSessionHolder().getTransaction().commit();
}
catch (org.hibernate.TransactionException ex) {
// assumably from commit call to the underlying JDBC connection
throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
}
catch (HibernateException ex) {
// assumably failed to flush changes to database
throw convertHibernateAccessException(ex);
}
}
|
protected Object doGetTransaction() {
HibernateTransactionObject txObject = new HibernateTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
if (sessionHolder != null) {
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound Session [" +
SessionFactoryUtils.toString(sessionHolder.getSession()) + "] for Hibernate transaction");
}
txObject.setSessionHolder(sessionHolder);
}
else if (this.hibernateManagedSession) {
try {
Session session = getSessionFactory().getCurrentSession();
if (logger.isDebugEnabled()) {
logger.debug("Found Hibernate-managed Session [" +
SessionFactoryUtils.toString(session) + "] for Spring-managed transaction");
}
txObject.setExistingSession(session);
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException(
"Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
}
}
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;
if (TransactionSynchronizationManager.hasResource(getSessionFactory())) {
// From non-transactional code running in active transaction synchronization
// - > can be safely removed, will be closed on transaction completion.
TransactionSynchronizationManager.unbindResource(getSessionFactory());
}
TransactionSynchronizationManager.bindResource(getSessionFactory(), resourcesHolder.getSessionHolder());
if (getDataSource() != null) {
TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
}
}
|
protected void doRollback(DefaultTransactionStatus status) {
HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Rolling back Hibernate transaction on Session [" +
SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "]");
}
try {
txObject.getSessionHolder().getTransaction().rollback();
}
catch (org.hibernate.TransactionException ex) {
throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
}
catch (HibernateException ex) {
// Shouldn't really happen, as a rollback doesn't cause a flush.
throw convertHibernateAccessException(ex);
}
finally {
if (!txObject.isNewSession() && !this.hibernateManagedSession) {
// Clear all pending inserts/updates/deletes in the Session.
// Necessary for pre-bound Sessions, to avoid inconsistent state.
txObject.getSessionHolder().getSession().clear();
}
}
}
|
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting Hibernate transaction on Session [" +
SessionFactoryUtils.toString(txObject.getSessionHolder().getSession()) + "] rollback-only");
}
txObject.setRollbackOnly();
}
|
protected Object doSuspend(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
txObject.setSessionHolder(null);
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
txObject.setConnectionHolder(null);
ConnectionHolder connectionHolder = null;
if (getDataSource() != null) {
connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
}
return new SuspendedResourcesHolder(sessionHolder, connectionHolder);
}
|
public DataSource getDataSource() {
return this.dataSource;
}
Return the JDBC DataSource that this instance manages transactions for. |
protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
if (this.defaultJdbcExceptionTranslator == null) {
if (getDataSource() != null) {
this.defaultJdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(getDataSource());
}
else {
this.defaultJdbcExceptionTranslator = SessionFactoryUtils.newJdbcExceptionTranslator(getSessionFactory());
}
}
return this.defaultJdbcExceptionTranslator;
}
|
public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
if (this.entityInterceptor instanceof Interceptor) {
return (Interceptor) entityInterceptor;
}
else if (this.entityInterceptor instanceof String) {
if (this.beanFactory == null) {
throw new IllegalStateException("Cannot get entity interceptor via bean name if no bean factory set");
}
String beanName = (String) this.entityInterceptor;
return (Interceptor) this.beanFactory.getBean(beanName, Interceptor.class);
}
else {
return null;
}
}
Return the current Hibernate entity interceptor, or null if none.
Resolves an entity interceptor bean name via the bean factory,
if necessary. |
public SQLExceptionTranslator getJdbcExceptionTranslator() {
return this.jdbcExceptionTranslator;
}
Return the JDBC exception translator for this transaction manager, if any. |
public Object getResourceFactory() {
return getSessionFactory();
}
|
public SessionFactory getSessionFactory() {
return this.sessionFactory;
}
Return the SessionFactory that this instance should manage transactions for. |
protected boolean isExistingTransaction(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
return (txObject.hasSpringManagedTransaction() ||
(this.hibernateManagedSession && txObject.hasHibernateManagedTransaction()));
}
|
protected boolean isSameConnectionForEntireSession(Session session) {
if (!(session instanceof SessionImpl)) {
// The best we can do is to assume we're safe.
return true;
}
ConnectionReleaseMode releaseMode = ((SessionImpl) session).getConnectionReleaseMode();
return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
}
Return whether the given Hibernate Session will always hold the same
JDBC Connection. This is used to check whether the transaction manager
can safely prepare and clean up the JDBC Connection used for a transaction.
Default implementation checks the Session's connection release mode
to be "on_close". Unfortunately, this requires casting to SessionImpl,
as of Hibernate 3.1. If that cast doesn't work, we'll simply assume
we're safe and return true. |
protected void prepareForCommit(DefaultTransactionStatus status) {
if (this.earlyFlushBeforeCommit && status.isNewTransaction()) {
HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
Session session = txObject.getSessionHolder().getSession();
if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
logger.debug("Performing an early flush for Hibernate transaction");
try {
session.flush();
}
catch (HibernateException ex) {
throw convertHibernateAccessException(ex);
}
finally {
session.setFlushMode(FlushMode.NEVER);
}
}
}
}
|
public void setAutodetectDataSource(boolean autodetectDataSource) {
this.autodetectDataSource = autodetectDataSource;
}
Set whether to autodetect a JDBC DataSource used by the Hibernate SessionFactory,
if set via LocalSessionFactoryBean's setDataSource. Default is "true".
Can be turned off to deliberately ignore an available DataSource, in order
to not expose Hibernate transactions as JDBC transactions for that DataSource. |
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
The bean factory just needs to be known for resolving entity interceptor
bean names. It does not need to be set for any other mode of operation. |
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 Hibernate SessionFactory:
for example, you could specify the same JNDI DataSource for both.
If the SessionFactory was configured with LocalDataSourceConnectionProvider,
i.e. by Spring's LocalSessionFactoryBean with a specified "dataSource",
the DataSource will be auto-detected: 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 Hibernate Session.
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 setEarlyFlushBeforeCommit(boolean earlyFlushBeforeCommit) {
this.earlyFlushBeforeCommit = earlyFlushBeforeCommit;
}
Set whether to perform an early flush before proceeding with a commit.
Default is "false", performing an implicit flush as part of the actual
commit step. Switch this to "true" in order to enforce an explicit early
flush right before the actual commit step.
An early flush happens before the before-commit synchronization phase,
making flushed state visible to beforeCommit callbacks of registered
org.springframework.transaction.support.TransactionSynchronization
objects. Such explicit flush behavior is consistent with Spring-driven
flushing in a JTA transaction environment, so may also get enforced for
consistency with JTA transaction behavior. |
public void setEntityInterceptor(Interceptor entityInterceptor) {
this.entityInterceptor = entityInterceptor;
}
Set a Hibernate entity interceptor that allows to inspect and change
property values before writing to and reading from the database.
Will get applied to any new Session created by this transaction manager.
Such an interceptor can either be set at the SessionFactory level,
i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
to avoid repeated configuration and guarantee consistent behavior in transactions. |
public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
this.entityInterceptor = entityInterceptorBeanName;
}
Set the bean name of a Hibernate entity interceptor that allows to inspect
and change property values before writing to and reading from the database.
Will get applied to any new Session created by this transaction manager.
Requires the bean factory to be known, to be able to resolve the bean
name to an interceptor instance on session creation. Typically used for
prototype interceptors, i.e. a new interceptor instance per session.
Can also be used for shared interceptor instances, but it is recommended
to set the interceptor reference directly in such a scenario. |
public void setHibernateManagedSession(boolean hibernateManagedSession) {
this.hibernateManagedSession = hibernateManagedSession;
}
Set whether to operate on a Hibernate-managed Session instead of a
Spring-managed Session, that is, whether to obtain the Session through
Hibernate's org.hibernate.SessionFactory#getCurrentSession()
instead of org.hibernate.SessionFactory#openSession() (with a Spring
org.springframework.transaction.support.TransactionSynchronizationManager
check preceding it).
Default is "false", i.e. using a Spring-managed Session: taking the current
thread-bound Session if available (e.g. in an Open-Session-in-View scenario),
creating a new Session for the current transaction otherwise.
Switch this flag to "true" in order to enforce use of a Hibernate-managed Session.
Note that this requires org.hibernate.SessionFactory#getCurrentSession()
to always return a proper Session when called for a Spring-managed transaction;
transaction begin will fail if the getCurrentSession() call fails.
This mode will typically be used in combination with a custom Hibernate
org.hibernate.context.CurrentSessionContext implementation that stores
Sessions in a place other than Spring's TransactionSynchronizationManager.
It may also be used in combination with Spring's Open-Session-in-View support
(using Spring's default SpringSessionContext ), in which case it subtly
differs from the Spring-managed Session mode: The pre-bound Session will not
receive a clear() call (on rollback) or a disconnect()
call (on transaction completion) in such a scenario; this is rather left up
to a custom CurrentSessionContext implementation (if desired). |
public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
this.jdbcExceptionTranslator = jdbcExceptionTranslator;
}
Set the JDBC exception translator for this transaction manager.
Applied to any SQLException root cause of a Hibernate JDBCException that
is thrown on flush, overriding Hibernate's default SQLException translation
(which is based on Hibernate's Dialect for a specific target database). |
public void setPrepareConnection(boolean prepareConnection) {
this.prepareConnection = prepareConnection;
}
Set whether to prepare the underlying JDBC Connection of a transactional
Hibernate Session, that is, whether to apply a transaction-specific
isolation level and/or the transaction's read-only flag to the underlying
JDBC Connection.
Default is "true". If you turn this flag off, the transaction manager
will not support per-transaction isolation levels anymore. It will not
call Connection.setReadOnly(true) for read-only transactions
anymore either. If this flag is turned off, no cleanup of a JDBC Connection
is required after a transaction, since no Connection settings will get modified.
It is recommended to turn this flag off if running against Hibernate 3.1
and a connection pool that does not reset connection settings (for example,
Jakarta Commons DBCP). To keep this flag turned on, you can set the
"hibernate.connection.release_mode" property to "on_close" instead,
or consider using a smarter connection pool (for example, C3P0). |
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
Set the SessionFactory that this instance should manage transactions for. |