Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate criteria query for Collection Table?

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.

like image 564
Anupam Gupta Avatar asked Oct 07 '11 12:10

Anupam Gupta


People also ask

How can we get data from Criteria in Hibernate?

The Hibernate Session interface provides createCriteria() method, which can be used to create a Criteria object that returns instances of the persistence object's class when your application executes a criteria query.

How do you write join query in Hibernate using criteria?

Criteria in Hibernate can be used for join queries by joining multiple tables, useful methods for Hibernate criteria join are createAlias(), setFetchMode() and setProjection() Criteria in Hibernate API can be used for fetching results with conditions, useful methods are add() where we can add Restrictions.


3 Answers

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.

like image 181
jbrookover Avatar answered Oct 27 '22 19:10

jbrookover


Another solution without wrapping your enums and using ResultTransformers is this:

        @ElementCollection(fetch = FetchType.EAGER)
        @Enumerated(value = EnumType.STRING)
        @Column(name = " action")
        @JoinTable(name = "rule_action", joinColumns = { @JoinColumn(name = "rule_id") })
        private Set<Action> actions;

Query:

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

That's what worked for me with Hibernate 4.1.6.Final. I found it here.

like image 40
schnatterer Avatar answered Oct 27 '22 19:10

schnatterer


The criteria query looks fine. Suppose we have tables:

rule(id, name,...)
action(id, name,..)
rule_action(id, rule_id, action_id,...) -- (or rule_id+action_id as  a composite pkey)

The mapping should be as follows:

public class Rule {
   ...
   @ManyToMany(mappedBy = "rule")
   private Set<Action> actions;
   ...
}

public class Action {
   ...
   @JoinTable(
      name="rule_action",
      joinColumns={@JoinColumn(name="action_id")},
      inverseJoinColumns={@JoinColumn(name="rule_id")}
   )
   private Set<Rule> rules;
   ...
}

This way your criteria query should work.

like image 24
forker Avatar answered Oct 27 '22 19:10

forker