views:

59

answers:

3

I am doing a simple web page and I have a NurseForm entity. When the nurse sees a patient he/she fills this form.

One of this form field is "Actions done" which is basically an enum with:

public enum NurseAction {
    GIVE_MEDICINE, PERFORM_SUTURE, SPRAY_THERAPY, NEBULIZATIONS;
}

A nurse can perform more than one action so I have a property:

private Collection<NurseAction> nurseActions;

From what I understand I need NurseAction to be an Entity, but if I do so I should populate the db by hand.

Can I avoid that? Which is the best way to solve this?

PS: I am a complete newbie to Hibernate.

+2  A: 

You can try using @CollectionOfElements(targetElement=NurseAction.class) (you can try to omit the attribute and let hibernate assume it based on the type parameter of the collection)

Note that this annotation is deprecated in hibernate 3.5, in favour of the same annotation from JPA 2.0.

Bozho
@Bozho: Cool. I added `@CollectionOfElements(targetElement=NurseAction.class)` to my `NurseForm`'s getter and remove the `@Entity` from the enum. It does build successfully but I don't understand how Hibernate will map it. Can you explain that?
Macarse
I don't know, try it, with `hbm2ddl.auto=true`. it might be a comma-separeted `name()` or `ordinal()`, or a new table creating those.
Bozho
The replacement is `@ElementCollection`.
Willi
Yes, that's how it [is documented](http://www.redhat.com/docs/manuals/jboss/jboss-eap-4.2/doc/hibernate/Annotations_Reference_Guide/Extra_collection_types-Collection_of_element_or_composite_elements.html). And Hibernate will use an implicit (or explicit) `JoinTable` (and not an horrible anti-relational string concatenation) :)
Pascal Thivent
+1  A: 

Unfortunately, Hibernate does not (yet) support collections of Enums properly. But do you not think that new actions will most likely be added later on? I would recommend going for an Entity for the action to make sure that you do not have to re-deploy your whole app just because you are adding an action...

Of course, you can also always do something (ugly :) like this:

@Entity public class NurseForm {
   private String actions;

   public void setActions(final List<NurseAction> actions) {
     if(actions == null)
        this.actions = null;
     else
        this.actions = Joiner.on(',').join(actions);
   }

   public List<NurseAction> getActions() {
      List<NurseAction> returner = Lists.newArrayList();
      if(this.actions != null) {
         Iterable<String> actions = Splitter.on(',').split(this.actions);
         for(String action : actions)
            returner.add(NurseAction.valueOf(action);
      }
      return returner;
   }
}

Disclaimer: all of this is untested and even un-compiled. Also, I am making heavy use of google guava because I love it. :)

Good luck!

LeChe
I would prefer returning a live view instead of a snapshot in `getActions` which writes changes back to the private field immediately. There would be no need for a setter.
Willi
Another idea would be to use a `@PreUpdate` (and `@PrePersist`?) method which serializes the current value of a transient field holding the collection into a mapped field holding the serialized value.
Willi
Why do you say it's not supported? What about `@CollectionOfElements` in pre 3.5?
Pascal Thivent
@Pascal: I did not say it's not supported, I said it's not supported PROPERLY. But you are right, I should have been more clear on this. Unfortunately, I am home now and I cannot check my documentation to see what went wrong back then. All I remember is that with JBoss 5.1 and collections of enums, we had loads of problems with queries and inheritance. Sorry for being so vague, I'll check on monday what exactly was going on...
LeChe
@LeChe Some tips for your solution above: Save those `Joiner` and `Splitter` instances somewhere and reuse them. And your `join` call relies on the `toString` method of `NurseAction` which is fragile and insecure as somebody might want to overwrite this method in the future. Use `name()` instead.
Willi
@LeChe No problem, thank you for your answer. I actually wanted to understand if theory and practice do match, and if they don't, why. Your practical feedback looks very interesting and I'll read it with interest.
Pascal Thivent
@Willi This was just a quick example, loads of things can be improved in it. But luckily, creating Joiners and Splitters is very cheap, so chaching is not really needed unless there is a performance problem: "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil" :) Also, the code will break indeed if someone changes the toString method ... of the Enumeration! Likely? Heck, no! Worth using name() instead? Probably, but again, not in this quick example...
LeChe
@Pascal Seems that I was a little quick with my post: the problem is not Hibernate, but EJB3. Checking my code doc, it turns out that since we did not want to use any non-JEE annotations, we could not use @CollectionOfElements and had to use above code: EJB3 does not let you use collections of enumerations. Why did we not want to use Hibernate annotations? Back then we still thought that we might switch the ORM layer at some point. Thanks, JBoss for making this impossible... :(
LeChe
Thanks for the feedback, and the clarification, makes sense now. And yes, JBoss somehow locks you in.
Pascal Thivent
+1  A: 

Under the assumption that any action may only be applied once to a patient, i would suggest using Set instead of Collection. Please correct me if this does not fit your requirements. In case set semantic is ok, i would suggest using an EnumSet to store the actions. It's extremely fast and a compliant Set implementation. It uses a single long value under the hood (as long as you have less than 64 actions).

Now my solution would be to store a single number column to the database using custom UserType which maps from EnumSet to long and vice versa.

Pros

  • single column
  • fast
  • extremely small overhead

Cons

  • values in database are hard to read (by humans)
  • custom queries are difficult

If you want to take a look at a generic abstract reusable implementation of such a UserType, take a look at http://source.palava2.org/browse/commons.svn.cosmocode.de/cosmocode-hibernate/trunk/src/main/java/de/cosmocode/hibernate/EnumSetUserType.java?r=HEAD

Willi