Author: max...@jb...
Date: 2006-05-05 16:29:41 -0400 (Fri, 05 May 2006)
New Revision: 9896
Modified:
trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java
trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java
trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java
trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java
trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java
trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java
trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java
trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java
trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml
Log:
HHH-870 support SQL updates in named queries
Modified: trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -2,10 +2,15 @@
package org.hibernate.action;
import org.hibernate.HibernateException;
+import org.hibernate.metadata.ClassMetadata;
+import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Queryable;
+import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.HashSet;
@@ -45,7 +50,55 @@
this.spaces[i] = ( Serializable ) tmpSpaces.get( i );
}
}
+
+ /** Create an action that will evict collection and entity regions based on queryspaces (table names).
+ * TODO: cache the autodetected information and pass it in instead.
+ **/
+ public BulkOperationCleanupAction(SessionImplementor session, Set querySpaces) {
+ this.session = session;
+ if(querySpaces!=null && !querySpaces.isEmpty()) {
+ Set tmpSpaces = new HashSet(querySpaces);
+ SessionFactoryImplementor factory = session.getFactory();
+ Iterator iterator = factory.getAllClassMetadata().entrySet().iterator();
+ while ( iterator.hasNext() ) {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ String entityName = (String) entry.getKey();
+ EntityPersister persister = factory.getEntityPersister( entityName );
+ Serializable[] entitySpaces = persister.getQuerySpaces();
+
+ if (affectedEntity( querySpaces, entitySpaces )) {
+ if ( persister.hasCache() ) {
+ affectedEntityNames.add( persister.getEntityName() );
+ }
+ Set roles = session.getFactory().getCollectionRolesByEntityParticipant( persister.getEntityName() );
+ if ( roles != null ) {
+ affectedCollectionRoles.addAll( roles );
+ }
+ for ( int y = 0; y < entitySpaces.length; y++ ) {
+ tmpSpaces.add( entitySpaces[y] );
+ }
+ }
+
+ }
+
+ this.spaces = (Serializable[]) tmpSpaces.toArray( new Serializable[tmpSpaces.size()] );
+ } else {
+ // TODO: no synchronize info, sync on *all* ?
+ this.spaces = new Serializable[0];
+ }
+ }
+
+
+ private boolean affectedEntity(Set querySpaces, Serializable[] entitySpaces) {
+ for ( int i = 0; i < entitySpaces.length; i++ ) {
+ if ( querySpaces.contains( entitySpaces[i] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void init() {
evictEntityRegions();
evictCollectionRegions();
@@ -69,7 +122,7 @@
}
public void execute() throws HibernateException {
- // nothing to do
+ // nothing to do
}
private void evictEntityRegions() {
Modified: trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -254,6 +254,11 @@
*/
int executeUpdate(String query, QueryParameters queryParameters) throws HibernateException;
+ /**
+ * Execute a native SQL update or delete query
+ */
+ int executeNativeUpdate(NativeSQLQuerySpecification specification, QueryParameters queryParameters) throws HibernateException;
+
// copied from Session:
public EntityMode getEntityMode();
Modified: trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -1,30 +1,49 @@
package org.hibernate.engine.query;
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.HibernateException;
+import org.hibernate.QueryException;
+import org.hibernate.action.BulkOperationCleanupAction;
+import org.hibernate.engine.QueryParameters;
+import org.hibernate.engine.SessionFactoryImplementor;
+import org.hibernate.engine.SessionImplementor;
+import org.hibernate.engine.TypedValue;
+import org.hibernate.event.EventSource;
+import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.loader.custom.SQLCustomQuery;
-import org.hibernate.engine.SessionFactoryImplementor;
-import org.hibernate.engine.NamedSQLQueryDefinition;
+import org.hibernate.pretty.Formatter;
+import org.hibernate.type.Type;
+import org.hibernate.util.ArrayHelper;
-import java.io.Serializable;
-
/**
* Defines a query execution plan for a native-SQL query.
- *
+ *
* @author <a href="mailto:st...@hi...">Steve Ebersole </a>
*/
public class NativeSQLQueryPlan implements Serializable {
private final String sourceQuery;
+
private final SQLCustomQuery customQuery;
- public NativeSQLQueryPlan(NativeSQLQuerySpecification specification, SessionFactoryImplementor factory) {
+ private static final Log log = LogFactory.getLog(NativeSQLQueryPlan.class);
+
+ public NativeSQLQueryPlan(NativeSQLQuerySpecification specification,
+ SessionFactoryImplementor factory) {
this.sourceQuery = specification.getQueryString();
- customQuery = new SQLCustomQuery(
- specification.getSqlQueryReturns(),
- specification.getSqlQueryScalarReturns(),
- specification.getQueryString(),
- specification.getQuerySpaces(),
- factory
- );
+ customQuery = new SQLCustomQuery( specification.getSqlQueryReturns(),
+ specification.getSqlQueryScalarReturns(), specification
+ .getQueryString(), specification.getQuerySpaces(),
+ factory );
}
public String getSourceQuery() {
@@ -34,4 +53,129 @@
public SQLCustomQuery getCustomQuery() {
return customQuery;
}
+
+ private int[] getNamedParameterLocs(String name) throws QueryException {
+ Object loc = customQuery.getNamedParameterBindPoints().get( name );
+ if ( loc == null ) {
+ throw new QueryException(
+ "Named parameter does not appear in Query: " + name,
+ customQuery.getSQL() );
+ }
+ if ( loc instanceof Integer ) {
+ return new int[] { ((Integer) loc ).intValue() };
+ }
+ else {
+ return ArrayHelper.toIntArray( (List) loc );
+ }
+ }
+
+ /**
+ * Bind positional parameter values to the <tt>PreparedStatement</tt>
+ * (these are parameters specified by a JDBC-style ?).
+ */
+ private int bindPositionalParameters(final PreparedStatement st,
+ final QueryParameters queryParameters, final int start,
+ final SessionImplementor session) throws SQLException,
+ HibernateException {
+
+ final Object[] values = queryParameters
+ .getFilteredPositionalParameterValues();
+ final Type[] types = queryParameters
+ .getFilteredPositionalParameterTypes();
+ int span = 0;
+ for (int i = 0; i < values.length; i++) {
+ types[i].nullSafeSet( st, values[i], start + span, session );
+ span += types[i].getColumnSpan( session.getFactory() );
+ }
+ return span;
+ }
+
+ /**
+ * Bind named parameters to the <tt>PreparedStatement</tt>. This has an
+ * empty implementation on this superclass and should be implemented by
+ * subclasses (queries) which allow named parameters.
+ */
+ private int bindNamedParameters(final PreparedStatement ps,
+ final Map namedParams, final int start,
+ final SessionImplementor session) throws SQLException,
+ HibernateException {
+
+ if ( namedParams != null ) {
+ // assumes that types are all of span 1
+ Iterator iter = namedParams.entrySet().iterator();
+ int result = 0;
+ while ( iter.hasNext() ) {
+ Map.Entry e = (Map.Entry) iter.next();
+ String name = (String) e.getKey();
+ TypedValue typedval = (TypedValue) e.getValue();
+ int[] locs = getNamedParameterLocs( name );
+ for (int i = 0; i < locs.length; i++) {
+ if ( log.isDebugEnabled() ) {
+ log.debug( "bindNamedParameters() "
+ + typedval.getValue() + " -> " + name + " ["
+ + (locs[i] + start ) + "]" );
+ }
+ typedval.getType().nullSafeSet( ps, typedval.getValue(),
+ locs[i] + start, session );
+ }
+ result += locs.length;
+ }
+ return result;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ protected void coordinateSharedCacheCleanup(SessionImplementor session) {
+ BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, getCustomQuery().getQuerySpaces() );
+
+ action.init();
+
+ if ( session.isEventSource() ) {
+ ( ( EventSource ) session ).getActionQueue().addAction( action );
+ }
+ }
+
+ public int performExecuteUpdate(QueryParameters queryParameters,
+ SessionImplementor session) throws HibernateException {
+
+ coordinateSharedCacheCleanup( session );
+
+ if(queryParameters.isCallable()) {
+ throw new IllegalArgumentException("callable not yet supported for native queries");
+ }
+
+ int result = 0;
+ PreparedStatement ps = null;
+ try {
+ queryParameters.processFilters( this.customQuery.getSQL(),
+ session );
+ String sql = queryParameters.getFilteredSQL();
+
+ ps = session.getBatcher().prepareStatement( sql );
+
+ try {
+ int col = 1;
+ col += bindPositionalParameters( ps, queryParameters, col,
+ session );
+ col += bindNamedParameters( ps, queryParameters
+ .getNamedParameters(), col, session );
+ result = ps.executeUpdate();
+ }
+ finally {
+ if ( ps != null ) {
+ session.getBatcher().closeStatement( ps );
+ }
+ }
+ }
+ catch (SQLException sqle) {
+ throw JDBCExceptionHelper.convert( session.getFactory()
+ .getSQLExceptionConverter(), sqle,
+ "could not execute native bulk manipulation query", this.sourceQuery );
+ }
+
+ return result;
+ }
+
}
Modified: trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -756,10 +756,6 @@
return uniqueElement( list() );
}
- public int executeUpdate() throws HibernateException {
- throw new UnsupportedOperationException( "Update queries only supported through HQL" );
- }
-
static Object uniqueElement(List list) throws NonUniqueResultException {
int size = list.size();
if (size==0) return null;
Modified: trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -317,4 +317,10 @@
}
return this;
}
+
+ public int executeUpdate() throws HibernateException {
+ Map namedParams = getNamedParams();
+ return getSession().executeNativeUpdate(generateQuerySpecification( namedParams ), getQueryParameters( namedParams ));
+ }
+
}
Modified: trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -29,7 +29,6 @@
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.ObjectDeletedException;
-import org.hibernate.ObjectNotFoundException;
import org.hibernate.Query;
import org.hibernate.QueryException;
import org.hibernate.ReplicationMode;
@@ -54,6 +53,8 @@
import org.hibernate.engine.Status;
import org.hibernate.engine.query.FilterQueryPlan;
import org.hibernate.engine.query.HQLQueryPlan;
+import org.hibernate.engine.query.NativeSQLQueryPlan;
+import org.hibernate.engine.query.NativeSQLQuerySpecification;
import org.hibernate.event.AutoFlushEvent;
import org.hibernate.event.AutoFlushEventListener;
import org.hibernate.event.DeleteEvent;
@@ -88,6 +89,8 @@
import org.hibernate.loader.criteria.CriteriaLoader;
import org.hibernate.loader.custom.CustomLoader;
import org.hibernate.loader.custom.CustomQuery;
+import org.hibernate.loader.custom.SQLQueryReturn;
+import org.hibernate.loader.custom.SQLQueryScalarReturn;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.OuterJoinLoadable;
@@ -116,7 +119,7 @@
// todo : need to find a clean way to handle the "event source" role
// a seperate classs responsible for generating/dispatching events just duplicates most of the Session methods...
- // passing around seperate references to interceptor, factory, actionQueue, and persistentContext is not manageable...
+ // passing around seperate reto interceptor, factory, actionQueue, and persistentContext is not manageable...
private static final Log log = LogFactory.getLog(SessionImpl.class);
@@ -1137,6 +1140,27 @@
return result;
}
+ public int executeNativeUpdate(NativeSQLQuerySpecification nativeQuerySpecification,
+ QueryParameters queryParameters) throws HibernateException {
+ errorIfClosed();
+ checkTransactionSynchStatus();
+ queryParameters.validateParameters();
+ NativeSQLQueryPlan plan = getNativeSQLQueryPlan(nativeQuerySpecification);
+
+
+ autoFlushIfRequired( plan.getCustomQuery().getQuerySpaces() );
+
+ boolean success = false;
+ int result = 0;
+ try {
+ result = plan.performExecuteUpdate(queryParameters, this);
+ success = true;
+ } finally {
+ afterOperation(success);
+ }
+ return result;
+ }
+
public Iterator iterate(String query) throws HibernateException {
return iterate( query, new QueryParameters() );
}
Modified: trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java
===================================================================
--- trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -9,6 +9,8 @@
import java.util.Map;
import java.util.Set;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.hibernate.CacheMode;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.Criteria;
@@ -26,7 +28,6 @@
import org.hibernate.Transaction;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.cache.CacheKey;
-import org.hibernate.pretty.MessageHelper;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.PersistenceContext;
@@ -34,6 +35,8 @@
import org.hibernate.engine.StatefulPersistenceContext;
import org.hibernate.engine.Versioning;
import org.hibernate.engine.query.HQLQueryPlan;
+import org.hibernate.engine.query.NativeSQLQueryPlan;
+import org.hibernate.engine.query.NativeSQLQuerySpecification;
import org.hibernate.event.EventListeners;
import org.hibernate.id.IdentifierGeneratorFactory;
import org.hibernate.jdbc.Batcher;
@@ -43,11 +46,10 @@
import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.OuterJoinLoadable;
+import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.type.Type;
import org.hibernate.util.CollectionHelper;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
/**
* @author Gavin King
@@ -606,4 +608,23 @@
// no auto-flushing to support in stateless session
return false;
}
+
+ public int executeNativeUpdate(NativeSQLQuerySpecification nativeSQLQuerySpecification,
+ QueryParameters queryParameters) throws HibernateException {
+ errorIfClosed();
+ queryParameters.validateParameters();
+ NativeSQLQueryPlan plan = getNativeSQLQueryPlan(nativeSQLQuerySpecification);
+
+ boolean success = false;
+ int result = 0;
+ try {
+ result = plan.performExecuteUpdate(queryParameters, this);
+ success = true;
+ } finally {
+ afterOperation(success);
+ }
+ temporaryPersistenceContext.clear();
+ return result;
+ }
+
}
Modified: trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java 2006-05-05 20:29:41 UTC (rev 9896)
@@ -138,35 +138,43 @@
TestData data = new TestData();
data.prepare();
- try {
- Session s = openSession();
- Transaction t = s.beginTransaction();
-
- List l = s.createQuery("from Vehicle").list();
- assertEquals(l.size(),4);
-
- s.createSQLQuery( "insert into Pickup (id, vin, owner) select id, vin, owner from Car" ).executeUpdate();
-
- l = s.createQuery("from Vehicle").list();
- assertEquals(l.size(),5);
-
- t.commit();
- t = s.beginTransaction();
-
- s.createSQLQuery( "delete Vehicle" ).executeUpdate();
-
- l = s.createQuery("from Vehicle").list();
- assertEquals(l.size(),0);
-
- t.commit();
- s.close();
- fail("native sql bulk manipulation should fail with UnsupportedOperationException");
- } catch(UnsupportedOperationException eo) {
- // should happen
- }
+ Session s = openSession();
+ Transaction t = s.beginTransaction();
+
+ List l = s.createQuery("from Vehicle").list();
+ assertEquals(l.size(),4);
+
+ s.createSQLQuery( "insert into PICKUP (id, vin, owner) select id, vin, owner from Car" ).executeUpdate();
+
+ l = s.createQuery("from Vehicle").list();
+ assertEquals(l.size(),5);
+
+ t.commit();
+ t = s.beginTransaction();
+
+ s.createSQLQuery( "delete from TRUCK" ).executeUpdate();
+
+ l = s.createQuery("from Vehicle").list();
+ assertEquals(l.size(),4);
+
+ Car c = (Car) s.createQuery( "from Car where owner = 'Kirsten'" ).uniqueResult();
+ c.setOwner("NotKirsten");
+ assertEquals(0,s.getNamedQuery( "native-delete-car" ).setString( 0, "Kirsten" ).executeUpdate());
+ assertEquals(1,s.getNamedQuery( "native-delete-car" ).setString( 0, "NotKirsten" ).executeUpdate());
+ assertEquals(0,s.createSQLQuery( "delete from SUV where owner = :owner" ).setString( "owner", "NotThere" ).executeUpdate());
+ assertEquals(1,s.createSQLQuery( "delete from SUV where owner = :owner" ).setString( "owner", "Joe" ).executeUpdate());
+ s.createSQLQuery( "delete from PICKUP" ).executeUpdate();
+ l = s.createQuery("from Vehicle").list();
+ assertEquals(l.size(),0);
+
+
+ t.commit();
+ s.close();
+
+
data.cleanup();
}
Modified: trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml
===================================================================
--- trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml 2006-05-05 19:27:17 UTC (rev 9895)
+++ trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml 2006-05-05 20:29:41 UTC (rev 9896)
@@ -24,4 +24,8 @@
</union-subclass>
</class>
+ <sql-query name="native-delete-car">
+ <synchronize table="Car"/>
+ delete from CAR where owner = ?
+ </sql-query>
</hibernate-mapping>
\ No newline at end of file
|