I am trying to create and Open Source library with JavBeans + Hibernate mappings that uses a provided database to read values (it only reads, no writes). Sadly, this database is not very well designed.
My problem is with a ManyToOne relationship that is optional - i.E. it can be null.
Here are the two tables (first is types, second is metaTypes):
+--------+---------------+
| typeID | typeName |
+--------+---------------+
| 1 | Chair |
| 2 | Table |
| 3 | Picnic Table |
| 4 | Bedside Table |
+--------+---------------+
+--------+--------------+
| typeID | parentTypeID |
+--------+--------------+
| 3 | 2 |
| 4 | 2 |
+--------+--------------+
So, now there is the problem how this all belongs together. In the types table there are all kinds of types, like a list of things thet can exist.
In the second table those things are grouped together. As you can see, the "picnic table" has an entry in the metaTypes table as being a child of the type table.
If the type is a base type, there is no corresponding entry in the metaTypes table. In a well designed database, one would at least except there be exist an entry with NULL as parentTypeID, but it doesn't. I solved this by using #NotFound(action = NotFoundAction.IGNORE) in the Type bean.
In the real database, there are more columns in the metaType table containing additional, relevant information, therefore I must have a MetaType bean.
After this lengthy introduction the real question occurs:
How do I map a Type to it's variations (MetaType)?
This is what I tried (shortened, getters & setters mostly ommited):
#Entity
#Table(name = "types")
public class Type {
#Id
private int typeID;
private String typeName, description;
// meta types/ variations
#OneToOne
#JoinColumn(name = "typeID", insertable = false, updatable = false, nullable = true)
#NotFound(action = NotFoundAction.IGNORE)
private MetaType metaType; // -> this works as expected
#OneToMany
#JoinColumn(name = "typeID", referencedColumnName = "parentTypeID")
#NotFound(action = NotFoundAction.IGNORE)
private List<MetaType> variations; // this doesn't work
/**
* This method is quite easy to explain: Either this type is a base
* type, in which case it has no metaType but holds the variations directly,
* or it is a dervied type, in which case we get it's parent type and list the
* variations of the parent type.
*/
public List<MetaType> getVariations () {
if (metaType != null) {
return metaType.getParentType().getVariations();
}
return variations;
}
}
#Entity
#Table (name = "metaTypes")
public class MetaType {
#Id
private int typeID;
private Integer parentTypeID;
// id <> object associations
#OneToOne (mappedBy = "metaType")
#JoinColumn(name = "typeID", insertable = false, updatable = false)
private Type type;
#OneToOne
#JoinColumn(name = "parentTypeID", referencedColumnName = "typeID", insertable = false, updatable = false)
private Type parentType;
}
The goal should be clear.
Let's say I query for the "Bedside Table". then metaType is not null but the variations list should be.
Let's say I queried for the "Table" type. In that case there is no MetaType and thanks to the #NotFound annotation, it silently fails and there is a null in the metaType field. So far, so good. But since "Table" has a typeID of 2 I would expect the variations list to include two entries. But instead I get the exception below.
But somehow this doesn't work, I get the below exception:
Exception in thread "main" java.lang.NullPointerException
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1456)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:864)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:779)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:728)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:70)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1695)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1424)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
So, what is wrong with the approach and how do I get this to work?
OK, the following is the way I did and it seems to work fine:
The Type Entity
#Entity
public class Type {
#Id
#Column(name = "typeId")
private Integer id;
#Column(name="typeName")
private String name;
#OneToMany
#JoinColumn(name="parentTypeId")
List<MetaType> metaTypes;
}
Notice that my #OneToMany is not using a mappedBy attribute. In this case I am using #JoinColumn instead. This is a unidirectional relationship.
The MetaType Entity
#Entity
public class MetaType {
#Id
private Integer id;
#MapsId
#OneToOne
#JoinColumn(name = "typeId")
private Type type;
#ManyToOne
#JoinColumn(name = "parentTypeId")
private Type parentType;
}
Now, I recover a given type, like the example you gave and I get the right data:
TypeService service = ...;
Type t = service.getTypeById(2);
System.out.println(t.getName());
for(MetaType mt : t.getMetaTypes()){
System.out.println("\t" + mt.getType().getName() + "-> " + mt.getParentType().getName());
}
This produces the output
Table
Picnic Table-> Table
Bedside Table-> Table
It also works correctly for types without variations. If you query for type 2 (chair), it will bring a Chair with empty variations collection, which I hope is what you were expecting.
Related
So I have a table with a column that has non-foreign key (no actual table reference) reference on another table but the other table might not have a matching row
class Component {
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "PART_ID", referencedColumnName="PART_ID", nullable = true, insertable = false, updatable = false)
#NotFound(action = NotFoundAction.IGNORE)
private Part part
#Id
#Column(name = "COMPONENT_ID")
private Long id;
}
Part class
class Part {
#Id
#Column(name = "PART_ID")
private Long id;
private String name;
}
without this #NotFound(action = NotFoundAction.IGNORE) I am getting an error
but with this I am not getting an error and get null value but I need the id to be present
e.g) I am looking for this
{"component": {"id":12, "part":{"id":100,"name":null}}}
but I am getting this (if no match)
{"component": {"id":12, "part":null}}
but I am getting this (if match)
{"component": {"id":12, "part":{"id":100,"name":"part_name"}}}
Tried with nullable=false and some combinations for updatable and insertable and still nothing works
If table has reference on another table but the other table might not have a matching row , the database structure is probably broken.
Do you really need what you're asking for?
The database has 2 part_id. One in Component and the other in Part. Java code has only one - Part.
You can make an additional field
сlass Component {
private Long partId;
...
and in the #PostLoad method - create a new Part with the desired id.
#PostLoad
public void postLoad(){
if(part == null && partId!=null){
part= new Part();
part.setId(partId)
}
}
}
I am facing a problem about how to manage mapping for a specific model.
This is a multitenant application, and we have made the choice of including the "tenant_id" in every entity, so we don't have to make a joint everytime we need to get an entity (in fact, this is the root of my problem...).
The model is as follow :
+--------------------+ +---------------+
| Book | | Author |
+--------------------+ +---------------+
| id (pk) | | id (pk) |
| tenant_id (pk)(fk) | | tenant_id (pk |
| author_id (fk) | | name |
| title | +---------------+
+--------------------+
As you can see, the tenant-id is in each entity, and part of the primary key. We use #IdClass to manage the composite key. Here is the code :
#Data
public class TenantAwareKey implements Serializable {
private UUID id;
private Integer tenantId;
}
#IdClass(TenantAwareKey.class)
#Entity
#Table(name = "BOOK")
#Data
public class Book {
#Id
#GeneratedValue
#Column(name = "ID")
private UUID id;
#Id
#Column(name = "TENANT_ID")
private Integer tenantId;
private String title;
#ManyToOne
#JoinColumns(
value = {
#JoinColumn(referencedColumnName = "id", name = "author_id"),
#JoinColumn(referencedColumnName = "tenant_id", name = "tenant_id", insertable = false, updatable = false)
})
private Author author;
}
#IdClass(TenantAwareKey.class)
#Entity
#Data
public class Author {
#Id
#GeneratedValue
#Column(name = TenantAwareConstant.ENTITY_ID_COLUMN_NAME)
private UUID id;
#Id
#Column(name = TenantAwareConstant.TENANT_ID_COLUMN_NAME)
private Integer tenantId;
private String name;
}
And then, when running my application I ended up with :
Caused by: org.hibernate.AnnotationException: Mixing insertable and non insertable columns in a property is not allowed:
com.pharmagest.durnal.tenant.entity.BookNoDuplicateColumn.author
at org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:725)
at org.hibernate.cfg.AnnotationBinder.bindManyToOne(AnnotationBinder.java:3084)
(...)
I manage to make it work if I don't try to "mutualize" the tenant_id column, it can be acceptable when I have only one foreign key with this tenant_id, but less and less as the number of foreign key increase, resulting in adding a tenant_id column each time, duplicating information et spoiling memory...
After digging a bit, I found an open issue in Hibernate : https://hibernate.atlassian.net/browse/HHH-6221
It has not been fixed for years... So, my question is : Have you faced a mapping like this, and is there a solution to avoid duplicated columen when I have a foreign-key that share a field with the primary key?
As described here, you can bypass the validation by using #JoinColumnOrFormula for the column id_tenant.
You should map the author's association like this:
#JoinColumnsOrFormulas(
value = {
#JoinColumnOrFormula(column = #JoinColumn(referencedColumnName = "id", name = "author_id")),
#JoinColumnOrFormula(formula = #JoinFormula(referencedColumnName = "tenant_id", value = "tenant_id"))
})
Building a Spring Boot REST service backed by MySQL here. I'm adding a super-simple chat feature to an app and this service will handle its backend/enndpoints. I'm new to JPA and have two concerns: (1) that my primordial data model itself may be a little awry; and (2) that I'm not wrapping that model correctly using JPA conventions/best practices.
So first: an overview of the simple problem I'm trying to solve: Users can send Messages to 1+ other Users. This creates a Conversation, which is really just a container of 1+ Messages. If the Conversation is only between 2 Users, it's considered (by the app) to be a Direct Message (DM). Otherwise its considered to be a Group Chat.
My tables (pseudo-schema):
[users]
=======
id PRIMARY KEY AUTO_INC INT NOT NULL,
username VARCHAR(255) NOT NULL
[conversations]
===============
id PRIMARY KEY AUTO_INC INT NOT NULL,
created_on DATETIME NOT NULL
[messages]
==========
id PRIMARY KEY AUTO_INC INT NOT NULL,
conversation_id FOREIGN KEY INT NOT NULL, # on conversations table
sender_id FOREIGN KEY INT NOT NULL, # on users table
text VARCHAR(2000) NOT NULL,
sent_at DATETIME
[users_x_conversations]
=======================
id PRIMARY KEY AUTO_INC INT NOT NULL,
conversation_id FOREIGN KEY INT NOT NULL, # on conversations table
user_id FOREIGN KEY INT NOT NULL, # on users table
So in my design above, you can see I'm really just using the [conversations] table as a placeholder and as a way of grouping messages to a single conversation_id, and then [users_x_conversations] is crosswalk (many-to-many) table where I'm actually storing who is a "member of" which conversation.
Is this the right approach to take or is there a better way to relate the tables here? That's Concern #1.
Assumning I'm modeling the problem at the database correctly, then I have the following JPA/entity classes:
#MappedSuperclass
abstract public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Ctors, getters & setters down here...
}
#Entity(name = 'messages')
#AttributeOverrides({
#AttributeOverride(name = 'id', column=#Column(name='message_id'))
})
public class Message extends BaseEntity {
#OneToOne(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinColumn(name = 'conversation_id', referencedColumnName = 'conversation_id')
#NotNull
#Valid
private Conversation conversation;
#OneToOne(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinColumn(name = 'user_id', referencedColumnName = 'user_id')
#NotNull
#Valid
private User sender;
#Column(name = 'message_text')
#NotEmpty
private String text;
#Column(name = 'message_sent_at')
#NotNull
private Date sentAt;
// Ctors, getters & setters down here...
}
#Entity(name = 'conversations')
#AttributeOverrides({
#AttributeOverride(name = 'id', column=#Column(name='conversation_id'))
})
public class Conversation extends BaseEntity {
#Column(name = 'conversation_created_on')
#NotNull
private Date createdOn;
// Ctors, getters & setters down here...
}
What I'm stuck on now is: how should I model my [users_x_conversations] table at the JPA layer? Should I create something like this:
#Entity(name = 'users_x_conversations')
#AttributeOverrides({
#AttributeOverride(name = 'id', column=#Column(name='users_x_conversations_id'))
})
public class UserConversations extends BaseEntity {
#ManyToMany(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
#JoinTable(
name="users_x_conversations",
joinColumns=[
#JoinColumn(name="user_id")
],
inverseJoinColumns=[
#JoinColumn(name="conversation_id")
]
)
private Map<User,Conversation> userConversations;
// Ctors, getters & setters down here...
}
Basically my service will want to be able to do queries like:
Given a conversationId, who are all the users that are members of that conversation?; and
Given a userId, what are all the conversations that user is a member of (DM and Group Chat alike)?
Is this the right approach to take or is there a better way to relate the tables here?
Your approach seems OK at the DB layer, except that if users_x_conversations serves only as a join table (i.e. if there are no extra properties associated with the (user, conversation) associations represented within), then I would use (conversation_id, user_id) as its PK instead of giving it a surrogate key. If you don't do that, then you should at least put a uniqueness constraint on that pair.
What I'm stuck on now is: how should I model my [users_x_conversations] table at the JPA layer?
I take you to be asking whether you should model that table as an entity. If you insist on giving it a surrogate key as you have done, then that implies "yes". But as I already discussed, I don't think that's needful. Nor much useful, for that matter. I would recommend instead modeling a direct many-to-many relationship between Conversation and User entities, with this table (less its id column) serving as the join table:
#Entity
#Table(name = "converations")
public class Conversation extends BaseEntity {
#Column(name = 'conversation_created_on')
#NotNull
private Date createdOn;
#ManyToMany(mappedBy = "conversations")
#JoinTable(name = "users_x_conversations",
joinColumns = #JoinColumn(name="conversation_id", nullable = false, updateable = false),
inverseJoinColumns = #JoinColumn(name = "user_id", nullable = false, updateable = false)
)
private Set<User> users;
// Ctors, getters & setters down here...
}
#Entity
#Table(name = "users")
public class User extends BaseEntity {
#NotNull
private String username;
#ManyToMany(mappedBy = "users")
// this is the non-owning side of the relationship; the join table mapping
// is declared on the other side
private Set<Conversation> conversations;
// Ctors, getters & setters down here...
}
Note in that case that User and Conversation entities are directly associated in the object model.
On the other hand, if you did choose to model users_x_conversations via an entity of its own, then the code you present for it is all wrong. It would look more like this:
#Entity
#Table(name = "users_x_converations", uniqueConstraints =
#UniqueConstraint(columnNames={"converation_id", "user_id"}))
public class UserConversation extends BaseEntity {
#ManyToOne(optional = false)
#JoinColumn(name = "conversation_id", nullable = false, updatable = false)
Conversation conversation;
#ManyToOne(optional = false)
#JoinColumn(name = "user_id", nullable = false, updatable = false)
User user;
// Ctors, getters & setters down here...
}
Note well that:
This makes the object-level association between Conversations and Users indirect, via UserConversation entities. If the relationships are navigable from the other side, then they would be modelled via #OneToMany relationship fields of type Set<UserConversation> or List<UserConversation>.
It requires more code, and more objects in the system at runtime.
On the other hand, it does have the minor advantage of saving you from making a somewhat arbitrary choice of which side of a direct #ManyToMany relationship is the owning side.
I have a problem with the auto update of foreign keys which appears as following:
I have a two tables HamKeyword and HamKeywordAlias. One entry in the hamKeyword has 0…n entries in HamKeywordAlias. This relationship is reflected with a foreign key field in the HamKeywordAlias table. Both tables have their own primary keys. I defined the two tables using reverse engineering of hibernate eclipse tools as follows:
#Entity
#Table(name = "HAM_KEYWORDS")
public class HamKeywords implements java.io.Serializable {
private long keywordid;
private String keyword;
…
#Id
#GenericGenerator(name="gen",strategy="increment")
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "KEYWORDID", unique = true)
public long getKeywordid() {
return this.keywordid;
}
…
#OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "hamKeywords")
public Set<HamKeywordsAlias> getHamKeywordsAliases() {
return this.hamKeywordsAliases;
}
#Entity
#Table(name = "HAM_KEYWORDS_ALIAS", schema = "dbo", catalog = "ham")
public class HamKeywordsAlias implements java.io.Serializable {
#Id
#GenericGenerator(name="gen",strategy="increment")
#GeneratedValue(generator="gen")
#Column(name = "ALIASID", unique = true, nullable = false)
public long getAliasid() {
return this.aliasid;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "KEYWORDID", nullable = false, updatable = false, insertable = true)
public HamKeywords getHamKeywords() {
return this.hamKeywords;
}
Now to my problem. I try to add a new entry to HamKeyword with 1 new related HamKeywordAlias:
HamKeywords hkw = new HamKeywords();
HamKeywordsAlias hka = new HamKeywordsAlias();
hka.setAlias("new alias");
hkw.setHamKeywordsAliases(new HashSet<HamKeywordsAlias>());
Set<HamKeywordsAlias> hkaS = hkw.getHamKeywordsAliases();
hkaS.add(hka);
hkw.setHamKeywordsAliases(hkaS);
session.flush();
session.save(hkw);
session.getTransaction().commit();
This code fails with the error message:
ERROR: The value NULL can not be inserted in table 'KEYWORDID'-Spalte, 'ham.dbo.HAM_KEYWORDS'. No NULL allowed for INSERT. Exception in thread "main" org.hibernate.exception.ConstraintViolationException: could not execute statement
(Please note that I translated the error message into english, it might be a bit different languagewise)
Obviously, the foreign key in field KEYWORDID of the HamKeywordAlias table is not be updated. I double checked this by removing the NOT NULL constraint. What happens is, that the enty into the ALIAS table is inserted but with a NULL in the field keywordid.
I tested furthermore adding manually rows into the HamKeywordAlias table. Retrieving an entry of the HamKeyword table and retrieving the related Aliases with following code works great:
HamKeywords hamCurrentKeyword = (HamKeywords) session.get(HamKeywords.class, (long)1);
hamCurrentKeyword.getHamKeywordsAliases();
Thus I assume that I defined the many to one relation correctly. However, the foreign key is not updated automatically.
Can you assist me why this is not be done?
Thanks
Felix
You have a bidirectional OneToMany association. The owner of the association is the Many side: HamKeywordsAlias.hamKeywords. That's the side that Hibernate cares about. But you didn't initialize it. You added an alias to the keywords' collection of aliases, but failed to set the keywords of the alias:
hka.setHamKeywords(hkw);
In which case do you use the JPA #JoinTable annotation?
EDIT 2017-04-29: As pointed to by some of the commenters, the JoinTable example does not need the mappedBy annotation attribute. In fact, recent versions of Hibernate refuse to start up by printing the following error:
org.hibernate.AnnotationException:
Associations marked as mappedBy must not define database mappings
like #JoinTable or #JoinColumn
Let's pretend that you have an entity named Project and another entity named Task and each project can have many tasks.
You can design the database schema for this scenario in two ways.
The first solution is to create a table named Project and another table named Task and add a foreign key column to the task table named project_id:
Project Task
------- ----
id id
name name
project_id
This way, it will be possible to determine the project for each row in the task table. If you use this approach, in your entity classes you won't need a join table:
#Entity
public class Project {
#OneToMany(mappedBy = "project")
private Collection<Task> tasks;
}
#Entity
public class Task {
#ManyToOne
private Project project;
}
The other solution is to use a third table, e.g. Project_Tasks, and store the relationship between projects and tasks in that table:
Project Task Project_Tasks
------- ---- -------------
id id project_id
name name task_id
The Project_Tasks table is called a "Join Table". To implement this second solution in JPA you need to use the #JoinTable annotation. For example, in order to implement a uni-directional one-to-many association, we can define our entities as such:
Project entity:
#Entity
public class Project {
#Id
#GeneratedValue
private Long pid;
private String name;
#JoinTable
#OneToMany
private List<Task> tasks;
public Long getPid() {
return pid;
}
public void setPid(Long pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Task> getTasks() {
return tasks;
}
public void setTasks(List<Task> tasks) {
this.tasks = tasks;
}
}
Task entity:
#Entity
public class Task {
#Id
#GeneratedValue
private Long tid;
private String name;
public Long getTid() {
return tid;
}
public void setTid(Long tid) {
this.tid = tid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
This will create the following database structure:
The #JoinTable annotation also lets you customize various aspects of the join table. For example, had we annotated the tasks property like this:
#JoinTable(
name = "MY_JT",
joinColumns = #JoinColumn(
name = "PROJ_ID",
referencedColumnName = "PID"
),
inverseJoinColumns = #JoinColumn(
name = "TASK_ID",
referencedColumnName = "TID"
)
)
#OneToMany
private List<Task> tasks;
The resulting database would have become:
Finally, if you want to create a schema for a many-to-many association, using a join table is the only available solution.
#ManyToMany associations
Most often, you will need to use #JoinTable annotation to specify the mapping of a many-to-many table relationship:
the name of the link table and
the two Foreign Key columns
So, assuming you have the following database tables:
In the Post entity, you would map this relationship, like this:
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(
name = "post_tag",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private List<Tag> tags = new ArrayList<>();
The #JoinTable annotation is used to specify the table name via the name attribute, as well as the Foreign Key column that references the post table (e.g., joinColumns) and the Foreign Key column in the post_tag link table that references the Tag entity via the inverseJoinColumns attribute.
Notice that the cascade attribute of the #ManyToMany annotation is set to PERSIST and MERGE only because cascading REMOVE is a bad idea since we the DELETE statement will be issued for the other parent record, tag in our case, not to the post_tag record.
Unidirectional #OneToMany associations
The unidirectional #OneToMany associations, that lack a #JoinColumn mapping, behave like many-to-many table relationships, rather than one-to-many.
So, assuming you have the following entity mappings:
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
//Constructors, getters and setters removed for brevity
}
#Entity(name = "PostComment")
#Table(name = "post_comment")
public class PostComment {
#Id
#GeneratedValue
private Long id;
private String review;
//Constructors, getters and setters removed for brevity
}
Hibernate will assume the following database schema for the above entity mapping:
As already explained, the unidirectional #OneToMany JPA mapping behaves like a many-to-many association.
To customize the link table, you can also use the #JoinTable annotation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinTable(
name = "post_comment_ref",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "post_comment_id")
)
private List<PostComment> comments = new ArrayList<>();
And now, the link table is going to be called post_comment_ref and the Foreign Key columns will be post_id, for the post table, and post_comment_id, for the post_comment table.
Unidirectional #OneToMany associations are not efficient, so you are better off using bidirectional #OneToMany associations or just the #ManyToOne side.
It's the only solution to map a ManyToMany association : you need a join table between the two entities tables to map the association.
It's also used for OneToMany (usually unidirectional) associations when you don't want to add a foreign key in the table of the many side and thus keep it independent of the one side.
Search for #JoinTable in the hibernate documentation for explanations and examples.
It's also cleaner to use #JoinTable when an Entity could be the child in several parent/child relationships with different types of parents. To follow up with Behrang's example, imagine a Task can be the child of Project, Person, Department, Study, and Process.
Should the task table have 5 nullable foreign key fields? I think not...
It lets you handle Many to Many relationship. Example:
Table 1: post
post has following columns
____________________
| ID | DATE |
|_________|_________|
| | |
|_________|_________|
Table 2: user
user has the following columns:
____________________
| ID |NAME |
|_________|_________|
| | |
|_________|_________|
Join Table lets you create a mapping using:
#JoinTable(
name="USER_POST",
joinColumns=#JoinColumn(name="USER_ID", referencedColumnName="ID"),
inverseJoinColumns=#JoinColumn(name="POST_ID", referencedColumnName="ID"))
will create a table:
____________________
| USER_ID| POST_ID |
|_________|_________|
| | |
|_________|_________|