that's exactly what I needed!
I exchanged some parts of mehow's class, to allow for more complex subquery including JOINs:
Code:
public class SubqueryProjection implements Projection {
private CriteriaImpl subquery;
private CriteriaQueryTranslator innerQuery;
public SubqueryProjection(DetachedCriteria subquery) {
this.subquery = (CriteriaImpl) subquery.getExecutableCriteria(null);
}
public String[] getAliases() {
return new String[1];
}
public String[] getColumnAliases(int loc) {
return new String[] { "y" + loc + "_" };
}
public String[] getColumnAliases(String alias, int loc) {
return null;
}
public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery)
throws HibernateException {
if (innerQuery == null)
innerQuery = buildInnerQuery(criteria, criteriaQuery);
return innerQuery.getProjectedTypes();
}
public Type[] getTypes(String alias, Criteria criteria,
CriteriaQuery criteriaQuery) throws HibernateException {
return null;
}
public boolean isGrouped() {
return false;
}
public String toGroupSqlString(Criteria arg0, CriteriaQuery arg1)
throws HibernateException {
throw new UnsupportedOperationException("not a grouping projection");
}
public String toSqlString(Criteria criteria, int position,
CriteriaQuery criteriaQuery) throws HibernateException {
final SessionImplementor session = ((CriteriaImpl) criteria)
.getSession(); // ugly!
final SessionFactoryImplementor factory = session.getFactory();
final OuterJoinLoadable persister = (OuterJoinLoadable) factory
.getEntityPersister(subquery.getEntityOrClassName());
if (innerQuery == null)
innerQuery = buildInnerQuery(criteria, criteriaQuery);
// added from 'SubqueryExpression.java' to allow for joins in the projected subquery
CriteriaJoinWalker walker = new CriteriaJoinWalker(
persister,
innerQuery,
factory,
subquery,
subquery.getEntityOrClassName(),
subquery.getSession().getLoadQueryInfluencers(),
innerQuery.getRootSQLALias()
);
String sql = walker.getSQLString();
return "("+sql+") as y"+position+"_";
}
private CriteriaQueryTranslator buildInnerQuery(Criteria criteria,
CriteriaQuery criteriaQuery) {
SessionImplementor session = ((CriteriaImpl) criteria).getSession(); // ugly!
SessionFactoryImplementor factory = session.getFactory();
CriteriaQueryTranslator innerQuery = new CriteriaQueryTranslator(
factory, subquery, subquery.getEntityOrClassName(), // implicit polymorphism not supported (would need a union)
criteriaQuery.generateSQLAlias(), criteriaQuery);
//UO: added
subquery.setSession(session);
return innerQuery;
}
}
However, JDBC parameters will get mixed up, because the "?" appear now in the generated SQL string but are not set!
Therefore I created another SubqueryProjectionCriterion which has to be added as the first Criterion! This Criterion generates a "1" in the sql string, i.e. will not affect the query, but the JDBC parameters will be set and used by the SubqueryProjection.
Code:
public class SubqueryProjectionCriterion implements Criterion {
private static final long serialVersionUID = 1776863999431477648L;
private CriteriaImpl criteriaImpl;
private QueryParameters params;
private Type[] types;
private CriteriaQueryTranslator innerQuery;
public SubqueryProjectionCriterion(DetachedCriteria dc) {
this.criteriaImpl = (CriteriaImpl) dc.getExecutableCriteria(null);
}
protected Type[] getTypes() {
return types;
}
protected String toLeftSqlString(Criteria criteria, CriteriaQuery outerQuery) {
return "";
}
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return "1";
}
private SessionImplementor deriveRootSession(Criteria criteria) {
if ( criteria instanceof CriteriaImpl ) {
return ( ( CriteriaImpl ) criteria ).getSession();
}
else if ( criteria instanceof CriteriaImpl.Subcriteria ) {
return deriveRootSession( ( ( CriteriaImpl.Subcriteria ) criteria ).getParent() );
}
else {
return null;
}
}
public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery)
throws HibernateException {
SessionFactoryImplementor factory = criteriaQuery.getFactory();
createAndSetInnerQuery(criteriaQuery, factory);
Type[] ppTypes = params.getPositionalParameterTypes();
Object[] ppValues = params.getPositionalParameterValues();
TypedValue[] tv = new TypedValue[ppTypes.length];
for ( int i=0; i<ppTypes.length; i++ ) {
tv[i] = new TypedValue( ppTypes[i], ppValues[i], EntityMode.POJO );
}
return tv;
}
private void createAndSetInnerQuery(CriteriaQuery criteriaQuery, SessionFactoryImplementor factory) {
if ( innerQuery == null ) {
String alias;
if ( this.criteriaImpl.getAlias() == null ) {
alias = criteriaQuery.generateSQLAlias();
}
else {
alias = this.criteriaImpl.getAlias() + "_";
}
innerQuery = new CriteriaQueryTranslator(
factory,
criteriaImpl,
criteriaImpl.getEntityOrClassName(),
alias,
criteriaQuery
);
params = innerQuery.getQueryParameters();
types = innerQuery.getProjectedTypes();
}
}
}
so you use it something like this:
Code:
DetachedCriteria criteria = DetachedCriteria.forClass(clazz, alias)
.add(new SubqueryProjectionCriterion(dcSubquery))
.add ...
.add ...(all the other parts of your criteria)
.add ...
.setProjection(new SubqueryProjection(dcSubquery));
some workaround, but it actually works!