I understand you want a tag table representing tags for multiple different entities rather than a tag table + join tables for specific entity types (post_tags, story_tags etc.), which is how JPA would map a unidirectional one-to-many by default.
In that case I believe this is what you're looking for.
There are essentially three ways to go around this:
1. @Where + @Any
Use @Where to restrict matching entities in the Post.tags collection:
@Entity public class Post {
@Id
private String id;
@OneToMany
@Immutable
@JoinColumn(name = "resource_id", referencedColumnName = "id", insertable = false, updatable = false)
@Where(clause = "resource_type = 'post'")
private Collection<Tag> tags;
}
Then, in Tag use @Any to define a multi-targeted association:
@Entity public class Tag {
@Id
private Long id;
private String tag;
@CreationTimestamp
private Instant timeCreated;
@JoinColumn(name = "resource_id")
@Any(metaColumn = @Column(name = "resource_type"), optional = false, fetch = LAZY)
@AnyMetaDef(idType = "string", metaType = "string",
metaValues = {
@MetaValue(value = "post", targetEntity = Post.class),
@MetaValue(value = "story", targetEntity = Story.class),
})
private Object resource;
}
Adding a new Tag to a Post is straightforward, just assign the Post to the Tag.resource property (same for stories and all the other 'taggable' entities)
(Note that you might want to add a base class/marker interface like Taggable and use it instead of Object to restrict the types one may assign to the Tag.resource property. It should work, but I haven't tested it, so I'm not 100% sure)
2. @Where + explicit join column mapping in Tag
Use the same approach as before for Post and map the resource_id and resource_type columns as explicit properties:
@Entity public class Tag {
@Id
private Long id;
private String tag;
@CreationTimestamp
private Instant timeCreated;
@Column(name = "resource_id")
private String resourceId;
private String resourceType;
}
Creating a new Tag now requires you to populate resourceId and resourceType yourself. This approach makes a lot of sense if you want to treat Post and Tag as separate aggregate roots, otherwise it's pretty cumbersome and error-prone, as Hibernate does not help you ensure consistency, you need to manage it yourself.
3. Inheritance + mappedBy
Create separate entities for post tags, story tags etc. with the single inheritance strategy and treating the resource_type column as the discriminator value:
@Entity
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(name = "resource_type")
public abstract class Tag {
@Id
private Long id;
private String tag;
@CreationTimestamp
private Instant timeCreated;
}
@Entity
@DiscriminatorValue("post")
public class PostTag extends Tag {
@JoinColumn(name = "resource_id")
@ManyToOne(optional = false, fetch = LAZY)
private Post post;
}
@Entity
@DiscriminatorValue("story")
public class StoryTag extends Tag {
@JoinColumn(name = "resource_id")
@ManyToOne(optional = false, fetch = LAZY)
private Story story;
}
This solution has the advantage that, in the 'taggable' entity, you no longer need a 'fake' owning @OneToMany association but can instead use mappedBy:
@Entity public class Post {
@Id
private String id;
@OneToMany(mappedBy = "post")
private Collection<PostTag> tags;
}
@Entity public class Story {
@Id
private String id;
@OneToMany(mappedBy = "story")
private Collection<StoryTag> tags;
}
Adding a new Tag is also simplified (Want a new post tag? Create a PostTag object. Want a new story tag? Create a StoryTag object instead). In addition, if you ever want to switch to managing Tags using the Post.tags association (i.e. a unidirectional one-to-many), this approach will be the easiest to convert.
(note that in this scenario, you cannot, of course, rely on Hibernate to generate the schema, because it will then try to create FK constraints on resource_id column pointing to all the candidate tables)
I created a github repo with all three approaches represented as separate commits. For each approach, there's a test proving that it actually works. Note that the database structure is the same for all three scenarios.
(As a side note, I only now noticed the PRIMARY KEY (resource_type, namespace_id, tag) part of the table definition, so I have to ask: you do understand this question is asked and answered with a one-to-many association in mind, and not many-to-many, right?
I'm asking because with such a PK definition, at most one postcould have a tag with a given value of the tag column - for a given namespace_id, of course. I'm assuming that's a typo and what you really wanted was a PRIMARY KEY(id) coupled with UNIQUE(resource_type, resource_id, namespace_id, tag))