When Should I Use #JoinColumn or #JoinTable with JPA? - java

#JoinColumn gives an Entity a foreign key to another Entity whereas #JoinTable will list the relationship between all relationships between Entity A and Entity B. As far as I can tell, they both appear to do similar things. When should I use one or the other?

Let's say you have an entity A which has a #ManyToOne association ot an entity B
#JoinColumn will define the target table Foreign Key (e.g B_ID) while using the target Entity table (e.g. B).
#Entity
public class A {
private Long id;
#ManyToOne
#JoinColumn(name="B_ID")
private B b;
}
#JoinTable will use a separate table to hold the relationship between A and B.
#Entity
public class A {
private Long id;
#ManyToOne
#JoinTable(
name = "A_B",
joinColumns = #JoinColumn(name = "B_ID"),
inverseJoinColumns = #JoinColumn(name = "A_ID")
)
private B b;
}
This time neither A nor B contain any Foreign Key, because there's a separate table (e.g. A_B) to hold the association between A and B.

#JoinTable stores the id of both the table into a separate table while #JoinColumn stores id of the another table in a new column.
#JoinTable : This is the default type. Use this when you you need a more normalized database. ie. to reduce redundancy.
#JoinColumn : Use this for better performance as it does not need to join extra table.

one important difference: #JoinColumn always depends upon the context it is used:
If the join is for a OneToOne or ManyToOne mapping using a foreign key mapping strategy, the foreign key column is in the table of the
source entity or embeddable.
If the join is for a unidirectional OneToMany mapping using a foreign key mapping strategy, the foreign key is in the table of
the target entity.
If the join is for a ManyToMany mapping or for a OneToOne or bidirectional ManyToOne/OneToMany mapping using a join table, the
foreign key is in a join table.
If the join is for an element collection, the foreign key is in a collection table.

Related

Hibernate throws error after migration (references on same collection)

I have a problem with using a OneToMany-Mapping with Hibernate that was working in version 4.3.8.Final but is not with version 5.4.2.Final .
When I query multiple entities from the db that have the same entries in a list that is mapped with OneToMany and then try to update those entities, hibernate throws the following exception even though I do not update any entries from the mapped relation: "Found shared reference to a collection: de.Artikel.filialeLager; nested exception is org.hibernate.HibernateException: Found shared reference to a collection: de.Artikel.filialeLager"
Here is my mapping, that was working fine with version 4.3.8.Final of Hibernate but is not anymore with 5.4.2.Final.:
#OneToMany(fetch = LAZY)
#JoinColumn(name = "SOME_ID", referencedColumnName="SOME_ID", insertable = false, updatable = false)
private List<filialeLager> filialeLager;
It seems to have a unidirectional relationship but is adding a reference column for a two-way relationship
The name of the column referenced by this foreign key column. When
used with entity relationship mappings other than the cases described
below, the referenced column is in the table of the target entity.
When used with a unidirectional OneToMany foreign key mapping, the
referenced column is in the table of the source entity. When used
inside a Join- Table annotation, the referenced key column is in the
entity table of the owning entity, or inverse entity if the join is
part of the inverse join definition. When used in a collection table
mapping, the referenced column is in the table of the entity
containing the collection.
You should have something similar to this:
#OneToMany(fetch = LAZY)
#JoinColumn(name = "SOME_ID", insertable = false, updatable = false)
private List<filialeLager> filialeLager;

Should Hibernate/JPA OneToMany be updated on both sides?

I have defined entities relation in Hibernate ORM with following code:
#Entity
public class Treatment {
#OneToMany(fetch = FetchType.EAGER, mappedBy="treatment")
private List<Consultation> consultations;
...
}
#Entity
public class Consultation {
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "treatment_id")
private Treatment treatment;
...
}
My question is, how should I update Treatment/Consultation when I want to make relation?
Is it enough to update it on one side:
treatment.getConsultations().add(newCon);
Or should I update it on both sides?
treatment.getConsultations().add(newCon);
newCon.setTreatment(treatment);
How it looks in case of delete?
Well, using the mappedBy you tell Hibernate that the relationship is maintained by the other side, a filed called treatment in Consultation class. So first you have to get the consultation instance, then set the treatment, and finally persist the consultation instance. It will update all the references in the DB as integrity constraints (Primary Key/ Foreign Key pairs). So here the consultation table will have a treatmentId foreign key column pointing to the ID column (primary key) of the Treatment table.
an example code is,
Consultation consultation = new Consultation();
// This maintains the relationship.
consultation.setTreatment(treatment);
someDaoRepository.save(consultation);
Hope this helps, Happy Coding !

Owner side with JoinColumns annotation

I have the following scenario:
I'm having troubles figuring out what the owner side is and why ?
Please can you help ?
public class Basket
{
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({#JoinColumn(name="BASKET", referencedColumnName="ID")})
public Set<Product> getProductList() {
return this.productList;
}
}
public class Product
{
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({#JoinColumn(name="BASKET", referencedColumnName="ID")})
public Basket getBasket() {
return this.basket;
}
}
Basket is the "One" side of the relationship and there are many products identified as Baskets. So the "one" is usually the parent code and the child records reference the parent. Usually the easiest way to understand these is to look at the database foreign key constraint (hopefully there is one) that connects the backing tables.
One client has One basket and One basket has One owner, so:
class Owner has basket in relation #OneToOne.
One basket has many products, so:
class Basket has List of products in relation #OneToMany.
Many Products has One basket so:
class Product has relation in #ManyToOne.
The extract from the JPA 2.0 specification might help you to understand the issue:
2.10.2Bidirectional ManyToOne / OneToMany Relationships
Assuming that:
Entity A references a single instance of Entity B.
Entity B references a collection of Entity A[21].
Entity A must be the owner of the relationship.
The following mapping defaults apply:
Entity A is mapped to a table named A.
Entity B is mapped to a table named B.
Table A contains a foreign key to table B. The foreign key column name is formed as the concatenation of the following: the name of the relationship property or field of entity A; "_"; the name of the primary key column in table B. The foreign key column has the same type as the
primary key of table B.
And in section 11.1.21 the following is stated:
If there is more than one join column, a JoinColumn annotation must be specified for each join column using the JoinColumns annotation. Both the name and the referencedColumnName elements must be specified in each such JoinColumn annotation.
In your case, there is only one join column. So you don't need to the #JoinColumns annotation. Just use#JoinColumn. The #JoinColumn annotation is used always on the owning side of the relationship, that is the #ManyToOne side in this case.

foreign key constraint in One To One relationship in JPA hibernate

I have two entities A and B with one-to-one relationship. When I want to insert them into DB, I get the following error:
Cannot add or update a child row: a foreign key constraint fails
(mydb.a, CONSTRAINT
FK_77pkrkrin5nqsx16b6nw6k9r7 FOREIGN KEY (id) REFERENCES
b (b_id))
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true, value={"hibernateLazyInitializer", "handler"})
#Generated("org.jsonschema2pojo")
#Inheritance(strategy = InheritanceType.JOINED)
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonIgnore
private Integer id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn(referencedColumnName="AId")
#JsonIgnore
private B b;
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true, value = {"hibernateLazyInitializer", "handler"})
#Generated("org.jsonschema2pojo")
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int bId;
#OneToOne()
#JsonIgnore
private A a;
}
The insert operation will work perfectly if I remove the optional=false. I checked the objects before inserting them into DB and I make sure that A contains B and B contains A.
The SQL scripts for A and B creation are:
Hibernate: create table b (b_id integer not null auto_increment,
string_results longtext, a_id integer, primary key (b_id))
Hibernate: create table a (id integer not null auto_increment, primary
key (id))
Hibernate: alter table b add constraint FK_o3oen721etlltdc7ls82524nh
foreign key (detail_id) references a (id)
Hibernate: alter table a add constraint FK_77pkrkrin5nqsx16b6nw6k9r7
foreign key (id) references b (b_id)
When I see the following sentence:
I checked the objects before inserting them into DB and I make sure that A contains B and B contains A.
I guess that you want to create a bidirectional one-to-one relationship. If so, your current mapping does not work as expected. Let us see what the JPA 2.0 spec(download link) states about this to understand the matter:
Relationships may be bidirectional or unidirectional. A bidirectional relationship has both an owning side and an inverse (non-owning) side. A unidirectional relationship has only an owning side. The owning side of a relationship determines the updates to the relationship in the database, as described in section 3.2.4.
The following rules apply to bidirectional relationships:
• The inverse side of a bidirectional relationship must refer to its owning side by use of the mappedBy element of the OneToOne, OneToMany, or ManyToMany annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.
•The many side of one-to-many / many-to-one bidirectional relationships must be the owning side, hence the mappedBy element cannot be specified on the ManyToOne annotation.
• For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
• For many-to-many bidirectional relationships either side may be the owning side.
So, according to the specification in a bidirectional one-to-one relationship one of the entities must be made the owning side and the other one the inverse side. Let assume entity A is the owning side the following mapping should work:
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="b_id", referencedColumnName="ID")
private B b;
}
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToOne(mappedBy = "b")
private A a;
}
In order to make the above mapping work either the physical tables must be generated automatically or if you want to create the tables yourself, the corresponding SQL should look the following:
create table a (id integer not null auto_increment,
b_id integer not null,
primary key (id),
foreign key b_id references b (id));
create table b (id integer not null auto_increment,
string_results longtext,
primary key (id));
NOTE:
I removed the JSON-specific annotation to make the code shorter (I don't have any knowledge about them)
If you want to make entity B the owning side, you have to adjust the relationship mapping accordingly.
The #JoinColumn annotation is always on the owning side.
Due to lack of time I haven't tested the code. If you find any bug (especially the MySQL syntax) just leave me a comment.
From the hibernate docs:
(Optional) Whether the association is optional. If set to false then a non-null relationship must always exist.
Right at the time of creation of B, the b_id is not available, it is created when the flush happens to database.
Since A has a foreign key relation on B(b_id), B needs to be created along with its id before you can insert A or mark this foreign relation as optional.
Create B and flush it to database, then create A. B has A is not required as its A who defines a foreign key to B and not vice-versa, B's reference to A is only reflexive, two-way foreign key will create cyclic reference issue.

JPA: difference between #JoinColumn and #PrimaryKeyJoinColumn?

What's the exact difference between #JoinColumn and #PrimaryKeyJoinColumn?
You use #JoinColumn for columns that are part of a foreign key. A typical column could look like (e.g. in a join table with additional attributes):
#ManyToOne
#JoinColumn(name = "...")
private OtherClass oc;
What happens if I promote the column to be a/the PK, too (a.k.a. identifying relationship)? As the column is now the PK, I must tag it with #Id:
#Id
#ManyToOne
#JoinColumn(name = "...")
private OtherClass oc;
Now the question is:
Are #Id + #JoinColumn the same as just #PrimaryKeyJoinColumn?:
#ManyToOne
#PrimaryKeyJoinColumn(name = "...")
private OtherClass oc;
If not, what's #PrimaryKeyJoinColumn there for?
What happens if I promote the column to be a/the PK, too (a.k.a. identifying relationship)? As the column is now the PK, I must tag it with #Id (...).
This enhanced support of derived identifiers is actually part of the new stuff in JPA 2.0 (see the section 2.4.1 Primary Keys Corresponding to Derived Identities in the JPA 2.0 specification), JPA 1.0 doesn't allow Id on a OneToOne or ManyToOne. With JPA 1.0, you'd have to use PrimaryKeyJoinColumn and also define a Basic Id mapping for the foreign key column.
Now the question is: are #Id + #JoinColumn the same as just #PrimaryKeyJoinColumn?
You can obtain a similar result but using an Id on OneToOne or ManyToOne is much simpler and is the preferred way to map derived identifiers with JPA 2.0. PrimaryKeyJoinColumn might still be used in a JOINED inheritance strategy. Below the relevant section from the JPA 2.0 specification:
11.1.40 PrimaryKeyJoinColumn Annotation
The PrimaryKeyJoinColumn annotation
specifies a primary key column that is
used as a foreign key to join to
another table.
The PrimaryKeyJoinColumn annotation
is used to join the primary table of
an entity subclass in the JOINED
mapping strategy to the primary table
of its superclass; it is used within a
SecondaryTable annotation to join a
secondary table to a primary table;
and it may be used in a OneToOne
mapping in which the primary key of
the referencing entity is used as a
foreign key to the referenced
entity[108].
...
If no PrimaryKeyJoinColumn
annotation is specified for a subclass
in the JOINED mapping strategy, the
foreign key columns are assumed to
have the same names as the primary key
columns of the primary table of the
superclass.
...
Example: Customer and ValuedCustomer subclass
#Entity
#Table(name="CUST")
#Inheritance(strategy=JOINED)
#DiscriminatorValue("CUST")
public class Customer { ... }
#Entity
#Table(name="VCUST")
#DiscriminatorValue("VCUST")
#PrimaryKeyJoinColumn(name="CUST_ID")
public class ValuedCustomer extends Customer { ... }
[108] The derived id mechanisms
described in section 2.4.1.1 are now
to be preferred over
PrimaryKeyJoinColumn for the
OneToOne mapping case.
See also
Primary Keys through OneToOne Relationships
This source http://weblogs.java.net/blog/felipegaucho/archive/2009/10/24/jpa-join-table-additional-state states that using #ManyToOne and #Id works with JPA 1.x. Who's correct now?
The author is using a pre release JPA 2.0 compliant version of EclipseLink (version 2.0.0-M7 at the time of the article) to write an article about JPA 1.0(!). This article is misleading, the author is using something that is NOT part of JPA 1.0.
For the record, support of Id on OneToOne and ManyToOne has been added in EclipseLink 1.1 (see this message from James Sutherland, EclipseLink comitter and main contributor of the Java Persistence wiki book). But let me insist, this is NOT part of JPA 1.0.
I normally differentiate these two via this diagram:
Use PrimaryKeyJoinColumn
Use JoinColumn
I know this is an old post, but a good time to use PrimaryKeyColumn would be if you wanted a unidirectional relationship or had multiple tables all sharing the same id.
In general this is a bad idea and it would be better to use foreign key relationships with JoinColumn.
Having said that, if you are working on an older database that used a system like this then that would be a good time to use it.
You use #JoinColumn when you want to manage (change the column name, set nullable and so on) the foreign key column in the target entity table. Here, the Address table will contains User table id like foreign key but the column it's will be name user_id (the second scheme of #Sam YC)
#Entity
public class Address implements Serializable {
#Id
#GeneratedValue
private String id;
private String city;
#OneToOne(optional = false)
#JoinColumn(name = "user_id", updatable = false)
private User user;
}
You use #PrimaryKeyJoinColumn, when you want to use the primary key of the referencing entity like the target entity primary key. Here the Address know the referencing User but Address table hasn't foreign key column, because it's has the same id than User Id (the first scheme of #Sam YC)
#Entity
public class Address implements Serializable {
#Id
#GeneratedValue(generator = "foreignKeyGenerator")
#GenericGenerator(
name = "foreignKeyGenerator",
strategy = "foreign",
parameters = #org.hibernate.annotations.Parameter(
name = "property", value = "userT"
)
)
private String id;
private String city;
#OneToOne(optional = false)
#PrimaryKeyJoinColumn
private User userT;
}

Categories