Hibernate criteria query for Collection Table?

Anupam Gupta picture Anupam Gupta · Oct 7, 2011 · Viewed 10.6k times · Source

I have following Entity

@Entity
@Table(name = "rule")
public class Rule implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "rule_id")
    private Long id;

    @ElementCollection(targetClass = Action.class)


     @CollectionTable(name = "rule_action", joinColumns = @JoinColumn(name = "rule_id"))
        @Enumerated(value = EnumType.STRING)
        @Column(name = "action")
        private Set<Action> actions;

//After editing as per jbrookover's suggestion adding a new mapping
       @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
       @JoinColumn(name = "rule_id")
       private Set<RuleAction> ruleActions;


    }

Following is my Action

public enum Action {
    PHONE, EMAIL, POSTAL,PHONE_OR_EMAIL, SMS;
}

I want to fetch a List of rule having particular set of actions I am trying this

 DetachedCriteria criteria = DetachedCriteria.forClass(Rule.class,"rule");
 criteria = criteria.createAlias("rule.actions", "action");
 criteria.add(Restrictions.in("action.name",actionSet));
 return getHibernateTemplate().findByCriteria(criteria);

But getting org.hibernate.MappingException: collection was not an association: exception..

EDIT So after guidance from jbrookover I tried going for a wrapper class for Action named as RuleAction and was able to eshtablish the oneToMany relationship, Also I modified the query as follows

    Set<Action> act = new HashSet<Action>();
    act.add(Action.EMAIL);
    act.add(Action.POSTAL);

    DetachedCriteria criteria = DetachedCriteria.forClass(Rule.class);
    criteria.add(Restrictions.eq(SUPPORT_LANG, Language.valueOf("EN")))
            .createCriteria("ruleActions").add(Restrictions.in("action",act));
    return getHibernateTemplate().findByCriteria(criteria);

But this is returning me all the rule which are having either EMAIL or POSTAL but what I want is all the rule having EMAIL and POSTAL both Please help me in modifying the query.

Answer

jbrookover picture jbrookover · Oct 10, 2011

Sorry, what you are trying to do, specifically, is not supported in Hibernate. See this FAQ:

http://community.jboss.org/wiki/HibernateFAQ-AdvancedProblems#Im_getting_orghibernateMappingException_collection_was_not_an_association_when_I_try_to_join_a_collection_of_components_with_Criteria_queries

I, too, was quite displeased with this. As you can see, though, they've tried to fix it an have been unable to do so and have put it on the community to deal with it. You have a few options:

  1. Use HQL to run the query.
  2. Re-write your collection association as an actual entity class with a single Enum field.

You can do something like this:

 @Entity
 public class ActionWrapper {

      public Action action;
 }

Then, update your associations and query accordingly so that Rule has a Set<ActionWrapper>. There are other workarounds, but you essentially cannot use Criteria and @ElementCollection together.

Update

In order to restrict the query further, to ensure that you get Rules that meet BOTH actions, you need to run the subquery and do a conjunction - 'and' - of the matched values. Something like this should work:

 Criteria subCriteria = criteria.createCriteria("ruleActions");
 Disjunction and = Restrictions.conjunction();
 for (Action a : act)
    and.add(Restrictions.eq("action", a)
 subCriteria.add(and);

At the end, you may find duplicate results. This is common and can be eliminated by adding this:

 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

I can't speak for the efficiency of this code - HQL may be better in the long run. However, I've done something similar in other projects and haven't encountered any issues.