-->
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.  [ 3 posts ] 
Author Message
 Post subject: Many-to-Many/One unique constraint failure
PostPosted: Mon May 03, 2010 1:41 pm 
Newbie

Joined: Mon May 03, 2010 11:45 am
Posts: 2
I'm fairly new to Hibernate, so as a learning exercise I'm trying to write up the data structure for a database of recipes, although the problem I'm having is probably fairly widely applicable. I'm still just playing around - a long way from anything like production.

Basically I'm trying to map multiple Owner objects to multiple Child objects through a link table, with a unique constraint on the Child objects (which contain a single field). In my app this takes the form of Recipes having multiple Categorys, although it could just as easily be thought of as BlogEntrys having multiple Tags or anything similar.

For example, a "Fish and Chips" recipe could have the categories "main course, deep fried", while a "Chocolate Cake" recipe has "dessert, cake, baking, vegetarian" or whatever.

My idea is to have the Category dataset as a simple lookup table with columns (id, category), and the link table containing (recipe_id, category_id) entries. Now, this works great, until I try to insert a recipe which specifies a category with the same name as one that exists already. Hibernate thinks I want to create a new instance of the Category object and persist it to the database, which violates the uniqueness constraint on the category column.

What I'd prefer is for it to only insert if that category doesn't already exist, and otherwise use the existing id for the link table. So it's kind of like a semi-dynamic lookup table, which can be inserted to in the event that the requested object doesn't exist.

I've been using annotated classes, which has been serving me very well. This is a cut down version of what I've got so far, hopefully someone can spot the problem:

Code:
//Recipe.java

@Entity
public class Recipe {
   @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
   private long id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(
      name = "recipe_category_link",
      joinColumns = @JoinColumn(name = "recipe_id"),
      inverseJoinColumns = @JoinColumn(name = "category_id")
      )
   private Set<RecipeCategory> categories = new HashSet<RecipeCategory>();

   // Getters and setters not shown

   public Recipe() {
   }

   // This is my guess as to where the problem is:
   public Recipe addCategory(String category) {
      categories.add(new RecipeCategory(this, category));
      return this;
   }
}


Code:
//RecipeCategory.java

@Entity
public class RecipeCategory {
   @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
   private long id;

   @Column(unique = true)
   private String category;
   
   @ManyToMany(
         cascade = { CascadeType.ALL },
         mappedBy = "categories"
         )
   private Set<Recipe> recipes = new HashSet<Recipe>();

   // Getters and setters not shown

   public RecipeCategory() {
   }

   public RecipeCategory(Recipe recipe, String category) {
      this.setCategory(category);
   }
}


Code:
// Implementation
// Get Session/open Transaction

Recipe recipe = new Recipe();
recipe.addCategory("First category")
   .addCategory("Second category");

session.save(recipe);

// Later or in another call

Recipe anotherRecipe = new Recipe();
anotherRecipe.addCategory("Third category")
   .addCategory("First category");
// This is what breaks it when it tries to add a new "First category"
// instead of reusing the id in the link table

session.save(anotherRecipe);


Chances are I've missed something blindingly obvious to any experienced developer, so be gentle and I'll gladly accept any other guidance on best practices and stuff.

Hopefully my intentions are clear, and I'm more than happy to provide any further information required.

As an aside, is setting the return value of methods to the object itself an acceptable practice? I've done some jQuery work and I like the way you can just chain everything together so I've implemented it here, but it feels like it might open up some unforseen problem.


Top
 Profile  
 
 Post subject: Re: Many-to-Many/One unique constraint failure
PostPosted: Sun May 09, 2010 11:40 am 
Newbie

Joined: Tue Dec 30, 2008 3:29 pm
Posts: 4
Location: Netherlands
Hi Zetten,

Hibernate considers every entity which does not have an ID set a new one that has to be inserted into the database.
You instantiate your RecipeCategory entity within the addCategory(String) method of your Recipe entity. The value of its id field is 0 by default which indicates to Hibernate that this entity is not yet in the database so it tries to insert it when you persist the Recipe. At the time of saving the first recipe everything works as expected because the database is empty (or you just simply don't have any of the persisted entities in the database). At the moment of saving the second recipe however the save operation will fail as you experienced since the name of the RecipeCategory is annotated as unique and you try to insert a new RecipeCategory entity into the database with the same category name.
So far you figured it out yourself. I would however not instantiate the RecipeCategory entity within the Recipe entity but use it as an argument of the addCategory() method. With this approach you can query all your existing recipe categories before you assign any of them to a recipe so Hibernate is not going to reinsert the categories already in the database.

As for your other question I can't really tell if its an acceptable practice or not but as long as it suits your needs and fits well into your implementation I don't see any reason why you should not use it. Personally I prefer to have an add() method that takes a list so I don't have to chain the same method call over and over again. This of course does not work with multiple add and set methods where each of them takes different type(s) of argument(s).


Top
 Profile  
 
 Post subject: Re: Many-to-Many/One unique constraint failure
PostPosted: Sun May 09, 2010 11:47 am 
Newbie

Joined: Mon May 03, 2010 11:45 am
Posts: 2
Thanks for the reply. That all matches up with what I've found elsewhere: I've moved that check into a slightly higher level DAO class, and made sure the add() methods for the various many-to-x properties are more sensible.

From my reading it made a lot more sense once I realised that it's not really Hibernate's job to keep an eye on the existing data, so of course it's not going to go and do an additional query to prevent re-instantiation of unique objects. I still think it'd be nice to have an attribute/property for relationships called something like 'insertIfNotExists/reuseIfExists' or something, but that's just me being lazy.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 3 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.