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.