HHH-3309 : AbstractLazyInitializer + serialization

git-svn-id: https://svn.jboss.org/repos/hibernate/core/branches/Branch_3_2@14900 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2008-07-09 03:38:33 +00:00
parent 1295812ac5
commit 437b0cafea
9 changed files with 503 additions and 189 deletions

View File

@ -8,6 +8,7 @@
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ConcurrentModificationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -298,28 +299,45 @@ public void closeStatements() {
releasing = true;
try {
if (batchUpdate!=null) batchUpdate.close();
if ( batchUpdate != null ) {
batchUpdate.close();
}
}
catch (SQLException sqle) {
catch ( SQLException sqle ) {
//no big deal
log.warn("Could not close a JDBC prepared statement", sqle);
log.warn( "Could not close a JDBC prepared statement", sqle );
}
batchUpdate=null;
batchUpdateSQL=null;
batchUpdate = null;
batchUpdateSQL = null;
Iterator iter = resultSetsToClose.iterator();
while ( iter.hasNext() ) {
try {
logCloseResults();
( (ResultSet) iter.next() ).close();
( ( ResultSet ) iter.next() ).close();
}
catch (SQLException e) {
catch ( SQLException e ) {
// no big deal
log.warn("Could not close a JDBC result set", e);
log.warn( "Could not close a JDBC result set", e );
}
catch (Throwable e) {
// sybase driver (jConnect) throwing NPE here in certain cases
log.warn("Could not close a JDBC result set", e);
catch ( ConcurrentModificationException e ) {
// this has been shown to happen occasionally in rare cases
// when using a transaction manager + transaction-timeout
// where the timeout calls back through Hibernate's
// registered transaction synchronization on a separate
// "reaping" thread. In cases where that reaping thread
// executes through this block at the same time the main
// application thread does we can get into situations where
// these CMEs occur. And though it is not "allowed" per-se,
// the end result without handling it specifically is infinite
// looping. So here, we simply break the loop
log.info( "encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" );
break;
}
catch ( Throwable e ) {
// sybase driver (jConnect) throwing NPE here in certain
// cases, but we'll just handle the general "unexpected" case
log.warn( "Could not close a JDBC result set", e );
}
}
resultSetsToClose.clear();
@ -327,11 +345,16 @@ public void closeStatements() {
iter = statementsToClose.iterator();
while ( iter.hasNext() ) {
try {
closeQueryStatement( (PreparedStatement) iter.next() );
closeQueryStatement( ( PreparedStatement ) iter.next() );
}
catch (SQLException e) {
catch ( ConcurrentModificationException e ) {
// see explanation above...
log.info( "encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" );
break;
}
catch ( SQLException e ) {
// no big deal
log.warn("Could not close a JDBC statement", e);
log.warn( "Could not close a JDBC statement", e );
}
}
statementsToClose.clear();

View File

@ -24,7 +24,13 @@ public abstract class AbstractLazyInitializer implements LazyInitializer {
private Serializable id;
private transient SessionImplementor session;
private boolean unwrap;
/**
* For serialization from the non-pojo initializers (HHH-3309)
*/
protected AbstractLazyInitializer() {
}
protected AbstractLazyInitializer(String entityName, Serializable id, SessionImplementor session) {
this.id = id;
this.session = session;

View File

@ -2,6 +2,8 @@
package org.hibernate.tool.hbm2ddl;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
@ -12,10 +14,12 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.NamingStrategy;
import org.hibernate.cfg.Settings;
import org.hibernate.dialect.Dialect;
import org.hibernate.pretty.DDLFormatter;
import org.hibernate.util.ReflectHelper;
/**
@ -31,6 +35,10 @@ public class SchemaUpdate {
private Configuration configuration;
private Dialect dialect;
private List exceptions;
private boolean haltOnError = false;
private boolean format = true;
private String outputFile = null;
private String delimiter;
public SchemaUpdate(Configuration cfg) throws HibernateException {
this( cfg, cfg.getProperties() );
@ -116,6 +124,7 @@ public void execute(boolean script, boolean doUpdate) {
Connection connection = null;
Statement stmt = null;
Writer outputFileWriter = null;
exceptions.clear();
@ -137,20 +146,36 @@ public void execute(boolean script, boolean doUpdate) {
log.info( "updating schema" );
if ( outputFile != null ) {
log.info( "writing generated schema to file: " + outputFile );
outputFileWriter = new FileWriter( outputFile );
}
String[] createSQL = configuration.generateSchemaUpdateScript( dialect, meta );
for ( int j = 0; j < createSQL.length; j++ ) {
final String sql = createSQL[j];
String formatted = format( sql );
try {
if ( delimiter != null ) {
formatted += delimiter;
}
if ( script ) {
System.out.println( sql );
System.out.println( formatted );
}
if ( outputFile != null ) {
outputFileWriter.write( formatted + "\n" );
}
if ( doUpdate ) {
log.debug( sql );
stmt.executeUpdate( sql );
stmt.executeUpdate( formatted );
}
}
catch ( SQLException e ) {
if ( haltOnError ) {
throw new JDBCException( "Error during DDL export", e );
}
exceptions.add( e );
log.error( "Unsuccessful: " + sql );
log.error( e.getMessage() );
@ -176,7 +201,15 @@ public void execute(boolean script, boolean doUpdate) {
exceptions.add( e );
log.error( "Error closing connection", e );
}
try {
if( outputFileWriter != null ) {
outputFileWriter.close();
}
}
catch(Exception e) {
exceptions.add(e);
log.error( "Error closing connection", e );
}
}
}
@ -188,4 +221,26 @@ public void execute(boolean script, boolean doUpdate) {
public List getExceptions() {
return exceptions;
}
public void setHaltOnError(boolean haltOnError) {
this.haltOnError = haltOnError;
}
public void setFormat(boolean format) {
this.format = format;
}
public void setOutputFile(String outputFile) {
this.outputFile = outputFile;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
private String format(String sql) {
return format ?
new DDLFormatter( sql ).format() :
sql;
}
}

View File

@ -47,9 +47,13 @@ public class SchemaUpdateTask extends MatchingTask {
private List fileSets = new LinkedList();
private File propertiesFile = null;
private File configurationFile = null;
private File outputFile = null;
private boolean quiet = false;
private boolean text = true;
private boolean haltOnError = false;
private String delimiter = null;
private String namingStrategy = null;
public void addFileset(FileSet set) {
fileSets.add(set);
@ -174,11 +178,39 @@ private SchemaUpdate getSchemaUpdate(Configuration cfg) throws HibernateExceptio
properties.load( new FileInputStream(propertiesFile) );
}
cfg.setProperties(properties);
return new SchemaUpdate(cfg);
SchemaUpdate su = new SchemaUpdate(cfg);
su.setOutputFile( outputFile.getPath() );
su.setDelimiter(delimiter);
su.setHaltOnError(haltOnError);
return su;
}
public void setNamingStrategy(String namingStrategy) {
this.namingStrategy = namingStrategy;
}
public File getOutputFile() {
return outputFile;
}
public void setOutputFile(File outputFile) {
this.outputFile = outputFile;
}
public boolean isHaltOnError() {
return haltOnError;
}
public void setHaltOnError(boolean haltOnError) {
this.haltOnError = haltOnError;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
}

View File

@ -1,8 +1,29 @@
//$Id$
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.transaction;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
@ -11,7 +32,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.Transaction;
import org.hibernate.TransactionException;
@ -19,77 +40,58 @@
import org.hibernate.util.JTAHelper;
/**
* Implements a basic transaction strategy for JTA transactions. Instances check to
* see if there is an existing JTA transaction. If none exists, a new transaction
* is started. If one exists, all work is done in the existing context. The
* following properties are used to locate the underlying <tt>UserTransaction</tt>:
* <br><br>
* <table>
* <tr><td><tt>hibernate.jndi.url</tt></td><td>JNDI initial context URL</td></tr>
* <tr><td><tt>hibernate.jndi.class</tt></td><td>JNDI provider class</td></tr>
* <tr><td><tt>jta.UserTransaction</tt></td><td>JNDI name</td></tr>
* </table>
* {@link Transaction} implementation based on transaction management through
* a JTA {@link UserTransaction}. Similar to {@link CMTTransaction}, except
* here we are actually managing the transactions through the Hibernate
* transaction mechanism.
*
* @author Gavin King
* @author Steve Ebersole
* @author Les Hazlewood
*/
public class JTATransaction implements Transaction {
private static final Log log = LogFactory.getLog(JTATransaction.class);
private static final Log log = LogFactory.getLog( JTATransaction.class );
private final JDBCContext jdbcContext;
private final TransactionFactory.Context transactionContext;
private UserTransaction ut;
private UserTransaction userTransaction;
private boolean newTransaction;
private boolean begun;
private boolean commitFailed;
private boolean commitSucceeded;
private boolean callback;
public JTATransaction(
InitialContext context,
String utName,
JDBCContext jdbcContext,
TransactionFactory.Context transactionContext
) {
UserTransaction userTransaction,
JDBCContext jdbcContext,
TransactionFactory.Context transactionContext) {
this.jdbcContext = jdbcContext;
this.transactionContext = transactionContext;
log.debug("Looking for UserTransaction under: " + utName);
try {
ut = (UserTransaction) context.lookup(utName);
}
catch (NamingException ne) {
log.error("Could not find UserTransaction in JNDI", ne);
throw new TransactionException("Could not find UserTransaction in JNDI: ", ne);
}
if (ut==null) {
throw new AssertionFailure("A naming service lookup returned null");
}
log.debug("Obtained UserTransaction");
this.userTransaction = userTransaction;
}
public void begin() throws HibernateException {
if (begun) {
if ( begun ) {
return;
}
if (commitFailed) {
throw new TransactionException("cannot re-start transaction after failed commit");
if ( commitFailed ) {
throw new TransactionException( "cannot re-start transaction after failed commit" );
}
log.debug("begin");
log.debug( "begin" );
try {
newTransaction = ut.getStatus() == Status.STATUS_NO_TRANSACTION;
if (newTransaction) {
ut.begin();
log.debug("Began a new JTA transaction");
newTransaction = userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION;
if ( newTransaction ) {
userTransaction.begin();
log.debug( "Began a new JTA transaction" );
}
}
catch (Exception e) {
log.error("JTA transaction begin failed", e);
throw new TransactionException("JTA transaction begin failed", e);
catch ( Exception e ) {
log.error( "JTA transaction begin failed", e );
throw new TransactionException( "JTA transaction begin failed", e );
}
/*if (newTransaction) {
@ -102,10 +104,10 @@ public void begin() throws HibernateException {
boolean synchronization = jdbcContext.registerSynchronizationIfPossible();
if ( !newTransaction && !synchronization ) {
log.warn("You should set hibernate.transaction.manager_lookup_class if cache is enabled");
log.warn( "You should set hibernate.transaction.manager_lookup_class if cache is enabled" );
}
if (!synchronization) {
if ( !synchronization ) {
//if we could not register a synchronization,
//do the before/after completion callbacks
//ourself (but we need to let jdbcContext
@ -117,40 +119,40 @@ public void begin() throws HibernateException {
begun = true;
commitSucceeded = false;
jdbcContext.afterTransactionBegin(this);
jdbcContext.afterTransactionBegin( this );
}
public void commit() throws HibernateException {
if (!begun) {
throw new TransactionException("Transaction not successfully started");
if ( !begun ) {
throw new TransactionException( "Transaction not successfully started" );
}
log.debug("commit");
log.debug( "commit" );
boolean flush = !transactionContext.isFlushModeNever()
&& ( callback || !transactionContext.isFlushBeforeCompletionEnabled() );
&& ( callback || !transactionContext.isFlushBeforeCompletionEnabled() );
if (flush) {
if ( flush ) {
transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback()
}
if (callback && newTransaction) {
jdbcContext.beforeTransactionCompletion(this);
if ( callback && newTransaction ) {
jdbcContext.beforeTransactionCompletion( this );
}
closeIfRequired();
if (newTransaction) {
if ( newTransaction ) {
try {
ut.commit();
userTransaction.commit();
commitSucceeded = true;
log.debug("Committed JTA UserTransaction");
log.debug( "Committed JTA UserTransaction" );
}
catch (Exception e) {
catch ( Exception e ) {
commitFailed = true; // so the transaction is already rolled back, by JTA spec
log.error("JTA commit failed", e);
throw new TransactionException("JTA commit failed: ", e);
log.error( "JTA commit failed", e );
throw new TransactionException( "JTA commit failed: ", e );
}
finally {
afterCommitRollback();
@ -167,11 +169,11 @@ public void commit() throws HibernateException {
}
public void rollback() throws HibernateException {
if (!begun && !commitFailed) {
throw new TransactionException("Transaction not successfully started");
if ( !begun && !commitFailed ) {
throw new TransactionException( "Transaction not successfully started" );
}
log.debug("rollback");
log.debug( "rollback" );
/*if (!synchronization && newTransaction && !commitFailed) {
jdbcContext.beforeTransactionCompletion(this);
@ -180,26 +182,26 @@ public void rollback() throws HibernateException {
try {
closeIfRequired();
}
catch (Exception e) {
log.error("could not close session during rollback", e);
catch ( Exception e ) {
log.error( "could not close session during rollback", e );
//swallow it, and continue to roll back JTA transaction
}
try {
if (newTransaction) {
if (!commitFailed) {
ut.rollback();
log.debug("Rolled back JTA UserTransaction");
if ( newTransaction ) {
if ( !commitFailed ) {
userTransaction.rollback();
log.debug( "Rolled back JTA UserTransaction" );
}
}
else {
ut.setRollbackOnly();
log.debug("set JTA UserTransaction to rollback only");
userTransaction.setRollbackOnly();
log.debug( "set JTA UserTransaction to rollback only" );
}
}
catch (Exception e) {
log.error("JTA rollback failed", e);
throw new TransactionException("JTA rollback failed", e);
catch ( Exception e ) {
log.error( "JTA rollback failed", e );
throw new TransactionException( "JTA rollback failed", e );
}
finally {
afterCommitRollback();
@ -209,28 +211,28 @@ public void rollback() throws HibernateException {
private static final int NULL = Integer.MIN_VALUE;
private void afterCommitRollback() throws TransactionException {
begun = false;
if (callback) { // this method is a noop if there is a Synchronization!
if ( callback ) { // this method is a noop if there is a Synchronization!
if (!newTransaction) {
log.warn("You should set hibernate.transaction.manager_lookup_class if cache is enabled");
if ( !newTransaction ) {
log.warn( "You should set hibernate.transaction.manager_lookup_class if cache is enabled" );
}
int status=NULL;
int status = NULL;
try {
status = ut.getStatus();
status = userTransaction.getStatus();
}
catch (Exception e) {
log.error("Could not determine transaction status after commit", e);
throw new TransactionException("Could not determine transaction status after commit", e);
catch ( Exception e ) {
log.error( "Could not determine transaction status after commit", e );
throw new TransactionException( "Could not determine transaction status after commit", e );
}
finally {
/*if (status!=Status.STATUS_COMMITTED && status!=Status.STATUS_ROLLEDBACK) {
log.warn("Transaction not complete - you should set hibernate.transaction.manager_lookup_class if cache is enabled");
//throw exception??
}*/
jdbcContext.afterTransactionCompletion(status==Status.STATUS_COMMITTED, this);
jdbcContext.afterTransactionCompletion( status == Status.STATUS_COMMITTED, this );
}
}
@ -243,17 +245,17 @@ public boolean wasRolledBack() throws TransactionException {
final int status;
try {
status = ut.getStatus();
status = userTransaction.getStatus();
}
catch (SystemException se) {
log.error("Could not determine transaction status", se);
throw new TransactionException("Could not determine transaction status", se);
catch ( SystemException se ) {
log.error( "Could not determine transaction status", se );
throw new TransactionException( "Could not determine transaction status", se );
}
if (status==Status.STATUS_UNKNOWN) {
throw new TransactionException("Could not determine transaction status");
if ( status == Status.STATUS_UNKNOWN ) {
throw new TransactionException( "Could not determine transaction status" );
}
else {
return JTAHelper.isRollback(status);
return JTAHelper.isRollback( status );
}
}
@ -263,50 +265,52 @@ public boolean wasCommitted() throws TransactionException {
final int status;
try {
status = ut.getStatus();
status = userTransaction.getStatus();
}
catch (SystemException se) {
log.error("Could not determine transaction status", se);
throw new TransactionException("Could not determine transaction status: ", se);
catch ( SystemException se ) {
log.error( "Could not determine transaction status", se );
throw new TransactionException( "Could not determine transaction status: ", se );
}
if (status==Status.STATUS_UNKNOWN) {
throw new TransactionException("Could not determine transaction status");
if ( status == Status.STATUS_UNKNOWN ) {
throw new TransactionException( "Could not determine transaction status" );
}
else {
return status==Status.STATUS_COMMITTED;
return status == Status.STATUS_COMMITTED;
}
}
public boolean isActive() throws TransactionException {
if (!begun || commitFailed || commitSucceeded) return false;
if ( !begun || commitFailed || commitSucceeded ) {
return false;
}
final int status;
try {
status = ut.getStatus();
status = userTransaction.getStatus();
}
catch (SystemException se) {
log.error("Could not determine transaction status", se);
throw new TransactionException("Could not determine transaction status: ", se);
catch ( SystemException se ) {
log.error( "Could not determine transaction status", se );
throw new TransactionException( "Could not determine transaction status: ", se );
}
if (status==Status.STATUS_UNKNOWN) {
throw new TransactionException("Could not determine transaction status");
if ( status == Status.STATUS_UNKNOWN ) {
throw new TransactionException( "Could not determine transaction status" );
}
else {
return status==Status.STATUS_ACTIVE;
return status == Status.STATUS_ACTIVE;
}
}
public void registerSynchronization(Synchronization sync) throws HibernateException {
if (getTransactionManager()==null) {
throw new IllegalStateException("JTA TransactionManager not available");
if ( getTransactionManager() == null ) {
throw new IllegalStateException( "JTA TransactionManager not available" );
}
else {
try {
getTransactionManager().getTransaction().registerSynchronization(sync);
getTransactionManager().getTransaction().registerSynchronization( sync );
}
catch (Exception e) {
throw new TransactionException("could not register synchronization", e);
catch ( Exception e ) {
throw new TransactionException( "could not register synchronization", e );
}
}
}
@ -316,8 +320,8 @@ private TransactionManager getTransactionManager() {
}
private void closeIfRequired() throws HibernateException {
boolean close = callback &&
transactionContext.shouldAutoClose() &&
boolean close = callback &&
transactionContext.shouldAutoClose() &&
!transactionContext.isClosed();
if ( close ) {
transactionContext.managedClose();
@ -326,14 +330,14 @@ private void closeIfRequired() throws HibernateException {
public void setTimeout(int seconds) {
try {
ut.setTransactionTimeout(seconds);
userTransaction.setTransactionTimeout( seconds );
}
catch (SystemException se) {
throw new TransactionException("could not set transaction timeout", se);
catch ( SystemException se ) {
throw new TransactionException( "could not set transaction timeout", se );
}
}
protected UserTransaction getUserTransaction() {
return ut;
return userTransaction;
}
}

View File

@ -1,4 +1,27 @@
//$Id$
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.transaction;
import java.util.Properties;
@ -20,61 +43,184 @@
import org.hibernate.util.JTAHelper;
/**
* Factory for <tt>JTATransaction</tt>.
* Factory for {@link JTATransaction} instances.
* <p/>
* To be completely accurate to the JTA spec, JTA implementations should
* publish their contextual {@link UserTransaction} reference into JNDI.
* However, in practice there are quite a few <tt>stand-alone</tt>
* implementations intended for use outside of J2EE/JEE containers and
* which therefore do not publish their {@link UserTransaction} references
* into JNDI but which otherwise follow the aspects of the JTA specification.
* This {@link TransactionFactory} implementation can support both models.
* <p/>
* For complete JTA implementations (including dependence on JNDI), the
* {@link UserTransaction} reference is obtained by a call to
* {@link #resolveInitialContext}. Hibernate will then attempt to locate the
* {@link UserTransaction} within this resolved
* {@link InitialContext} based on the namespace returned by
* {@link #resolveUserTransactionName}.
* <p/>
* For the so-called <tt>stand-alone</tt> implementations, we do not care at
* all about the JNDI aspects just described. Here, the implementation would
* have a specific manner to obtain a reference to its contextual
* {@link UserTransaction}; usually this would be a static code reference, but
* again it varies. Anyway, for each implementation the integration would need
* to override the {@link #getUserTransaction} method and return the appropriate
* thing.
*
* @see JTATransaction
* @author Gavin King
* @author Steve Ebersole
* @author Les Hazlewood
*/
public class JTATransactionFactory implements TransactionFactory {
public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
private static final Log log = LogFactory.getLog(JTATransactionFactory.class);
private static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
protected InitialContext context;
protected String utName;
protected InitialContext initialContext;
protected String userTransactionName;
/**
* Configure this transaction factory. Specifically here we are attempting to
* resolve both an {@link #getInitialContext InitialContext} as well as the
* {@link #getUserTransactionName() JNDI namespace} for the {@link UserTransaction}.
*
* @param props The configuration properties
*
* @exception HibernateException
*/
public void configure(Properties props) throws HibernateException {
this.initialContext = resolveInitialContext( props );
this.userTransactionName = resolveUserTransactionName( props );
log.trace( "Configured JTATransactionFactory to use [" + userTransactionName + "] for UserTransaction JDNI namespace" );
}
/**
* Given the lot of Hibernate configuration properties, resolve appropriate
* reference to JNDI {@link InitialContext}.
* <p/>
* In general, the properties in which we are interested here all begin with
* <tt>hibernate.jndi</tt>. Especially important depending on your
* environment are {@link Environment#JNDI_URL hibernate.jndi.url} and
* {@link Environment#JNDI_CLASS hibernate.jndi.class}
*
* @param properties The Hibernate config properties.
* @return The resolved InitialContext.
*/
protected final InitialContext resolveInitialContext(Properties properties) {
try {
context = NamingHelper.getInitialContext(props);
return NamingHelper.getInitialContext( properties );
}
catch (NamingException ne) {
log.error("Could not obtain initial context", ne);
catch ( NamingException ne ) {
throw new HibernateException( "Could not obtain initial context", ne );
}
}
utName = props.getProperty(Environment.USER_TRANSACTION);
if (utName==null) {
TransactionManagerLookup lookup = TransactionManagerLookupFactory.getTransactionManagerLookup(props);
if (lookup!=null) utName = lookup.getUserTransactionName();
/**
* Given the lot of Hibernate configuration properties, resolve appropriate
* JNDI namespace to use for {@link UserTransaction} resolution.
* <p/>
* We determine the namespace to use by<ol>
* <li>Any specified {@link Environment#USER_TRANSACTION jta.UserTransaction} config property</li>
* <li>If a {@link TransactionManagerLookup} was indicated, use its
* {@link TransactionManagerLookup#getUserTransactionName}</li>
* <li>finally, as a last resort, we use {@link #DEFAULT_USER_TRANSACTION_NAME}</li>
* </ol>
*
* @param properties The Hibernate config properties.
* @return The resolved {@link UserTransaction} namespace
*/
protected final String resolveUserTransactionName(Properties properties) {
String utName = properties.getProperty( Environment.USER_TRANSACTION );
if ( utName == null ) {
TransactionManagerLookup lookup = TransactionManagerLookupFactory.getTransactionManagerLookup( properties );
if ( lookup != null ) {
utName = lookup.getUserTransactionName();
}
}
if (utName==null) utName = DEFAULT_USER_TRANSACTION_NAME;
return utName == null ? DEFAULT_USER_TRANSACTION_NAME : utName;
}
/**
* {@inheritDoc}
*/
public Transaction createTransaction(JDBCContext jdbcContext, Context transactionContext)
throws HibernateException {
return new JTATransaction(context, utName, jdbcContext, transactionContext);
throws HibernateException {
UserTransaction ut = getUserTransaction();
return new JTATransaction( ut, jdbcContext, transactionContext );
}
/**
* Get the {@link UserTransaction} reference.
*
* @return The appropriate {@link UserTransaction} reference.
*/
protected UserTransaction getUserTransaction() {
log.trace( "Attempting to locate UserTransaction via JNDI [" + getUserTransactionName() + "]" );
try {
UserTransaction ut = ( UserTransaction ) getInitialContext().lookup( getUserTransactionName() );
if ( ut == null ) {
throw new TransactionException( "Naming service lookup for UserTransaction returned null [" + getUserTransactionName() +"]" );
}
log.trace( "Obtained UserTransaction" );
return ut;
}
catch ( NamingException ne ) {
throw new TransactionException( "Could not find UserTransaction in JNDI [" + getUserTransaction() + "]", ne );
}
}
/**
* Getter for property 'initialContext'.
*
* @return Value for property 'initialContext'.
*/
protected InitialContext getInitialContext() {
return initialContext;
}
/**
* Getter for property 'userTransactionName'.
* The algorithm here is
*
* @return Value for property 'userTransactionName'.
*/
protected String getUserTransactionName() {
return userTransactionName;
}
/**
* {@inheritDoc}
*/
public ConnectionReleaseMode getDefaultReleaseMode() {
return ConnectionReleaseMode.AFTER_STATEMENT;
}
/**
* {@inheritDoc}
*/
public boolean isTransactionManagerRequired() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean areCallbacksLocalToHibernateTransactions() {
return false;
}
/**
* {@inheritDoc}
*/
public boolean isTransactionInProgress(
JDBCContext jdbcContext,
Context transactionContext,
Transaction transaction) {
Context transactionContext,
Transaction transaction) {
try {
// Essentially:
// Essentially:
// 1) If we have a local (Hibernate) transaction in progress
// and it already has the UserTransaction cached, use that
// UserTransaction to determine the status.
@ -83,27 +229,22 @@ public boolean isTransactionInProgress(
// 3) Finally, as the last resort, try to lookup the
// UserTransaction via JNDI and use that to determine the
// status.
if ( transaction != null ) {
if ( transaction != null ) {
UserTransaction ut = ( ( JTATransaction ) transaction ).getUserTransaction();
if ( ut != null ) {
return JTAHelper.isInProgress( ut.getStatus() );
}
}
if ( ut != null ) {
return JTAHelper.isInProgress( ut.getStatus() );
}
}
if ( jdbcContext.getFactory().getTransactionManager() != null ) {
return JTAHelper.isInProgress( jdbcContext.getFactory().getTransactionManager().getStatus() );
}
else {
try {
UserTransaction ut = ( UserTransaction ) context.lookup( utName );
return ut != null && JTAHelper.isInProgress( ut.getStatus() );
}
catch ( NamingException ne ) {
throw new TransactionException( "Unable to locate UserTransaction to check status", ne );
}
}
if ( jdbcContext.getFactory().getTransactionManager() != null ) {
return JTAHelper.isInProgress( jdbcContext.getFactory().getTransactionManager().getStatus() );
}
else {
UserTransaction ut = getUserTransaction();
return ut != null && JTAHelper.isInProgress( ut.getStatus() );
}
}
catch( SystemException se ) {
catch ( SystemException se ) {
throw new TransactionException( "Unable to check transaction status", se );
}
}

View File

@ -550,12 +550,16 @@ public Object replace(
//for arrays, replaceElements() may return a different reference, since
//the array length might not match
result = replaceElements( original, result, owner, copyCache, session );
if (original==target) {
//get the elements back into the target
if ( original == target ) {
// get the elements back into the target making sure to handle dirty flag
boolean wasClean = PersistentCollection.class.isInstance( target ) && !( ( PersistentCollection ) target ).isDirty();
//TODO: this is a little inefficient, don't need to do a whole
// deep replaceElements() call
replaceElements( result, target, owner, copyCache, session );
if ( wasClean ) {
( ( PersistentCollection ) target ).clearDirty();
}
result = target;
}

View File

@ -283,6 +283,55 @@ public void testNoExtraUpdatesOnMergeVersionedWithCollection() throws Exception
// cleanup();
}
public void testNoExtraUpdatesOnPersistentMergeVersionedWithCollection() throws Exception {
Session s = openSession();
s.beginTransaction();
VersionedEntity parent = new VersionedEntity( "parent", "parent" );
VersionedEntity child = new VersionedEntity( "child", "child" );
parent.getChildren().add( child );
child.setParent( parent );
s.persist( parent );
s.getTransaction().commit();
s.close();
clearCounts();
// parent is now detached, but we have made no changes. so attempt to merge it
// into this new session; this should cause no updates...
s = openSession();
s.beginTransaction();
// load parent so that merge will follow entityIsPersistent path
VersionedEntity persistentParent = ( VersionedEntity ) s.get( VersionedEntity.class, parent.getId() );
// load children
VersionedEntity persistentChild = ( VersionedEntity ) persistentParent.getChildren().iterator().next();
VersionedEntity mergedParent = ( VersionedEntity ) s.merge( persistentParent ); // <-- This merge leads to failure
s.getTransaction().commit();
s.close();
assertUpdateCount( 0 );
assertInsertCount( 0 );
assertEquals( "unexpected parent version increment", parent.getVersion(), mergedParent.getVersion() );
VersionedEntity mergedChild = ( VersionedEntity ) mergedParent.getChildren().iterator().next();
assertEquals( "unexpected child version increment", child.getVersion(), mergedChild.getVersion() );
///////////////////////////////////////////////////////////////////////
// as a control measure, now update the node once it is loaded and
// make sure we get an update as a result...
s = openSession();
s.beginTransaction();
persistentParent = ( VersionedEntity ) s.get( VersionedEntity.class, parent.getId() );
persistentParent.setName( "new name" );
persistentParent.getChildren().add( new VersionedEntity( "child2", "new child" ) );
persistentParent = ( VersionedEntity ) s.merge( persistentParent );
s.getTransaction().commit();
s.close();
assertUpdateCount( 1 );
assertInsertCount( 1 );
///////////////////////////////////////////////////////////////////////
// cleanup();
}
public void testPersistThenMergeInSameTxnWithVersion() {
Session s = openSession();
Transaction tx = s.beginTransaction();

View File

@ -191,7 +191,7 @@
<database-object>
<create>
CREATE PROCEDURE HIBDB2TST.selectAllEmployments ()
CREATE PROCEDURE HIBDB2TST.selectAllEmployments () RESULT SETS 1
P1: BEGIN
DECLARE C1 CURSOR WITH RETURN FOR
SELECT EMPLOYEE, EMPLOYER, STARTDATE, ENDDATE,
@ -208,7 +208,7 @@
<database-object>
<create>
CREATE PROCEDURE HIBDB2TST.paramHandling (IN j SMALLINT, IN i SMALLINT)
CREATE PROCEDURE HIBDB2TST.paramHandling (IN j SMALLINT, IN i SMALLINT) RESULT SETS 1
P1: BEGIN
DECLARE C1 CURSOR WITH RETURN FOR
SELECT j as value, i as value2 from sysibm.sysdummy1;
@ -222,7 +222,7 @@
<database-object>
<create>
CREATE PROCEDURE HIBDB2TST.simpleScalar (IN j SMALLINT)
CREATE PROCEDURE HIBDB2TST.simpleScalar (IN j SMALLINT) RESULT SETS 1
P1: BEGIN
DECLARE C1 CURSOR WITH RETURN FOR
SELECT j as value, 'getAll' as name from sysibm.sysdummy1;