HHH-2308 - Adding predicates to the join condition using Criteria Query

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18012 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2009-11-20 05:20:28 +00:00
parent a3d1a3c59f
commit a8e34f0066
11 changed files with 293 additions and 42 deletions

View File

@ -135,7 +135,10 @@ public interface Criteria extends CriteriaSpecification {
*
* @param associationPath a dot seperated property path
* @param mode The fetch mode for the referenced association
*
* @return this (for method chaining)
*
* @throws HibernateException Indicates a problem applying the given fetch mode
*/
public Criteria setFetchMode(String associationPath, FetchMode mode) throws HibernateException;
@ -143,6 +146,7 @@ public interface Criteria extends CriteriaSpecification {
* Set the lock mode of the current entity
*
* @param lockMode The lock mode to be applied
*
* @return this (for method chaining)
*/
public Criteria setLockMode(LockMode lockMode);
@ -151,8 +155,9 @@ public interface Criteria extends CriteriaSpecification {
* Set the lock mode of the aliased entity
*
* @param alias The previously assigned alias representing the entity to
* which the given lock mode should apply.
* which the given lock mode should apply.
* @param lockMode The lock mode to be applied
*
* @return this (for method chaining)
*/
public Criteria setLockMode(String alias, LockMode lockMode);
@ -165,7 +170,10 @@ public interface Criteria extends CriteriaSpecification {
*
* @param associationPath A dot-seperated property path
* @param alias The alias to assign to the joined association (for later reference).
*
* @return this (for method chaining)
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createAlias(String associationPath, String alias) throws HibernateException;
@ -179,10 +187,31 @@ public interface Criteria extends CriteriaSpecification {
* @param associationPath A dot-seperated property path
* @param alias The alias to assign to the joined association (for later reference).
* @param joinType The type of join to use.
*
* @return this (for method chaining)
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createAlias(String associationPath, String alias, int joinType) throws HibernateException;
/**
* Join an association using the specified join-type, assigning an alias
* to the joined association.
* <p/>
* The joinType is expected to be one of {@link #INNER_JOIN} (the default),
* {@link #FULL_JOIN}, or {@link #LEFT_JOIN}.
*
* @param associationPath A dot-seperated property path
* @param alias The alias to assign to the joined association (for later reference).
* @param joinType The type of join to use.
* @param withClause The criteria to be added to the join condition (<tt>ON</tt> clause)
*
* @return this (for method chaining)
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createAlias(String associationPath, String alias, int joinType, Criterion withClause) throws HibernateException;
/**
* Create a new <tt>Criteria</tt>, "rooted" at the associated entity.
* <p/>
@ -190,7 +219,10 @@ public interface Criteria extends CriteriaSpecification {
* {@link #INNER_JOIN} for the joinType.
*
* @param associationPath A dot-seperated property path
*
* @return the created "sub criteria"
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createCriteria(String associationPath) throws HibernateException;
@ -200,7 +232,10 @@ public interface Criteria extends CriteriaSpecification {
*
* @param associationPath A dot-seperated property path
* @param joinType The type of join to use.
*
* @return the created "sub criteria"
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createCriteria(String associationPath, int joinType) throws HibernateException;
@ -213,7 +248,10 @@ public interface Criteria extends CriteriaSpecification {
*
* @param associationPath A dot-seperated property path
* @param alias The alias to assign to the joined association (for later reference).
*
* @return the created "sub criteria"
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createCriteria(String associationPath, String alias) throws HibernateException;
@ -224,10 +262,28 @@ public interface Criteria extends CriteriaSpecification {
* @param associationPath A dot-seperated property path
* @param alias The alias to assign to the joined association (for later reference).
* @param joinType The type of join to use.
*
* @return the created "sub criteria"
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createCriteria(String associationPath, String alias, int joinType) throws HibernateException;
/**
* Create a new <tt>Criteria</tt>, "rooted" at the associated entity,
* assigning the given alias and using the specified join type.
*
* @param associationPath A dot-seperated property path
* @param alias The alias to assign to the joined association (for later reference).
* @param joinType The type of join to use.
* @param withClause The criteria to be added to the join condition (<tt>ON</tt> clause)
*
* @return the created "sub criteria"
*
* @throws HibernateException Indicates a problem creating the sub criteria
*/
public Criteria createCriteria(String associationPath, String alias, int joinType, Criterion withClause) throws HibernateException;
/**
* Set a strategy for handling the query results. This determines the
* "shape" of the query result.
@ -327,6 +383,9 @@ public interface Criteria extends CriteriaSpecification {
* Get the results.
*
* @return The list of matched query results.
*
* @throws HibernateException Indicates a problem either translating the criteria to SQL,
* exeucting the SQL or processing the SQL results.
*/
public List list() throws HibernateException;
@ -335,6 +394,9 @@ public interface Criteria extends CriteriaSpecification {
*
* @return The {@link ScrollableResults} representing the matched
* query results.
*
* @throws HibernateException Indicates a problem either translating the criteria to SQL,
* exeucting the SQL or processing the SQL results.
*/
public ScrollableResults scroll() throws HibernateException;
@ -344,8 +406,12 @@ public interface Criteria extends CriteriaSpecification {
*
* @param scrollMode Indicates the type of underlying database cursor to
* request.
*
* @return The {@link ScrollableResults} representing the matched
* query results.
*
* @throws HibernateException Indicates a problem either translating the criteria to SQL,
* exeucting the SQL or processing the SQL results.
*/
public ScrollableResults scroll(ScrollMode scrollMode) throws HibernateException;
@ -358,4 +424,4 @@ public interface Criteria extends CriteriaSpecification {
*/
public Object uniqueResult() throws HibernateException;
}
}

View File

@ -80,7 +80,7 @@ public class CriteriaImpl implements Criteria, Serializable {
private CacheMode cacheMode;
private FlushMode sessionFlushMode;
private CacheMode sessionCacheMode;
private ResultTransformer resultTransformer = Criteria.ROOT_ENTITY;
@ -202,6 +202,11 @@ public class CriteriaImpl implements Criteria, Serializable {
return this;
}
public Criteria createAlias(String associationPath, String alias, int joinType, Criterion withClause) {
new Subcriteria( this, associationPath, alias, joinType, withClause );
return this;
}
public Criteria createCriteria(String associationPath) {
return createCriteria( associationPath, INNER_JOIN );
}
@ -218,6 +223,10 @@ public class CriteriaImpl implements Criteria, Serializable {
return new Subcriteria( this, associationPath, alias, joinType );
}
public Criteria createCriteria(String associationPath, String alias, int joinType, Criterion withClause) {
return new Subcriteria( this, associationPath, alias, joinType, withClause );
}
public ResultTransformer getResultTransformer() {
return resultTransformer;
}
@ -309,7 +318,7 @@ public class CriteriaImpl implements Criteria, Serializable {
after();
}
}
public ScrollableResults scroll() {
return scroll( ScrollMode.SCROLL_INSENSITIVE );
}
@ -338,7 +347,7 @@ public class CriteriaImpl implements Criteria, Serializable {
getSession().setCacheMode( cacheMode );
}
}
protected void after() {
if ( sessionFlushMode != null ) {
getSession().setFlushMode( sessionFlushMode );
@ -349,7 +358,7 @@ public class CriteriaImpl implements Criteria, Serializable {
sessionCacheMode = null;
}
}
public boolean isLookupByNaturalKey() {
if ( projection != null ) {
return false;
@ -374,16 +383,21 @@ public class CriteriaImpl implements Criteria, Serializable {
private Criteria parent;
private LockMode lockMode;
private int joinType;
private Criterion withClause;
// Constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private Subcriteria(Criteria parent, String path, String alias, int joinType) {
private Subcriteria(Criteria parent, String path, String alias, int joinType, Criterion withClause) {
this.alias = alias;
this.path = path;
this.parent = parent;
this.joinType = joinType;
CriteriaImpl.this.subcriteriaList.add(this);
this.withClause = withClause;
CriteriaImpl.this.subcriteriaList.add( this );
}
private Subcriteria(Criteria parent, String path, String alias, int joinType) {
this( parent, path, alias, joinType, null );
}
private Subcriteria(Criteria parent, String path, int joinType) {
@ -391,10 +405,10 @@ public class CriteriaImpl implements Criteria, Serializable {
}
public String toString() {
return "Subcriteria(" +
path + ":" +
(alias==null ? "" : alias) +
')';
return "Subcriteria("
+ path + ":"
+ (alias==null ? "" : alias)
+ ')';
}
@ -429,6 +443,10 @@ public class CriteriaImpl implements Criteria, Serializable {
return joinType;
}
public Criterion getWithClause() {
return this.withClause;
}
// Criteria impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -451,6 +469,11 @@ public class CriteriaImpl implements Criteria, Serializable {
return this;
}
public Criteria createAlias(String associationPath, String alias, int joinType, Criterion withClause) throws HibernateException {
new Subcriteria( this, associationPath, alias, joinType, withClause );
return this;
}
public Criteria createCriteria(String associationPath) {
return createCriteria( associationPath, INNER_JOIN );
}
@ -467,6 +490,10 @@ public class CriteriaImpl implements Criteria, Serializable {
return new Subcriteria( Subcriteria.this, associationPath, alias, joinType );
}
public Criteria createCriteria(String associationPath, String alias, int joinType, Criterion withClause) throws HibernateException {
return new Subcriteria( this, associationPath, alias, joinType, withClause );
}
public Criteria setCacheable(boolean cacheable) {
CriteriaImpl.this.setCacheable(cacheable);
return this;
@ -493,8 +520,7 @@ public class CriteriaImpl implements Criteria, Serializable {
return CriteriaImpl.this.uniqueResult();
}
public Criteria setFetchMode(String associationPath, FetchMode mode)
throws HibernateException {
public Criteria setFetchMode(String associationPath, FetchMode mode) {
CriteriaImpl.this.setFetchMode( StringHelper.qualify(path, associationPath), mode);
return this;
}

View File

@ -85,6 +85,7 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker {
null,
alias,
JoinFragment.LEFT_OUTER_JOIN,
null,
getFactory(),
CollectionHelper.EMPTY_MAP
)

View File

@ -207,6 +207,10 @@ public class JoinWalker {
}
}
protected String getWithClause(String path) {
return "";
}
/**
* Add on association (one-to-one, many-to-one, or a collection) to a list
* of associations to be fetched by outerjoin
@ -235,6 +239,7 @@ public class JoinWalker {
aliasedLhsColumns,
subalias,
joinType,
getWithClause(path),
getFactory(),
loadQueryInfluencers.getEnabledFilters()
);

View File

@ -59,6 +59,7 @@ public final class OuterJoinableAssociation {
String[] lhsColumns,
String rhsAlias,
int joinType,
String withClause,
SessionFactoryImplementor factory,
Map enabledFilters) throws MappingException {
this.joinableType = joinableType;
@ -68,7 +69,8 @@ public final class OuterJoinableAssociation {
this.joinType = joinType;
this.joinable = joinableType.getAssociatedJoinable(factory);
this.rhsColumns = JoinHelper.getRHSColumnNames(joinableType, factory);
this.on = joinableType.getOnCondition(rhsAlias, factory, enabledFilters);
this.on = joinableType.getOnCondition(rhsAlias, factory, enabledFilters)
+ ( withClause == null || withClause.trim().length() == 0 ? "" : " and ( " + withClause + " )" );
this.enabledFilters = enabledFilters; // needed later for many-to-many/filter application
}
@ -188,4 +190,4 @@ public final class OuterJoinableAssociation {
joinable.whereJoinFragment(rhsAlias, false, true)
);
}
}
}

View File

@ -80,6 +80,7 @@ public class BasicCollectionJoinWalker extends CollectionJoinWalker {
null,
alias,
JoinFragment.LEFT_OUTER_JOIN,
null,
getFactory(),
CollectionHelper.EMPTY_MAP
)

View File

@ -85,6 +85,7 @@ public class OneToManyJoinWalker extends CollectionJoinWalker {
null,
alias,
JoinFragment.LEFT_OUTER_JOIN,
null,
getFactory(),
CollectionHelper.EMPTY_MAP
) );

View File

@ -210,4 +210,8 @@ public class CriteriaJoinWalker extends AbstractEntityJoinWalker {
return "criteria query";
}
protected String getWithClause(String path) {
return translator.getWithClause(path);
}
}

View File

@ -43,6 +43,7 @@ import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.hql.ast.util.SessionFactoryHelper;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projection;
import org.hibernate.engine.QueryParameters;
import org.hibernate.engine.RowSelection;
@ -77,7 +78,8 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
private final Map aliasCriteriaMap = new HashMap();
private final Map associationPathCriteriaMap = new LinkedHashMap();
private final Map associationPathJoinTypesMap = new LinkedHashMap();
private final Map withClauseMap = new HashMap();
private final SessionFactoryImplementor sessionFactory;
public CriteriaQueryTranslator(
@ -169,6 +171,10 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
// TODO : not so sure this is needed...
throw new QueryException( "duplicate association path: " + wholeAssociationPath );
}
if ( crit.getWithClause() != null )
{
this.withClauseMap.put(wholeAssociationPath, crit.getWithClause());
}
}
}
@ -266,9 +272,42 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
}
public QueryParameters getQueryParameters() {
RowSelection selection = new RowSelection();
selection.setFirstRow( rootCriteria.getFirstResult() );
selection.setMaxRows( rootCriteria.getMaxResults() );
selection.setTimeout( rootCriteria.getTimeout() );
selection.setFetchSize( rootCriteria.getFetchSize() );
Map lockModes = new HashMap();
Iterator iter = rootCriteria.getLockModes().entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = ( Map.Entry ) iter.next();
final Criteria subcriteria = getAliasedCriteria( ( String ) me.getKey() );
lockModes.put( getSQLAlias( subcriteria ), me.getValue() );
}
List values = new ArrayList();
List types = new ArrayList();
Iterator iter = rootCriteria.iterateExpressionEntries();
iter = rootCriteria.iterateSubcriteria();
while ( iter.hasNext() ) {
CriteriaImpl.Subcriteria subcriteria = ( CriteriaImpl.Subcriteria ) iter.next();
LockMode lm = subcriteria.getLockMode();
if ( lm != null ) {
lockModes.put( getSQLAlias( subcriteria ), lm );
}
if ( subcriteria.getWithClause() != null )
{
TypedValue[] tv = subcriteria.getWithClause().getTypedValues( subcriteria, this );
for ( int i = 0; i < tv.length; i++ ) {
values.add( tv[i].getValue() );
types.add( tv[i].getType() );
}
}
}
// Type and value gathering for the WHERE clause needs to come AFTER lock mode gathering,
// because the lock mode gathering loop now contains join clauses which can contain
// parameter bindings (as in the HQL WITH clause).
iter = rootCriteria.iterateExpressionEntries();
while ( iter.hasNext() ) {
CriteriaImpl.CriterionEntry ce = ( CriteriaImpl.CriterionEntry ) iter.next();
TypedValue[] tv = ce.getCriterion().getTypedValues( ce.getCriteria(), this );
@ -277,31 +316,9 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
types.add( tv[i].getType() );
}
}
Object[] valueArray = values.toArray();
Type[] typeArray = ArrayHelper.toTypeArray( types );
RowSelection selection = new RowSelection();
selection.setFirstRow( rootCriteria.getFirstResult() );
selection.setMaxRows( rootCriteria.getMaxResults() );
selection.setTimeout( rootCriteria.getTimeout() );
selection.setFetchSize( rootCriteria.getFetchSize() );
Map lockModes = new HashMap();
iter = rootCriteria.getLockModes().entrySet().iterator();
while ( iter.hasNext() ) {
Map.Entry me = ( Map.Entry ) iter.next();
final Criteria subcriteria = getAliasedCriteria( ( String ) me.getKey() );
lockModes.put( getSQLAlias( subcriteria ), me.getValue() );
}
iter = rootCriteria.iterateSubcriteria();
while ( iter.hasNext() ) {
CriteriaImpl.Subcriteria subcriteria = ( CriteriaImpl.Subcriteria ) iter.next();
LockMode lm = subcriteria.getLockMode();
if ( lm != null ) {
lockModes.put( getSQLAlias( subcriteria ), lm );
}
}
return new QueryParameters(
typeArray,
valueArray,
@ -576,4 +593,10 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
return propertyName;
}
public String getWithClause(String path)
{
final Criterion crit = (Criterion)this.withClauseMap.get(path);
return crit == null ? null : crit.toSqlString(getCriteria(path), this);
}
}

View File

@ -196,6 +196,34 @@ while ( iter.hasNext() ) {
Cat kitten = (Cat) map.get("kt");
}]]></programlisting>
<para>
Additionally you may manipulate the result set using a left outer join:
</para>
<programlisting><![CDATA[
List cats = session.createCriteria( Cat.class )
.createAlias("mate", "mt", Criteria.LEFT_JOIN, Restrictions.like("mt.name", "good%") )
.addOrder(Order.asc("mt.age"))
.list();
]]></programlisting>
<para>
This will return all of the <literal>Cat</literal>s with a mate whose name starts with "good"
ordered by their mate's age, and all cats who do not have a mate.
This is useful when there is a need to order or limit in the database
prior to returning complex/large result sets, and removes many instances where
multiple queries would have to be performed and the results unioned
by java in memory.
</para>
<para>
Without this feature, first all of the cats without a mate would need to be loaded in one query.
</para>
<para>
A second query would need to retreive the cats with mates who's name started with "good" sorted by the mates age.
</para>
<para>
Thirdly, in memory; the lists would need to be joined manually.
</para>
</sect1>
<sect1 id="querycriteria-dynamicfetching" revision="1">

View File

@ -794,5 +794,99 @@ public class CriteriaQueryTest extends FunctionalTestCase {
t.commit();
session.close();
}
public void testAliasJoinCriterion() {
Session session = openSession();
Transaction t = session.beginTransaction();
Course courseA = new Course();
courseA.setCourseCode("HIB-A");
courseA.setDescription("Hibernate Training A");
session.persist(courseA);
Course courseB = new Course();
courseB.setCourseCode("HIB-B");
courseB.setDescription("Hibernate Training B");
session.persist(courseB);
Student gavin = new Student();
gavin.setName("Gavin King");
gavin.setStudentNumber(232);
gavin.setPreferredCourse(courseA);
session.persist(gavin);
Student leonardo = new Student();
leonardo.setName("Leonardo Quijano");
leonardo.setStudentNumber(233);
leonardo.setPreferredCourse(courseB);
session.persist(leonardo);
Student johnDoe = new Student();
johnDoe.setName("John Doe");
johnDoe.setStudentNumber(235);
johnDoe.setPreferredCourse(null);
session.persist(johnDoe);
// test == on one value exists
List result = session.createCriteria( Student.class )
.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN, Restrictions.eq("pc.courseCode", "HIB-A") )
.setProjection( Property.forName("pc.courseCode") )
.addOrder(Order.asc("pc.courseCode"))
.list();
assertEquals( 3, result.size() );
// can't be sure of NULL comparison ordering aside from they should
// either come first or last
if ( result.get( 0 ) == null ) {
assertNull(result.get(1));
assertEquals( "HIB-A", result.get(2) );
}
else {
assertNull( result.get(2) );
assertNull( result.get(1) );
assertEquals( "HIB-A", result.get(0) );
}
// test == on non existent value
result = session.createCriteria( Student.class )
.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN, Restrictions.eq("pc.courseCode", "HIB-R") )
.setProjection( Property.forName("pc.courseCode") )
.addOrder(Order.asc("pc.courseCode"))
.list();
assertEquals( 3, result.size() );
assertNull( result.get(2) );
assertNull( result.get(1) );
assertNull(result.get(0) );
// test != on one existing value
result = session.createCriteria( Student.class )
.createAlias( "preferredCourse", "pc", Criteria.LEFT_JOIN, Restrictions.ne("pc.courseCode", "HIB-A") )
.setProjection( Property.forName("pc.courseCode") )
.addOrder(Order.asc("pc.courseCode"))
.list();
assertEquals( 3, result.size() );
// can't be sure of NULL comparison ordering aside from they should
// either come first or last
if ( result.get( 0 ) == null ) {
assertNull( result.get(1) );
assertEquals( "HIB-B", result.get(2) );
}
else {
assertEquals( "HIB-B", result.get(0) );
assertNull( result.get(1) );
assertNull( result.get(2) );
}
session.delete(gavin);
session.delete(leonardo);
session.delete(johnDoe);
session.delete(courseA);
session.delete(courseB);
t.commit();
session.close();
}
}