Index: /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/builder/xml/SqlStatementParser.java
===================================================================
--- /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/builder/xml/SqlStatementParser.java (revision 663510)
+++ /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/builder/xml/SqlStatementParser.java (working copy)
@@ -5,7 +5,6 @@
import com.ibatis.sqlmap.engine.config.*;
import com.ibatis.sqlmap.engine.mapping.statement.*;
import com.ibatis.sqlmap.client.*;
-import org.w3c.dom.CharacterData;
import org.w3c.dom.*;
import java.util.Properties;
@@ -108,7 +107,18 @@
String keyPropName = attributes.getProperty("keyProperty");
String resultClassName = attributes.getProperty("resultClass");
String type = attributes.getProperty("type");
- config.setSelectKeyStatement(new XMLSqlSource(state, child), resultClassName, keyPropName, foundSQLFirst, type);
+ String contents = null;
+ try
+ {
+ // The contents is passed so that it can be determined whether it
+ // contains sql or a key column for jdbc3 generated keys
+ // see SelectKeyStatement class
+ contents = ((CharacterData) child.getFirstChild( )).getData();
+ }
+ catch( DOMException e )
+ {
+ }
+ config.setSelectKeyStatement(new XMLSqlSource(state, child), resultClassName, keyPropName, foundSQLFirst, type, contents);
break;
}
}
Index: /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/config/MappedStatementConfig.java
===================================================================
--- /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/config/MappedStatementConfig.java (revision 663510)
+++ /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/config/MappedStatementConfig.java (working copy)
@@ -114,7 +114,7 @@
delegate.addMappedStatement(mappedStatement);
}
- public void setSelectKeyStatement(SqlSource processor, String resultClassName, String keyPropName, boolean runAfterSQL, String type) {
+ public void setSelectKeyStatement(SqlSource processor, String resultClassName, String keyPropName, boolean runAfterSQL, String type, String contents) {
if (rootStatement instanceof InsertStatement) {
InsertStatement insertStatement = ((InsertStatement) rootStatement);
Class parameterClass = insertStatement.getParameterClass();
@@ -129,6 +129,19 @@
selectKeyStatement.setResource(errorContext.getResource());
selectKeyStatement.setKeyProperty(keyPropName);
selectKeyStatement.setRunAfterSQL(runAfterSQL);
+ // contents holds the textual contents of the selectKey config element
+ if( contents != null )
+ {
+ // check if keyColumn is defined for getting jdbc3 keys
+ // see SelectKeyStatement class
+ contents = contents.trim( );
+ if( contents.startsWith( "KEY_COLUMN:" ) )
+ {
+ contents = contents.replace( "KEY_COLUMN:", "" ).trim( );
+ selectKeyStatement.setKeyColumn( contents );
+ }
+ }
+
// process the type (pre or post) attribute
if (type != null) {
selectKeyStatement.setRunAfterSQL("post".equals(type));
Index: /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/execution/GeneratedKeyThreadLocal.java
===================================================================
--- /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/execution/GeneratedKeyThreadLocal.java (revision 0)
+++ /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/execution/GeneratedKeyThreadLocal.java (revision 0)
@@ -0,0 +1,31 @@
+package com.ibatis.sqlmap.engine.execution;
+
+import com.ibatis.sqlmap.engine.mapping.statement.SelectKeyStatement;
+
+/**
+ * This class stores a jdbc3 generated key per thread using a {@link ThreadLocal}.
+ * In this way the key can be passed to lower layers of interfaces without making
+ * any changes to current method signatures.
+ *
+ * @see SelectKeyStatement
+ */
+public class GeneratedKeyThreadLocal
+{
+ private static final ThreadLocal tl = new ThreadLocal( );
+
+ public static final Object getKey( )
+ {
+ return tl.get( );
+ }
+
+ public static final void setKey( Object key )
+ {
+ tl.set( key );
+ }
+
+ public static final void clear( )
+ {
+ tl.remove( );
+ }
+
+}
Index: /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/execution/SqlExecutor.java
===================================================================
--- /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/execution/SqlExecutor.java (revision 663510)
+++ /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/execution/SqlExecutor.java (working copy)
@@ -19,8 +19,10 @@
import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
import com.ibatis.sqlmap.engine.mapping.result.ResultObjectFactoryUtil;
+import com.ibatis.sqlmap.engine.mapping.statement.InsertStatement;
import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
import com.ibatis.sqlmap.engine.mapping.statement.RowHandlerCallback;
+import com.ibatis.sqlmap.engine.mapping.statement.StatementType;
import com.ibatis.sqlmap.engine.scope.ErrorContext;
import com.ibatis.sqlmap.engine.scope.StatementScope;
import com.ibatis.sqlmap.engine.scope.SessionScope;
@@ -72,7 +74,27 @@
int rows = 0;
try {
errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
- ps = prepareStatement(statementScope.getSession(), conn, sql);
+ /*
+ * Check whether JDBC 3 feature Statement.getGeneratedKeys is to be used.
+ * This is only relevant for insert statements and will only be done if
+ * the selectKeyStatement has a keyColumn defined.
+ * see SelectKeyStatement class
+ */
+ boolean getJdbc3GeneratedKey = false;
+ if( statementScope.getStatement( ) instanceof InsertStatement )
+ {
+ InsertStatement is = (InsertStatement) statementScope.getStatement( );
+ if( is.getSelectKeyStatement( ) != null && is.getSelectKeyStatement( ).getKeyColumn( ) != null )
+ {
+ getJdbc3GeneratedKey = true;
+ ps = prepareStatement(statementScope.getSession(), conn, sql, is.getSelectKeyStatement( ).getKeyColumn( ) );
+ }
+ }
+
+ // Check if all the jdbc3 key checks produced a value for ps
+ if( ps == null )
+ ps = prepareStatement(statementScope.getSession(), conn, sql);
+
setStatementTimeout(statementScope.getStatement(), ps);
errorContext.setMoreInfo("Check the parameters (set parameters failed).");
statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
@@ -78,6 +100,26 @@
statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
errorContext.setMoreInfo("Check the statement (update failed).");
ps.execute();
+
+ // Check if we need to get the generated keys, any failure will lead
+ // to it being ignored
+ if( getJdbc3GeneratedKey )
+ {
+ try
+ {
+ ResultSet rs = ps.getGeneratedKeys( );
+ if( rs.next( ) )
+ {
+ Object obj = rs.getObject( 1 );
+ // This threadlocal carries the generatedkey value across the
+ // layers of interfaces to be as little intrusive as possible
+ GeneratedKeyThreadLocal.setKey( obj );
+ }
+ }
+ catch( Exception e )
+ { }
+ }
+
rows = ps.getUpdateCount();
} finally {
closeStatement(statementScope.getSession(), ps);
@@ -489,7 +531,22 @@
}
}
- private static PreparedStatement prepareStatement(SessionScope sessionScope, Connection conn, String sql) throws SQLException {
+ /**
+ * This method supports statements which generate keys. The keyColumn is
+ * passed to the prepareStatement(String sql, String columnNames[])
+ * method as a single value String[].
+ *
+ * If the keyColumn is null, it reverts to a normal
+ * prepareStatement(String sql) method.
+ *
+ * @param sessionScope
+ * @param conn
+ * @param sql
+ * @param keyColumn
+ * @return
+ * @throws SQLException
+ */
+ private static PreparedStatement prepareStatement(SessionScope sessionScope, Connection conn, String sql, String keyColumn) throws SQLException {
SqlMapExecutorDelegate delegate = ((SqlMapClientImpl) sessionScope.getSqlMapExecutor()).getDelegate();
if (sessionScope.hasPreparedStatementFor(sql)) {
return sessionScope.getPreparedStatement((sql));
@@ -494,7 +551,11 @@
if (sessionScope.hasPreparedStatementFor(sql)) {
return sessionScope.getPreparedStatement((sql));
} else {
- PreparedStatement ps = conn.prepareStatement(sql);
+ PreparedStatement ps;
+ if( keyColumn == null )
+ ps = conn.prepareStatement(sql);
+ else
+ ps = conn.prepareStatement(sql, new String[] { keyColumn } );
sessionScope.putPreparedStatement(delegate, sql, ps);
return ps;
}
@@ -500,6 +561,10 @@
}
}
+ private static PreparedStatement prepareStatement(SessionScope sessionScope, Connection conn, String sql) throws SQLException {
+ return prepareStatement( sessionScope, conn, sql, (String) null );
+ }
+
private CallableStatement prepareCall(SessionScope sessionScope, Connection conn, String sql) throws SQLException {
SqlMapExecutorDelegate delegate = ((SqlMapClientImpl) sessionScope.getSqlMapExecutor()).getDelegate();
if (sessionScope.hasPreparedStatementFor(sql)) {
Index: /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/impl/SqlMapExecutorDelegate.java
===================================================================
--- /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/impl/SqlMapExecutorDelegate.java (revision 663510)
+++ /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/impl/SqlMapExecutorDelegate.java (working copy)
@@ -25,6 +25,7 @@
import com.ibatis.sqlmap.engine.cache.CacheModel;
import com.ibatis.sqlmap.engine.exchange.DataExchangeFactory;
import com.ibatis.sqlmap.engine.execution.BatchException;
+import com.ibatis.sqlmap.engine.execution.GeneratedKeyThreadLocal;
import com.ibatis.sqlmap.engine.execution.SqlExecutor;
import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
@@ -381,7 +382,9 @@
Object oldKeyValue = null;
String keyProperty = null;
boolean resetKeyValueOnFailure = false;
- if (selectKeyStatement != null && !selectKeyStatement.isRunAfterSQL()) {
+ // check if keyColumn is null, if it isn't then jdbc 3 generated keys will
+ // be used and isRunAfterSQL is irrelevant
+ if (selectKeyStatement != null && !selectKeyStatement.isRunAfterSQL() && selectKeyStatement.getKeyColumn( ) == null) {
keyProperty = selectKeyStatement.getKeyProperty();
oldKeyValue = PROBE.getObject(param, keyProperty);
generatedKey = executeSelectKey(sessionScope, trans, ms, param);
@@ -401,8 +404,20 @@
endStatementScope(statementScope);
}
- if (selectKeyStatement != null && selectKeyStatement.isRunAfterSQL()) {
- generatedKey = executeSelectKey(sessionScope, trans, ms, param);
+ // need the extra keyColumn null check to effectively ignore isRunAfterSQL
+ // when getting jdbc3 generated keys
+ if (selectKeyStatement != null && (selectKeyStatement.isRunAfterSQL() || selectKeyStatement.getKeyColumn( ) != null)) {
+ if( selectKeyStatement.getKeyColumn( ) != null )
+ {
+ // So check the thread local whether we retrieved a jdbc3 generated key
+ // if we didn't then revert to the selectKey statement.
+ Object key = getJdbc3Key( selectKeyStatement, param );
+ if( key != null )
+ generatedKey = key;
+ }
+ // if it is null then we didn't get a jdbc3 key, so use the selectKey
+ if( generatedKey == null )
+ generatedKey = executeSelectKey(sessionScope, trans, ms, param);
}
autoCommitTransaction(sessionScope, autoStart);
@@ -413,6 +428,26 @@
return generatedKey;
}
+ /**
+ * Retrieves the jdbc3 generated key from a thread local and sets it on the
+ * PROBE.
+ *
+ * @param selectKeyStatement
+ * @param param
+ * @return null if no key was set on the thread local.
+ */
+ private Object getJdbc3Key( SelectKeyStatement selectKeyStatement, Object param )
+ {
+ Object generatedKey = GeneratedKeyThreadLocal.getKey( );
+ String keyProp = selectKeyStatement.getKeyProperty();
+ if( keyProp != null && generatedKey != null )
+ {
+ PROBE.setObject(param, keyProp, generatedKey);
+ }
+ GeneratedKeyThreadLocal.clear( );
+ return generatedKey;
+ }
+
private Object executeSelectKey(SessionScope sessionScope, Transaction trans, MappedStatement ms, Object param) throws SQLException {
Object generatedKey = null;
StatementScope statementScope;
Index: /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/mapping/statement/SelectKeyStatement.java
===================================================================
--- /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/mapping/statement/SelectKeyStatement.java (revision 663510)
+++ /home/zachv/devel/workspace/ibatis/src/com/ibatis/sqlmap/engine/mapping/statement/SelectKeyStatement.java (working copy)
@@ -16,6 +16,7 @@
package com.ibatis.sqlmap.engine.mapping.statement;
import com.ibatis.sqlmap.client.event.RowHandler;
+import com.ibatis.sqlmap.engine.execution.GeneratedKeyThreadLocal;
import com.ibatis.sqlmap.engine.scope.StatementScope;
import com.ibatis.sqlmap.engine.transaction.Transaction;
@@ -22,6 +23,28 @@
import java.sql.SQLException;
import java.util.List;
+/**
+ * Regarding jdbc 3 generated keys:
+ *