-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: How to retrieve the result of a subquery with Criteria API?
PostPosted: Sat Apr 07, 2007 2:41 pm 
Newbie

Joined: Sat Apr 07, 2007 12:08 pm
Posts: 6
I did read the documentation and search the forums but still didn't find the answer to the following question:

I have a unidirectional many-to-one relationship between two classes:

Code:
@Entity
public class Company implements Serializable {
    ...
}

@Entity
public class Employee implements Serializable {

    private Company company;

    @ManyToOne
    public Company getCompany() {
        return company;
    }

    ...
}


Now, I'd like to use Criteria API to construct a query equivalent to the following SQL query:

Code:
SELECT c.*, COUNT(e.*) AS ecount
FROM Company c
LEFT JOIN Employee e ON e.company_id = c.id


Now, since the relationship is unidirectional I cannot use an association here (or can I?). Instead I decided to use a subquery like this:

Code:
SELECT c.*, (SELECT COUNT(*) FROM Employee e WHERE e.company_id = c.id) ecount
FROM Company c


However what I can't figure out is how to use Projection from Criteria API to retrieve the result of the subquery.

Here's my code so far:

Code:
Criteria query = session.createCriteria(Company.class, "c");
DetachedCriteria subQuery =
    DetachedCriteria.forClass(Employee.class)
    .createCriteria("company")
    .add(Restrictions.idEq(Property.forName("c.id")));
subQuery.setProjection(Projections.rowCount());


What is missing is the code that would attach the subquery to the main query and make it return subquery's result. I could then use setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP) to return both the Company objects and the respective counts.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 11, 2007 3:16 pm 
Newbie

Joined: Sat Apr 07, 2007 12:08 pm
Posts: 6
I stressed that the association is unidirectional and that its direction is not the one useful in my case. It's not that it cannot be bidirectional. I just cannot modify the "one" end. So if there's a way to specify a one-to-many association on the "many" end, that would solve my problem as well.

Is still would like to use Criteria API though. Any ideas?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Apr 12, 2007 3:03 pm 
Newbie

Joined: Sat Apr 07, 2007 12:08 pm
Posts: 6
While the above idea cannot work (there's no employees property defined in the Company class), I think I might be able to create my own projection type by implementing Projection interface. However when I tried this in a code deployed on a JBoss server, I got following exception:

Code:
javax.ejb.EJBException: java.lang.RuntimeException: java.lang.IllegalAccessError: tried to access method org.hibernate.criterion.DetachedCriteria.getCriteriaImpl()Lorg/hibernate/impl/CriteriaImpl; from class org.hibernate.criterion.SubqueryProjection
   at org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:69)
...
Caused by: java.lang.RuntimeException: java.lang.IllegalAccessError: tried to access method org.hibernate.criterion.DetachedCriteria.getCriteriaImpl()Lorg/hibernate/impl/CriteriaImpl; from class org.hibernate.criterion.SubqueryProjection
   at org.jboss.ejb3.interceptor.InvocationContextImpl.proceed(InvocationContextImpl.java:174)
   at org.jboss.ejb3.interceptor.EJB3InterceptorsInterceptor.invoke(EJB3InterceptorsInterceptor.java:63)
...
Caused by: java.lang.IllegalAccessError: tried to access method org.hibernate.criterion.DetachedCriteria.getCriteriaImpl()Lorg/hibernate/impl/CriteriaImpl; from class org.hibernate.criterion.SubqueryProjection
   at org.hibernate.criterion.SubqueryProjection.<init>(SubqueryProjection.java:22)
...

As you can see I was forced to create my class (SubqueryProjection) in org.hibernate.criterion package to be able to access package private method getCriteriaImpl of DetachedCriteria class. I don't know if that causes the error though.

Can anyone shed some light on this, please?


Top
 Profile  
 
 Post subject: Solved
PostPosted: Sat Apr 14, 2007 7:44 am 
Newbie

Joined: Sat Apr 07, 2007 12:08 pm
Posts: 6
I overcame the above problem by replacing getCriteriaImpl() call with a call getExecutableCriteria(null) (Session is not available at this point).

I also managed to finish the SubqueryProjection class, which is mainly based on other implementations of Projection as well as SubqueryExpression. Here's how I use it:

Code:
Criteria query = session.createCriteria(Company.class, "c");
DetachedCriteria subQuery = DetachedCriteria.forClass(Employee.class, "e")
    .setProjection(Projections.rowCount())
    .add(Expression.eqProperty("e.company.id", "c.id"));
query.setProjection(Projections.alias(new SubqueryProjection(subQuery), "ecount"));
List result = query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP).list();

The resulting query is:

Code:
select (select count(*) as y0_ from employee this0__ where this0__.companyId=this_.id) as y0_ from company this_

And here is the class itself:

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);
      
      String sql = new Select(factory.getDialect())
         .setWhereClause(innerQuery.getWhereCondition())
         .setGroupByClause(innerQuery.getGroupBy())
         .setSelectClause(innerQuery.getSelect())
         .setFromClause(
               persister.fromTableFragment(innerQuery.getRootSQLALias()) +   
               persister.fromJoinFragment(innerQuery.getRootSQLALias(), true, false)
            )
         .toStatementString();

      final StringBuffer buf = new StringBuffer();
      return buf.append('(').append(sql).append(") as y")
         .append(position).append('_').toString();
   }

   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
         );
      return innerQuery;
   }

}

Let me know if you know how to make it better.

After all I just wish I will get more help next time :/


Top
 Profile  
 
 Post subject: Re: How to retrieve the result of a subquery with Criteria API?
PostPosted: Tue May 18, 2010 7:54 am 
Newbie

Joined: Tue May 18, 2010 7:15 am
Posts: 1
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!


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.