HHH-3409 : ResultTransformer uniqueing

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@14995 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2008-07-30 20:55:24 +00:00
parent 280f722a32
commit 1851bffce7
10 changed files with 426 additions and 92 deletions

View File

@ -29,29 +29,64 @@ import java.util.List;
import org.hibernate.QueryException; import org.hibernate.QueryException;
/**
* Wraps the tuples in a constructor call.
*
* todo : why Alias* in the name???
*/
public class AliasToBeanConstructorResultTransformer implements ResultTransformer { public class AliasToBeanConstructorResultTransformer implements ResultTransformer {
private Constructor constructor; private final Constructor constructor;
/**
* Instantiates a AliasToBeanConstructorResultTransformer.
*
* @param constructor The contructor in which to wrap the tuples.
*/
public AliasToBeanConstructorResultTransformer(Constructor constructor) { public AliasToBeanConstructorResultTransformer(Constructor constructor) {
this.constructor = constructor; this.constructor = constructor;
} }
/**
* Wrap the incoming tuples in a call to our configured constructor.
*/
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
try { try {
return constructor.newInstance( tuple ); return constructor.newInstance( tuple );
} }
catch ( Exception e ) { catch ( Exception e ) {
throw new QueryException( throw new QueryException(
"could not instantiate: " + "could not instantiate class [" + constructor.getDeclaringClass().getName() + "] from tuple",
constructor.getDeclaringClass().getName(), e
e ); );
} }
} }
/**
* {@inheritDoc}
*/
public List transformList(List collection) { public List transformList(List collection) {
return collection; return collection;
} }
/**
* Define our hashCode by our defined constructor's hasCode.
*
* @return Our defined ctor hashCode
*/
public int hashCode() {
return constructor.hashCode();
}
/**
* 2 AliasToBeanConstructorResultTransformer are considered equal if they have the same
* defined constructor.
*
* @param other The other instance to check for equality.
* @return True if both have the same defined constuctor; false otherwise.
*/
public boolean equals(Object other) {
return other instanceof AliasToBeanConstructorResultTransformer
&& constructor.equals( ( ( AliasToBeanConstructorResultTransformer ) other ).constructor );
}
} }

View File

@ -33,64 +33,76 @@ import org.hibernate.property.PropertyAccessorFactory;
import org.hibernate.property.Setter; import org.hibernate.property.Setter;
/** /**
* Result transformer that allows to transform a result to * Result transformer that allows to transform a result to
* a user specified class which will be populated via setter * a user specified class which will be populated via setter
* methods or fields matching the alias names. * methods or fields matching the alias names.
* * <p/>
* <pre> * <pre>
* List resultWithAliasedBean = s.createCriteria(Enrolment.class) * List resultWithAliasedBean = s.createCriteria(Enrolment.class)
* .createAlias("student", "st") * .createAlias("student", "st")
* .createAlias("course", "co") * .createAlias("course", "co")
* .setProjection( Projections.projectionList() * .setProjection( Projections.projectionList()
* .add( Projections.property("co.description"), "courseDescription" ) * .add( Projections.property("co.description"), "courseDescription" )
* ) * )
* .setResultTransformer( new AliasToBeanResultTransformer(StudentDTO.class) ) * .setResultTransformer( new AliasToBeanResultTransformer(StudentDTO.class) )
* .list(); * .list();
* * <p/>
* StudentDTO dto = (StudentDTO)resultWithAliasedBean.get(0); * StudentDTO dto = (StudentDTO)resultWithAliasedBean.get(0);
* </pre> * </pre>
* *
* @author max * @author max
*
*/ */
public class AliasToBeanResultTransformer implements ResultTransformer { public class AliasToBeanResultTransformer implements ResultTransformer {
// IMPL NOTE : due to the delayed population of setters (setters cached
// for performance), we really cannot pro0perly define equality for
// this transformer
private final Class resultClass; private final Class resultClass;
private final PropertyAccessor propertyAccessor;
private Setter[] setters; private Setter[] setters;
private PropertyAccessor propertyAccessor;
public AliasToBeanResultTransformer(Class resultClass) { public AliasToBeanResultTransformer(Class resultClass) {
if(resultClass==null) throw new IllegalArgumentException("resultClass cannot be null"); if ( resultClass == null ) {
throw new IllegalArgumentException( "resultClass cannot be null" );
}
this.resultClass = resultClass; this.resultClass = resultClass;
propertyAccessor = new ChainedPropertyAccessor(new PropertyAccessor[] { PropertyAccessorFactory.getPropertyAccessor(resultClass,null), PropertyAccessorFactory.getPropertyAccessor("field")}); propertyAccessor = new ChainedPropertyAccessor(
new PropertyAccessor[] {
PropertyAccessorFactory.getPropertyAccessor( resultClass, null ),
PropertyAccessorFactory.getPropertyAccessor( "field" )
}
);
} }
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
Object result; Object result;
try { try {
if(setters==null) { if ( setters == null ) {
setters = new Setter[aliases.length]; setters = new Setter[aliases.length];
for (int i = 0; i < aliases.length; i++) { for ( int i = 0; i < aliases.length; i++ ) {
String alias = aliases[i]; String alias = aliases[i];
if(alias != null) { if ( alias != null ) {
setters[i] = propertyAccessor.getSetter(resultClass, alias); setters[i] = propertyAccessor.getSetter( resultClass, alias );
} }
} }
} }
result = resultClass.newInstance(); result = resultClass.newInstance();
for (int i = 0; i < aliases.length; i++) { for ( int i = 0; i < aliases.length; i++ ) {
if(setters[i]!=null) { if ( setters[i] != null ) {
setters[i].set(result, tuple[i], null); setters[i].set( result, tuple[i], null );
} }
} }
} catch (InstantiationException e) {
throw new HibernateException("Could not instantiate resultclass: " + resultClass.getName());
} catch (IllegalAccessException e) {
throw new HibernateException("Could not instantiate resultclass: " + resultClass.getName());
} }
catch ( InstantiationException e ) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
}
catch ( IllegalAccessException e ) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
}
return result; return result;
} }
@ -98,4 +110,10 @@ public class AliasToBeanResultTransformer implements ResultTransformer {
return collection; return collection;
} }
public int hashCode() {
int result;
result = resultClass.hashCode();
result = 31 * result + propertyAccessor.hashCode();
return result;
}
} }

View File

@ -25,13 +25,31 @@
package org.hibernate.transform; package org.hibernate.transform;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.io.Serializable;
/** /**
* {@link ResultTransformer} implementation which builds a map for each "row",
* made up of each aliased value where the alias is the map key.
* <p/>
* Since this transformer is stateless, all instances would be considered equal.
* So for optimization purposes we limit it to a single, singleton {@link #INSTANCE instance}.
*
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
*/ */
public class AliasToEntityMapResultTransformer implements ResultTransformer { public class AliasToEntityMapResultTransformer extends BasicTransformerAdapter implements Serializable {
public static final AliasToEntityMapResultTransformer INSTANCE = new AliasToEntityMapResultTransformer();
/**
* Instantiate AliasToEntityMapResultTransformer.
*
* @deprecated Use the {@link #INSTANCE} reference instead of explicitly creating a new one.
*/
public AliasToEntityMapResultTransformer() {
// todo : make private
}
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
Map result = new HashMap(tuple.length); Map result = new HashMap(tuple.length);
@ -44,8 +62,38 @@ public class AliasToEntityMapResultTransformer implements ResultTransformer {
return result; return result;
} }
public List transformList(List collection) { /**
return collection; * Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
} }
// all AliasToEntityMapResultTransformer are considered equal ~~~~~~~~~~~~~
/**
* All AliasToEntityMapResultTransformer are considered equal
*
* @param other The other instance to check for equality
* @return True if (non-null) other is a instance of
* AliasToEntityMapResultTransformer.
*/
public boolean equals(Object other) {
// todo : we can remove this once the deprecated ctor can be made private...
return other != null && AliasToEntityMapResultTransformer.class.isInstance( other );
}
/**
* All AliasToEntityMapResultTransformer are considered equal
*
* @return We simply return the hashCode of the
* AliasToEntityMapResultTransformer class name string.
*/
public int hashCode() {
// todo : we can remove this once the deprecated ctor can be made private...
return getClass().getName().hashCode();
}
} }

View File

@ -0,0 +1,42 @@
/*
* 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.transform;
import java.util.List;
/**
* Provides the basic "noop" impls of the {@link ResultTransformer} contract.
*
* @author Steve Ebersole
*/
public abstract class BasicTransformerAdapter implements ResultTransformer {
public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple;
}
public List transformList(List list) {
return list;
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.transform;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Distinctions the result tuples in the final result based on the defined
* equality of the tuples.
* <p/>
* Since this transformer is stateless, all instances would be considered equal.
* So for optimization purposes we limit it to a single, singleton {@link #INSTANCE instance}.
*
* @author Steve Ebersole
*/
public class DistinctResultTransformer extends BasicTransformerAdapter implements Serializable {
public static final DistinctResultTransformer INSTANCE = new DistinctResultTransformer();
private static final Logger log = LoggerFactory.getLogger( DistinctResultTransformer.class );
/**
* Helper class to handle distincting
*/
private static final class Identity {
final Object entity;
private Identity(Object entity) {
this.entity = entity;
}
/**
* {@inheritDoc}
*/
public boolean equals(Object other) {
return Identity.class.isInstance( other )
&& this.entity == ( ( Identity ) other ).entity;
}
/**
* {@inheritDoc}
*/
public int hashCode() {
return System.identityHashCode( entity );
}
}
/**
* Disallow instantiation of DistinctResultTransformer.
*/
private DistinctResultTransformer() {
}
/**
* Uniquely distinct each tuple row here.
*/
public List transformList(List list) {
List result = new ArrayList( list.size() );
Set distinct = new HashSet();
for ( int i = 0; i < list.size(); i++ ) {
Object entity = list.get( i );
if ( distinct.add( new Identity( entity ) ) ) {
result.add( entity );
}
}
if ( log.isDebugEnabled() ) {
log.debug(
"transformed: " +
list.size() + " rows to: " +
result.size() + " distinct results"
);
}
return result;
}
/**
* Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
}
}

View File

@ -24,54 +24,63 @@
*/ */
package org.hibernate.transform; package org.hibernate.transform;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.io.Serializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Much like {@link RootEntityResultTransformer}, but we also distinct
* the entity in the final result.
* <p/>
* Since this transformer is stateless, all instances would be considered equal.
* So for optimization purposes we limit it to a single, singleton {@link #INSTANCE instance}.
*
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
*/ */
public class DistinctRootEntityResultTransformer implements ResultTransformer { public class DistinctRootEntityResultTransformer implements ResultTransformer, Serializable {
private static final Logger log = LoggerFactory.getLogger(DistinctRootEntityResultTransformer.class); public static final DistinctRootEntityResultTransformer INSTANCE = new DistinctRootEntityResultTransformer();
static final class Identity { /**
final Object entity; * Instantiate a DistinctRootEntityResultTransformer.
Identity(Object entity) { *
this.entity = entity; * @deprecated Use the {@link #INSTANCE} reference instead of explicitly creating a new one.
} */
public boolean equals(Object other) { public DistinctRootEntityResultTransformer() {
Identity that = (Identity) other;
return entity==that.entity;
}
public int hashCode() {
return System.identityHashCode(entity);
}
} }
/**
* Simply delegates to {@link RootEntityResultTransformer#transformTuple}.
*
* @param tuple The tuple to transform
* @param aliases The tuple aliases
* @return The transformed tuple row.
*/
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple[ tuple.length-1 ]; return RootEntityResultTransformer.INSTANCE.transformTuple( tuple, aliases );
} }
/**
* Simply delegates to {@link DistinctResultTransformer#transformList}.
*
* @param list The list to transform.
* @return The transformed List.
*/
public List transformList(List list) { public List transformList(List list) {
List result = new ArrayList( list.size() ); return DistinctResultTransformer.INSTANCE.transformList( list );
Set distinct = new HashSet();
for ( int i=0; i<list.size(); i++ ) {
Object entity = list.get(i);
if ( distinct.add( new Identity(entity) ) ) {
result.add(entity);
}
}
if ( log.isDebugEnabled() ) log.debug(
"transformed: " +
list.size() + " rows to: " +
result.size() + " distinct results"
);
return result;
} }
/**
* Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
}
public boolean equals(Object obj) {
// todo : we can remove this once the deprecated ctor can be made private...
return DistinctRootEntityResultTransformer.class.isInstance( obj );
}
} }

View File

@ -24,19 +24,41 @@
*/ */
package org.hibernate.transform; package org.hibernate.transform;
import java.util.List; import java.io.Serializable;
/** /**
* ???
*
* @author max * @author max
*/ */
public class PassThroughResultTransformer implements ResultTransformer { public class PassThroughResultTransformer extends BasicTransformerAdapter implements Serializable {
public static final PassThroughResultTransformer INSTANCE = new PassThroughResultTransformer();
/**
* Instamtiate a PassThroughResultTransformer.
*
* @deprecated Use the {@link #INSTANCE} reference instead of explicitly creating a new one.
*/
public PassThroughResultTransformer() {
}
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple.length==1 ? tuple[0] : tuple; return tuple.length==1 ? tuple[0] : tuple;
} }
public List transformList(List collection) { /**
return collection; * Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
}
public boolean equals(Object obj) {
// todo : we can remove this once the deprecated ctor can be made private...
return PassThroughResultTransformer.class.isInstance( obj );
} }
} }

View File

@ -25,18 +25,48 @@
package org.hibernate.transform; package org.hibernate.transform;
import java.util.List; import java.util.List;
import java.io.Serializable;
/** /**
* {@link ResultTransformer} implementation which limits the result tuple
* to only the "root entity".
* <p/>
* Since this transformer is stateless, all instances would be considered equal.
* So for optimization purposes we limit it to a single, singleton {@link #INSTANCE instance}.
*
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
*/ */
public class RootEntityResultTransformer implements ResultTransformer { public final class RootEntityResultTransformer extends BasicTransformerAdapter implements Serializable {
public static final RootEntityResultTransformer INSTANCE = new RootEntityResultTransformer();
/**
* Instantiate RootEntityResultTransformer.
*
* @deprecated Use the {@link #INSTANCE} reference instead of explicitly creating a new one.
*/
public RootEntityResultTransformer() {
}
/**
* Return just the root entity from the row tuple.
*/
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
return tuple[ tuple.length-1 ]; return tuple[ tuple.length-1 ];
} }
public List transformList(List collection) { /**
return collection; * Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
} }
public boolean equals(Object obj) {
// todo : we can remove this once the deprecated ctor can be made private...
return RootEntityResultTransformer.class.isInstance( obj );
}
} }

View File

@ -26,19 +26,35 @@ package org.hibernate.transform;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.io.Serializable;
public class ToListResultTransformer implements ResultTransformer { /**
* Tranforms each result row from a tuple into a {@link List}, such that what
* you end up with is a {@link List} of {@link List Lists}.
*/
public class ToListResultTransformer extends BasicTransformerAdapter implements Serializable {
public static final ResultTransformer INSTANCE = new ToListResultTransformer(); public static final ToListResultTransformer INSTANCE = new ToListResultTransformer();
private ToListResultTransformer() {} /**
* Disallow instantiation of ToListResultTransformer.
*/
private ToListResultTransformer() {
}
/**
* {@inheritDoc}
*/
public Object transformTuple(Object[] tuple, String[] aliases) { public Object transformTuple(Object[] tuple, String[] aliases) {
return Arrays.asList(tuple); return Arrays.asList( tuple );
} }
public List transformList(List collection) { /**
return collection; * Serialization hook for ensuring singleton uniqueing.
*
* @return The singleton instance : {@link #INSTANCE}
*/
private Object readResolve() {
return INSTANCE;
} }
} }

View File

@ -31,7 +31,8 @@ final public class Transformers {
/** /**
* Each row of results is a <tt>Map</tt> from alias to values/entities * Each row of results is a <tt>Map</tt> from alias to values/entities
*/ */
public static final ResultTransformer ALIAS_TO_ENTITY_MAP = new AliasToEntityMapResultTransformer(); public static final AliasToEntityMapResultTransformer ALIAS_TO_ENTITY_MAP =
AliasToEntityMapResultTransformer.INSTANCE;
/** /**
* Each row of results is a <tt>List</tt> * Each row of results is a <tt>List</tt>