Author: steve.ebersole(a)jboss.com
Date: 2010-07-22 16:23:08 -0400 (Thu, 22 Jul 2010)
New Revision: 20014
Added:
core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java
Modified:
core/trunk/core/src/main/java/org/hibernate/SQLQuery.java
core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java
core/trunk/core/src/main/java/org/hibernate/classic/Session.java
core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java
core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java
core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java
core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java
Log:
HHH-3908 - Expose way to fully control fetching and result mapping on SQLQuery
Modified: core/trunk/core/src/main/java/org/hibernate/SQLQuery.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/SQLQuery.java 2010-07-22 19:52:43 UTC (rev
20013)
+++ core/trunk/core/src/main/java/org/hibernate/SQLQuery.java 2010-07-22 20:23:08 UTC (rev
20014)
@@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, Red Hat Inc. 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.
+ * distributed under license by Red Hat Inc.
*
* 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
@@ -20,97 +20,323 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
- *
*/
package org.hibernate;
import org.hibernate.type.Type;
/**
- * Allows the user to declare the types and select list injection
- * points of all entities returned by the query. Also allows
- * declaration of the type and column alias of any scalar results
- * of the query.
+ * Represents a "native sql" query and allows the user to define certain
aspects about its execution, such as:<ul>
+ * <li>result-set value mapping (see below)</li>
+ * <li>
+ * Tables used via {@link #addSynchronizedQuerySpace}, {@link
#addSynchronizedEntityName} and
+ * {@link #addSynchronizedEntityClass}. This allows Hibernate to properly know how to
deal with auto-flush checking
+ * as well as cached query results if the results of the query are being cached.
+ * </li>
+ * </ul>
+ * <p/>
+ * In terms of result-set mapping, there are 3 approaches to defining:<ul>
+ * <li>If this represents a named sql query, the mapping could be associated with
the query as part of its metadata</li>
+ * <li>A pre-defined (defined in metadata and named) mapping can be associated with
{@link #setResultSetMapping}</li>
+ * <li>Defined locally per the various {@link #addEntity}, {@link #addRoot}, {@link
#addJoin}, {@link #addFetch} and {@link #addScalar} methods</li>
+ *
+ * </ul>
*
* @author Gavin King
+ * @author Steve Ebersole
*/
public interface SQLQuery extends Query {
+
/**
- * Declare a "root" entity, without specifying an alias
+ * Adds a query space (table name) for (a) auto-flush checking and (b) query result
cache invalidation checking
+ *
+ * @param querySpace The query space to be auto-flushed for this query.
+ *
+ * @return this, for method chaining
*/
- public SQLQuery addEntity(String entityName);
+ public SQLQuery addSynchronizedQuerySpace(String querySpace);
+
/**
- * Declare a "root" entity
+ * Adds an entity name for (a) auto-flush checking and (b) query result cache
invalidation checking. Same as
+ * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
+ *
+ * @param entityName The name of the entity upon whose defined query spaces we should
additionally synchronize.
+ *
+ * @return this, for method chaining
+ *
+ * @throws MappingException Indicates the given name could not be resolved as an entity
*/
- public SQLQuery addEntity(String alias, String entityName);
+ public SQLQuery addSynchronizedEntityName(String entityName) throws MappingException;
+
/**
- * Declare a "root" entity, specifying a lock mode
+ * Adds an entity for (a) auto-flush checking and (b) query result cache invalidation
checking. Same as
+ * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
+ *
+ * @param entityClass The class of the entity upon whose defined query spaces we should
additionally synchronize.
+ *
+ * @return this, for method chaining
+ *
+ * @throws MappingException Indicates the given class could not be resolved as an
entity
*/
- public SQLQuery addEntity(String alias, String entityName, LockMode lockMode);
+ public SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException;
+
/**
- * Declare a "root" entity, without specifying an alias
+ * Use a predefined named result-set mapping. This might be defined by a {@code
<result-set/>} element in a
+ * Hibernate <tt>hbm.xml</tt> file or through a {@link
javax.persistence.SqlResultSetMapping} annotation.
+ *
+ * @param name The name of the mapping to use.
+ *
+ * @return this, for method chaining
*/
- public SQLQuery addEntity(Class entityClass);
+ public SQLQuery setResultSetMapping(String name);
+
/**
+ * Declare a scalar query result. Hibernate will attempt to automatically detect the
underlying type.
+ * <p/>
+ * Functions like {@code <return-scalar/>} in {@code hbm.xml} or {@link
javax.persistence.ColumnResult}
+ *
+ * @param columnAlias The column alias in the result-set to be processed as a scalar
result
+ *
+ * @return {@code this}, for method chaining
+ */
+ public SQLQuery addScalar(String columnAlias);
+
+ /**
+ * Declare a scalar query result.
+ * <p/>
+ * Functions like {@code <return-scalar/>} in {@code hbm.xml} or {@link
javax.persistence.ColumnResult}
+ *
+ * @param columnAlias The column alias in the result-set to be processed as a scalar
result
+ * @param type The Hibernate type as which to treat the value.
+ *
+ * @return {@code this}, for method chaining
+ */
+ public SQLQuery addScalar(String columnAlias, Type type);
+
+ /**
+ * Add a new root return mapping, returning a {@link RootReturn} to allow further
definition
+ *
+ * @param tableAlias The SQL table alias to map to this entity
+ * @param entityName The name of the entity.
+ *
+ * @return The return config object for further control.
+ *
+ * @since 3.6
+ */
+ public RootReturn addRoot(String tableAlias, String entityName);
+
+ /**
+ * Add a new root return mapping, returning a {@link RootReturn} to allow further
definition
+ *
+ * @param tableAlias The SQL table alias to map to this entity
+ * @param entityType The java type of the entity.
+ *
+ * @return The return config object for further control.
+ *
+ * @since 3.6
+ */
+ public RootReturn addRoot(String tableAlias, Class entityType);
+
+ /**
+ * Declare a "root" entity, without specifying an alias. The expectation here
is that the table alias is the
+ * same as the unqualified entity name
+ * <p/>
+ * Use {@link #addRoot} if you need further control of the mapping
+ *
+ * @param entityName The entity name that is the root return of the query.
+ *
+ * @return {@code this}, for method chaining
+ */
+ public SQLQuery addEntity(String entityName);
+
+ /**
* Declare a "root" entity
+ *
+ * @param tableAlias The SQL table alias
+ * @param entityName The entity name
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery addEntity(String alias, Class entityClass);
+ public SQLQuery addEntity(String tableAlias, String entityName);
+
/**
* Declare a "root" entity, specifying a lock mode
+ *
+ * @param tableAlias The SQL table alias
+ * @param entityName The entity name
+ * @param lockMode The lock mode for this return.
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery addEntity(String alias, Class entityClass, LockMode lockMode);
+ public SQLQuery addEntity(String tableAlias, String entityName, LockMode lockMode);
/**
- * Declare a "joined" entity
+ * Declare a "root" entity, without specifying an alias. The expectation here
is that the table alias is the
+ * same as the unqualified entity name
+ *
+ * @param entityType The java type of the entity to add as a root
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery addJoin(String alias, String path);
+ public SQLQuery addEntity(Class entityType);
+
/**
- * Declare a "joined" entity, specifying a lock mode
+ * Declare a "root" entity
+ *
+ * @param tableAlias The SQL table alias
+ * @param entityType The java type of the entity to add as a root
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery addJoin(String alias, String path, LockMode lockMode);
-
+ public SQLQuery addEntity(String tableAlias, Class entityType);
+
/**
- * Declare a scalar query result
+ * Declare a "root" entity, specifying a lock mode
+ *
+ * @param tableAlias The SQL table alias
+ * @param entityName The entity name
+ * @param lockMode The lock mode for this return.
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery addScalar(String columnAlias, Type type);
+ public SQLQuery addEntity(String tableAlias, Class entityName, LockMode lockMode);
/**
- * Declare a scalar query. Hibernate will attempt to automatically detect the underlying
type.
+ * Declare a join fetch result.
+ *
+ * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+ * @param ownerTableAlias Identify the table alias of the owner of this association.
Should match the alias of a
+ * previously added root or fetch
+ * @param joinPropertyName The name of the property being join fetched.
+ *
+ * @return The return config object for further control.
+ *
+ * @since 3.6
*/
- public SQLQuery addScalar(String columnAlias);
+ public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String
joinPropertyName);
/**
- * Use a predefined named ResultSetMapping
+ * Declare a join fetch result.
+ *
+ * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+ * @param path The association path ([owner-alias].[property-name]).
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery setResultSetMapping(String name);
+ public SQLQuery addJoin(String tableAlias, String path);
/**
- * Adds a query space for auto-flush synchronization.
+ * Declare a join fetch result.
*
- * @param querySpace The query space to be auto-flushed for this query.
- * @return this, for method chaning
+ * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+ * @param ownerTableAlias Identify the table alias of the owner of this association.
Should match the alias of a
+ * previously added root or fetch
+ * @param joinPropertyName The name of the property being join fetched.
+ *
+ * @return {@code this}, for method chaining
+ *
+ * @since 3.6
*/
- public SQLQuery addSynchronizedQuerySpace(String querySpace);
+ public SQLQuery addJoin(String tableAlias, String ownerTableAlias, String
joinPropertyName);
/**
- * Adds an entity name or auto-flush synchronization.
+ * Declare a join fetch result, specifying a lock mode
*
- * @param entityName The name of the entity upon whose defined
- * query spaces we should additionally synchronize.
- * @return this, for method chaning
- * @throws MappingException Indicates the given entity name could not be
- * resolved.
+ * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+ * @param path The association path ([owner-alias].[property-name]).
+ * @param lockMode The lock mode for this return.
+ *
+ * @return {@code this}, for method chaining
*/
- public SQLQuery addSynchronizedEntityName(String entityName) throws MappingException;
+ public SQLQuery addJoin(String tableAlias, String path, LockMode lockMode);
/**
- * Adds an entity name or auto-flush synchronization.
- *
- * @param entityClass The class of the entity upon whose defined
- * query spaces we should additionally synchronize.
- * @return this, for method chaning
- * @throws MappingException Indicates the given entity class could not be
- * resolved.
+ * Allows access to further control how properties within a root or join fetch are
mapped back from the result set.
+ * Generally used in composite value scenarios.
*/
- public SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException;
+ public static interface ReturnProperty {
+ /**
+ * Add a column alias to this property mapping.
+ *
+ * @param columnAlias The column alias.
+ *
+ * @return {@code this}, for method chaining
+ */
+ public ReturnProperty addColumnAlias(String columnAlias);
+ }
+
+ /**
+ * Allows access to further control how root returns are mapped back from result sets
+ */
+ public static interface RootReturn {
+ /**
+ * Set the lock mode for this return
+ *
+ * @param lockMode The new lock mode.
+ *
+ * @return {@code this}, for method chaining
+ */
+ public RootReturn setLockMode(LockMode lockMode);
+
+ /**
+ * Name the column alias that identifies the entity's discriminator
+ *
+ * @param columnAlias The discriminator column alias
+ *
+ * @return {@code this}, for method chaining
+ */
+ public RootReturn setDiscriminatorAlias(String columnAlias);
+
+ /**
+ * Add a simple property-to-one-column mapping
+ *
+ * @param propertyName The name of the property.
+ * @param columnAlias The name of the column
+ *
+ * @return {@code this}, for method chaining
+ */
+ public RootReturn addProperty(String propertyName, String columnAlias);
+
+ /**
+ * Add a property, presumably with more than one column.
+ *
+ * @param propertyName The name of the property.
+ *
+ * @return The config object for further control.
+ */
+ public ReturnProperty addProperty(String propertyName);
+ }
+
+ /**
+ * Allows access to further control how join fetch returns are mapped back from result
sets
+ */
+ public static interface FetchReturn {
+ /**
+ * Set the lock mode for this return
+ *
+ * @param lockMode The new lock mode.
+ *
+ * @return {@code this}, for method chaining
+ */
+ public FetchReturn setLockMode(LockMode lockMode);
+
+ /**
+ * Add a simple property-to-one-column mapping
+ *
+ * @param propertyName The name of the property.
+ * @param columnAlias The name of the column
+ *
+ * @return {@code this}, for method chaining
+ */
+ public FetchReturn addProperty(String propertyName, String columnAlias);
+
+ /**
+ * Add a property, presumably with more than one column.
+ *
+ * @param propertyName The name of the property.
+ *
+ * @return The config object for further control.
+ */
+ public ReturnProperty addProperty(String propertyName);
+ }
}
Added: core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java
(rev 0)
+++
core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java 2010-07-22
20:23:08 UTC (rev 20014)
@@ -0,0 +1,53 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat Inc. 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 Inc.
+ *
+ * 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;
+
+import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
+import org.hibernate.type.Type;
+
+/**
+ * Allows programmatic access from {@link SQLQuery} to define how to map SQL {@link
java.sql.ResultSet results}
+ * back to in-memory objects, both entities as well as scalars. Essentially it defines
an API akin to the
+ * {@code <return/>}, {@code <return-scalar/>} and {@code
<return-join/>} elements under {@code <sql-query/>}
+ * definition in a Hibernate <tt>hbm.xml</tt> file.
+ *
+ * @author Steve Ebersole
+ */
+public interface SQLQueryResultMappingBuilder {
+
+ public static interface ReturnsHolder {
+ public void add(NativeSQLQueryReturn queryReturn);
+ }
+
+ public static class ScalarReturn {
+ private final ReturnsHolder returnsHolder;
+ private String name;
+ private Type type;
+
+ public ScalarReturn(ReturnsHolder returnsHolder) {
+ this.returnsHolder = returnsHolder;
+ }
+ }
+
+}
Modified: core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java
===================================================================
---
core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java 2010-07-22
19:52:43 UTC (rev 20013)
+++
core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java 2010-07-22
20:23:08 UTC (rev 20014)
@@ -68,7 +68,7 @@
Attribute cmAtt = queryElem.attribute( "comment" );
String comment = cmAtt == null ? null : cmAtt.getValue();
- java.util.List synchronizedTables = new ArrayList();
+ java.util.List<String> synchronizedTables = new ArrayList<String>();
Iterator tables = queryElem.elementIterator( "synchronize" );
while ( tables.hasNext() ) {
synchronizedTables.add( ( (Element) tables.next() ).attributeValue( "table"
) );
Modified: core/trunk/core/src/main/java/org/hibernate/classic/Session.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/classic/Session.java 2010-07-22 19:52:43
UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/classic/Session.java 2010-07-22 20:23:08
UTC (rev 20014)
@@ -31,6 +31,7 @@
import org.hibernate.HibernateException;
import org.hibernate.Query;
+import org.hibernate.SQLQuery;
import org.hibernate.type.Type;
/**
@@ -297,24 +298,34 @@
/**
* Create a new instance of <tt>Query</tt> for the given SQL string.
+ * <p/>
+ * Returned instances should all be {@link org.hibernate.SQLQuery}.
*
- * @deprecated will be replaced with a more Query like interface in later release
- *
* @param sql a query expressed in SQL
* @param returnAlias a table alias that appears inside <tt>{}</tt> in the
SQL string
* @param returnClass the returned persistent class
+ *
+ * @deprecated use {@link org.hibernate.SQLQuery#addRoot} or {@link
org.hibernate.SQLQuery#addEntity} variants
+ * instead to define the alias/class
*/
+ @Deprecated
+ @SuppressWarnings({ "JavaDoc" })
public Query createSQLQuery(String sql, String returnAlias, Class returnClass);
/**
* Create a new instance of <tt>Query</tt> for the given SQL string.
+ * <p/>
+ * Returned instances should all be {@link org.hibernate.SQLQuery}.
*
- * @deprecated will be replaced with a more Query like interface in later release
- *
* @param sql a query expressed in SQL
* @param returnAliases an array of table aliases that appear inside
<tt>{}</tt> in the SQL string
* @param returnClasses the returned persistent classes
+ *
+ * @deprecated use {@link org.hibernate.SQLQuery#addRoot} or {@link
org.hibernate.SQLQuery#addEntity} variants
+ * instead to define the aliases/classes
*/
+ @Deprecated
+ @SuppressWarnings({ "JavaDoc" })
public Query createSQLQuery(String sql, String[] returnAliases, Class[] returnClasses);
Modified: core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java
===================================================================
---
core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java 2010-07-22
19:52:43 UTC (rev 20013)
+++
core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java 2010-07-22
20:23:08 UTC (rev 20014)
@@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, Red Hat Inc. 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.
+ * distributed under license by Red Hat Inc.
*
* 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
@@ -20,7 +20,6 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
- *
*/
package org.hibernate.engine;
@@ -32,15 +31,14 @@
import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
/**
- * Definition of a named native SQL query, defined
- * in the mapping metadata.
+ * Definition of a named native SQL query, defined in the mapping metadata.
*
* @author Max Andersen
*/
public class NamedSQLQueryDefinition extends NamedQueryDefinition {
private NativeSQLQueryReturn[] queryReturns;
- private final List querySpaces;
+ private final List<String> querySpaces;
private final boolean callable;
private String resultSetRef;
@@ -66,7 +64,7 @@
public NamedSQLQueryDefinition(
String query,
NativeSQLQueryReturn[] queryReturns,
- List querySpaces,
+ List<String> querySpaces,
boolean cacheable,
String cacheRegion,
Integer timeout,
@@ -115,7 +113,7 @@
public NamedSQLQueryDefinition(
String query,
String resultSetRef,
- List querySpaces,
+ List<String> querySpaces,
boolean cacheable,
String cacheRegion,
Integer timeout,
@@ -165,7 +163,7 @@
public NamedSQLQueryDefinition(
String query,
String resultSetRef,
- List querySpaces,
+ List<String> querySpaces,
boolean cacheable,
String cacheRegion,
Integer timeout,
@@ -194,7 +192,7 @@
return queryReturns;
}
- public List getQuerySpaces() {
+ public List<String> getQuerySpaces() {
return querySpaces;
}
Modified:
core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java
===================================================================
---
core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java 2010-07-22
19:52:43 UTC (rev 20013)
+++
core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java 2010-07-22
20:23:08 UTC (rev 20014)
@@ -26,6 +26,8 @@
/**
* Describes a return in a native SQL query.
+ * <p/>
+ * IMPL NOTE : implementations should be immutable as they are used as part of cache keys
for result caching.
*
* @author Steve Ebersole
*/
Modified: core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java 2010-07-22 19:52:43
UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java 2010-07-22 20:23:08
UTC (rev 20014)
@@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, Red Hat Inc. 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.
+ * distributed under license by Red Hat Inc.
*
* 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
@@ -20,13 +20,13 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
- *
*/
package org.hibernate.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -53,28 +53,23 @@
import org.hibernate.engine.query.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
import org.hibernate.type.Type;
-import org.hibernate.util.CollectionHelper;
import org.hibernate.util.StringHelper;
/**
- * Implements SQL query passthrough.
+ * Implementation of the {@link SQLQuery} contract.
*
- * <pre>
- * <sql-query name="mySqlQuery">
- * <return alias="person" class="eg.Person"/>
- * SELECT {person}.NAME AS {person.name}, {person}.AGE AS {person.age}, {person}.SEX AS
{person.sex}
- * FROM PERSON {person} WHERE {person}.NAME LIKE 'Hiber%'
- * </sql-query>
- * </pre>
- *
* @author Max Andersen
+ * @author Steve Ebersole
*/
public class SQLQueryImpl extends AbstractQueryImpl implements SQLQuery {
- private final List queryReturns;
- private Collection querySpaces;
+ private List<NativeSQLQueryReturn> queryReturns;
+ private List<ReturnBuilder> queryReturnBuilders;
+ private boolean autoDiscoverTypes;
+
+ private Collection<String> querySpaces;
+
private final boolean callable;
- private boolean autodiscovertypes;
/**
* Constructs a SQLQueryImpl given a sql query defined in the mappings.
@@ -100,7 +95,7 @@
this.queryReturns = Arrays.asList( queryDef.getQueryReturns() );
}
else {
- this.queryReturns = new ArrayList();
+ this.queryReturns = new ArrayList<NativeSQLQueryReturn>();
}
this.querySpaces = queryDef.getQuerySpaces();
@@ -109,31 +104,16 @@
SQLQueryImpl(
final String sql,
- final List queryReturns,
- final Collection querySpaces,
- final FlushMode flushMode,
- boolean callable,
- final SessionImplementor session,
- ParameterMetadata parameterMetadata) {
- // TODO : absolutely no usages of this constructor form; can it go away?
- super( sql, flushMode, session, parameterMetadata );
- this.queryReturns = queryReturns;
- this.querySpaces = querySpaces;
- this.callable = callable;
- }
-
- SQLQueryImpl(
- final String sql,
final String returnAliases[],
final Class returnClasses[],
final LockMode[] lockModes,
final SessionImplementor session,
- final Collection querySpaces,
+ final Collection<String> querySpaces,
final FlushMode flushMode,
ParameterMetadata parameterMetadata) {
// TODO : this constructor form is *only* used from constructor directly below us; can
it go away?
super( sql, flushMode, session, parameterMetadata );
- queryReturns = new ArrayList(returnAliases.length);
+ queryReturns = new ArrayList<NativeSQLQueryReturn>( returnAliases.length );
for ( int i=0; i<returnAliases.length; i++ ) {
NativeSQLQueryRootReturn ret = new NativeSQLQueryRootReturn(
returnAliases[i],
@@ -157,15 +137,13 @@
SQLQueryImpl(String sql, SessionImplementor session, ParameterMetadata
parameterMetadata) {
super( sql, null, session, parameterMetadata );
- queryReturns = new ArrayList();
+ queryReturns = new ArrayList<NativeSQLQueryReturn>();
querySpaces = null;
callable = false;
}
- private static final NativeSQLQueryReturn[] NO_SQL_RETURNS = new
NativeSQLQueryReturn[0];
-
private NativeSQLQueryReturn[] getQueryReturns() {
- return ( NativeSQLQueryReturn[] ) queryReturns.toArray( NO_SQL_RETURNS );
+ return queryReturns.toArray( new NativeSQLQueryReturn[queryReturns.size()] );
}
public List list() throws HibernateException {
@@ -220,24 +198,25 @@
public QueryParameters getQueryParameters(Map namedParams) {
QueryParameters qp = super.getQueryParameters(namedParams);
qp.setCallable(callable);
- qp.setAutoDiscoverScalarTypes(autodiscovertypes);
+ qp.setAutoDiscoverScalarTypes( autoDiscoverTypes );
return qp;
}
protected void verifyParameters() {
+ // verifyParameters is called at the start of all execution type methods, so we use
that here to perform
+ // some preparation work.
+ prepare();
verifyParameters( callable );
boolean noReturns = queryReturns==null || queryReturns.isEmpty();
if ( noReturns ) {
- this.autodiscovertypes = noReturns;
+ this.autoDiscoverTypes = noReturns;
}
else {
- Iterator itr = queryReturns.iterator();
- while ( itr.hasNext() ) {
- NativeSQLQueryReturn rtn = ( NativeSQLQueryReturn ) itr.next();
- if ( rtn instanceof NativeSQLQueryScalarReturn ) {
- NativeSQLQueryScalarReturn scalar = ( NativeSQLQueryScalarReturn ) rtn;
+ for ( NativeSQLQueryReturn queryReturn : queryReturns ) {
+ if ( queryReturn instanceof NativeSQLQueryScalarReturn ) {
+ NativeSQLQueryScalarReturn scalar = (NativeSQLQueryScalarReturn) queryReturn;
if ( scalar.getType() == null ) {
- autodiscovertypes = true;
+ autoDiscoverTypes = true;
break;
}
}
@@ -245,6 +224,23 @@
}
}
+ private void prepare() {
+ if ( queryReturnBuilders != null ) {
+ if ( ! queryReturnBuilders.isEmpty() ) {
+ if ( queryReturns != null ) {
+ queryReturns.clear();
+ queryReturns = null;
+ }
+ queryReturns = new ArrayList<NativeSQLQueryReturn>();
+ for ( ReturnBuilder builder : queryReturnBuilders ) {
+ queryReturns.add( builder.buildReturn() );
+ }
+ queryReturnBuilders.clear();
+ }
+ queryReturnBuilders = null;
+ }
+ }
+
public String[] getReturnAliases() throws HibernateException {
throw new UnsupportedOperationException("SQL queries do not currently support
returning aliases");
}
@@ -266,23 +262,35 @@
return null;
}
- public SQLQuery addScalar(String columnAlias, Type type) {
- queryReturns.add( new NativeSQLQueryScalarReturn( columnAlias, type ) );
+ public SQLQuery addScalar(final String columnAlias, final Type type) {
+ if ( queryReturnBuilders == null ) {
+ queryReturnBuilders = new ArrayList<ReturnBuilder>();
+ }
+ queryReturnBuilders.add(
+ new ReturnBuilder() {
+ public NativeSQLQueryReturn buildReturn() {
+ return new NativeSQLQueryScalarReturn( columnAlias, type );
+ }
+ }
+ );
return this;
}
public SQLQuery addScalar(String columnAlias) {
- autodiscovertypes = true;
- queryReturns.add( new NativeSQLQueryScalarReturn( columnAlias, null ) );
- return this;
+ return addScalar( columnAlias, null );
}
- public SQLQuery addJoin(String alias, String path) {
- return addJoin(alias, path, LockMode.READ);
+ public RootReturn addRoot(String tableAlias, String entityName) {
+ RootReturnBuilder builder = new RootReturnBuilder( tableAlias, entityName );
+ if ( queryReturnBuilders == null ) {
+ queryReturnBuilders = new ArrayList<ReturnBuilder>();
+ }
+ queryReturnBuilders.add( builder );
+ return builder;
}
- public SQLQuery addEntity(Class entityClass) {
- return addEntity( StringHelper.unqualify( entityClass.getName() ), entityClass );
+ public RootReturn addRoot(String tableAlias, Class entityType) {
+ return addRoot( tableAlias, entityType.getName() );
}
public SQLQuery addEntity(String entityName) {
@@ -290,49 +298,74 @@
}
public SQLQuery addEntity(String alias, String entityName) {
- return addEntity(alias, entityName, LockMode.READ);
+ addRoot( alias, entityName );
+ return this;
}
+ public SQLQuery addEntity(String alias, String entityName, LockMode lockMode) {
+ addRoot( alias, entityName ).setLockMode( lockMode );
+ return this;
+ }
+
+ public SQLQuery addEntity(Class entityType) {
+ return addEntity( entityType.getName() );
+ }
+
public SQLQuery addEntity(String alias, Class entityClass) {
return addEntity( alias, entityClass.getName() );
}
- public SQLQuery addJoin(String alias, String path, LockMode lockMode) {
+ public SQLQuery addEntity(String alias, Class entityClass, LockMode lockMode) {
+ return addEntity( alias, entityClass.getName(), lockMode );
+ }
+
+ public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String
joinPropertyName) {
+ FetchReturnBuilder builder = new FetchReturnBuilder( tableAlias, ownerTableAlias,
joinPropertyName );
+ if ( queryReturnBuilders == null ) {
+ queryReturnBuilders = new ArrayList<ReturnBuilder>();
+ }
+ queryReturnBuilders.add( builder );
+ return builder;
+ }
+
+ public SQLQuery addJoin(String tableAlias, String ownerTableAlias, String
joinPropertyName) {
+ addFetch( tableAlias, ownerTableAlias, joinPropertyName );
+ return this;
+ }
+
+ public SQLQuery addJoin(String alias, String path) {
+ createFetchJoin( alias, path );
+ return this;
+ }
+
+ private FetchReturn createFetchJoin(String tableAlias, String path) {
int loc = path.indexOf('.');
if ( loc < 0 ) {
throw new QueryException( "not a property path: " + path );
}
- String ownerAlias = path.substring(0, loc);
- String role = path.substring(loc+1);
- queryReturns.add( new NativeSQLQueryJoinReturn(alias, ownerAlias, role,
CollectionHelper.EMPTY_MAP, lockMode) );
- return this;
+ final String ownerTableAlias = path.substring( 0, loc );
+ final String joinedPropertyName = path.substring( loc+1 );
+ return addFetch( tableAlias, ownerTableAlias, joinedPropertyName );
}
- public SQLQuery addEntity(String alias, String entityName, LockMode lockMode) {
- queryReturns.add( new NativeSQLQueryRootReturn(alias, entityName, lockMode) );
+ public SQLQuery addJoin(String alias, String path, LockMode lockMode) {
+ createFetchJoin( alias, path ).setLockMode( lockMode );
return this;
}
- public SQLQuery addEntity(String alias, Class entityClass, LockMode lockMode) {
- return addEntity( alias, entityClass.getName(), lockMode );
- }
-
public SQLQuery setResultSetMapping(String name) {
ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( name );
if ( mapping == null ) {
throw new MappingException( "Unknown SqlResultSetMapping [" + name +
"]" );
}
NativeSQLQueryReturn[] returns = mapping.getQueryReturns();
- int length = returns.length;
- for ( int index = 0 ; index < length ; index++ ) {
- queryReturns.add( returns[index] );
- }
+ queryReturns.addAll( Arrays.asList( returns ) );
return this;
}
public SQLQuery addSynchronizedQuerySpace(String querySpace) {
if ( querySpaces == null ) {
- querySpaces = new ArrayList();
+ querySpaces = new ArrayList<String>();
}
querySpaces.add( querySpace );
return this;
@@ -349,11 +382,9 @@
private SQLQuery addQuerySpaces(Serializable[] spaces) {
if ( spaces != null ) {
if ( querySpaces == null ) {
- querySpaces = new ArrayList();
+ querySpaces = new ArrayList<String>();
}
- for ( int i = 0; i < spaces.length; i++ ) {
- querySpaces.add( spaces[i] );
- }
+ querySpaces.addAll( Arrays.asList( (String[]) spaces ) );
}
return this;
}
@@ -372,4 +403,97 @@
}
}
+ private class RootReturnBuilder implements RootReturn, ReturnBuilder {
+ private final String alias;
+ private final String entityName;
+ private LockMode lockMode = LockMode.READ;
+ private Map<String,List<String>> propertyMappings;
+
+ private RootReturnBuilder(String alias, String entityName) {
+ this.alias = alias;
+ this.entityName = entityName;
+ }
+
+ public RootReturn setLockMode(LockMode lockMode) {
+ this.lockMode = lockMode;
+ return this;
+ }
+
+ public RootReturn setDiscriminatorAlias(String alias) {
+ addProperty( "class", alias );
+ return this;
+ }
+
+ public RootReturn addProperty(String propertyName, String columnAlias) {
+ addProperty( propertyName ).addColumnAlias( columnAlias );
+ return this;
+ }
+
+ public ReturnProperty addProperty(final String propertyName) {
+ if ( propertyMappings == null ) {
+ propertyMappings = new HashMap<String,List<String>>();
+ }
+ return new ReturnProperty() {
+ public ReturnProperty addColumnAlias(String columnAlias) {
+ List<String> columnAliases = propertyMappings.get( propertyName );
+ if ( columnAliases == null ) {
+ columnAliases = new ArrayList<String>();
+ }
+ columnAliases.add( columnAlias );
+ return this;
+ }
+ };
+ }
+
+ public NativeSQLQueryReturn buildReturn() {
+ return new NativeSQLQueryRootReturn( alias, entityName, propertyMappings, lockMode );
+ }
+ }
+ private class FetchReturnBuilder implements FetchReturn, ReturnBuilder {
+ private final String alias;
+ private String ownerTableAlias;
+ private final String joinedPropertyName;
+ private LockMode lockMode = LockMode.READ;
+ private Map<String,List<String>> propertyMappings;
+
+ private FetchReturnBuilder(String alias, String ownerTableAlias, String
joinedPropertyName) {
+ this.alias = alias;
+ this.ownerTableAlias = ownerTableAlias;
+ this.joinedPropertyName = joinedPropertyName;
+ }
+
+ public FetchReturn setLockMode(LockMode lockMode) {
+ this.lockMode = lockMode;
+ return this;
+ }
+
+ public FetchReturn addProperty(String propertyName, String columnAlias) {
+ addProperty( propertyName ).addColumnAlias( columnAlias );
+ return this;
+ }
+
+ public ReturnProperty addProperty(final String propertyName) {
+ if ( propertyMappings == null ) {
+ propertyMappings = new HashMap<String,List<String>>();
+ }
+ return new ReturnProperty() {
+ public ReturnProperty addColumnAlias(String columnAlias) {
+ List<String> columnAliases = propertyMappings.get( propertyName );
+ if ( columnAliases == null ) {
+ columnAliases = new ArrayList<String>();
+ }
+ columnAliases.add( columnAlias );
+ return this;
+ }
+ };
+ }
+
+ public NativeSQLQueryReturn buildReturn() {
+ return new NativeSQLQueryJoinReturn( alias, ownerTableAlias, joinedPropertyName,
propertyMappings, lockMode );
+ }
+ }
+
+ private interface ReturnBuilder {
+ NativeSQLQueryReturn buildReturn();
+ }
}
Modified:
core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java
===================================================================
---
core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java 2010-07-22
19:52:43 UTC (rev 20013)
+++
core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java 2010-07-22
20:23:08 UTC (rev 20014)
@@ -13,9 +13,14 @@
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
+import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.dialect.H2Dialect;
+import org.hibernate.type.FloatType;
+import org.hibernate.type.LongType;
+import org.hibernate.type.StringType;
+import org.hibernate.type.TimestampType;
import org.hibernate.util.ArrayHelper;
import org.hibernate.test.sql.hand.Organization;
import org.hibernate.test.sql.hand.Person;
@@ -41,6 +46,7 @@
*
* @author Steve Ebersole
*/
+@SuppressWarnings({ "UnnecessaryBoxing", "UnnecessaryUnboxing" })
public class NativeSQLQueriesTest extends FunctionalTestCase {
public NativeSQLQueriesTest(String x) {
@@ -606,6 +612,87 @@
}
+ public void testExplicitReturnAPI() {
+ Session s = openSession();
+ s.beginTransaction();
+ Organization jboss = new Organization( "JBoss" );
+ Person me = new Person( "Steve" );
+ Employment emp = new Employment( me, jboss, "US" );
+ Serializable jbossId = s.save( jboss );
+ s.save( me );
+ s.save( emp );
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+
+ String sql =
+ "SELECT org.ORGID as orgid," +
+ " org.NAME as name," +
+ " emp.EMPLOYER as employer," +
+ " emp.EMPID as empid," +
+ " emp.EMPLOYEE as employee," +
+ " emp.EMPLOYER as employer," +
+ " emp.STARTDATE as startDate," +
+ " emp.ENDDATE as endDate," +
+ " emp.REGIONCODE as regionCode," +
+ " emp.VALUE as VALUE," +
+ " emp.CURRENCY as CURRENCY" +
+ " FROM ORGANIZATION org" +
+ " LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER";
+
+ // as a control, lets apply an existing rs mapping
+ SQLQuery sqlQuery = s.createSQLQuery( sql );
+ sqlQuery.setResultSetMapping( "org-description" );
+ sqlQuery.list();
+
+ // next try a partial mapping def
+ sqlQuery.addRoot( "org", Organization.class );
+ sqlQuery.addFetch( "emp", "org", "employments" );
+ sqlQuery.list();
+
+ // now try full explicit mappings
+ sqlQuery.addRoot( "org", Organization.class )
+ .addProperty( "id", "orgid" )
+ .addProperty( "name" ).addColumnAlias( "name" );
+ sqlQuery.addFetch( "emp", "org", "employments" )
+ .addProperty( "key", "employer" )
+ .addProperty( "element", "empid" )
+ .addProperty( "element.employee", "employee" )
+ .addProperty( "element.employer", "employer" )
+ .addProperty( "element.startDate", "startDate" )
+ .addProperty( "element.endDate", "endDate" )
+ .addProperty( "element.regionCode", "regionCode" )
+ .addProperty( "element.employmentId", "empId" )
+ .addProperty( "element.salary" ).addColumnAlias( "VALUE"
).addColumnAlias( "CURRENCY" );
+ sqlQuery.list();
+
+ // lets try a totally different approach now and pull back scalars, first with explicit
types
+ sqlQuery.addScalar( "orgid", LongType.INSTANCE )
+ .addScalar( "name", StringType.INSTANCE )
+ .addScalar( "empid", LongType.INSTANCE )
+ .addScalar( "employee", LongType.INSTANCE )
+ .addScalar( "startDate", TimestampType.INSTANCE )
+ .addScalar( "endDate", TimestampType.INSTANCE )
+ .addScalar( "regionCode", StringType.INSTANCE )
+ .addScalar( "empId", LongType.INSTANCE )
+ .addScalar( "VALUE", FloatType.INSTANCE )
+ .addScalar( "CURRENCY", StringType.INSTANCE );
+
+
+ s.getTransaction().commit();
+ s.close();
+
+ s = openSession();
+ s.beginTransaction();
+ s.delete( emp );
+ s.delete( jboss );
+ s.delete( me );
+ s.getTransaction().commit();
+ s.close();
+ }
+
public void testMixAndMatchEntityScalar() {
Session s = openSession();
Transaction t = s.beginTransaction();