what is #JoinColumn and how it is used in Hibernate - java

I have been reading a lot about #JoinColumn but I still don't get the idea behind it.
Patient Table
CREATE TABLE patient (
patient_id BIGINT NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
PRIMARY KEY(patient_id));
Vehicle Table
CREATE TABLE vehicles (
patient_id BIGINT NOT NULL,
vehicle_id BIGINT NOT NULL,
vehicle_manufacturer VARCHAR(255),
PRIMARY KEY (vehicle_id),
CONSTRAINT patienthasmanyvehicle FOREIGN KEY(patient_id) REFERENCES patient(patient_id));
Patient Class
#OneToMany(mappedBy = "patient")
private Collection<Vehicle> patientVehicles = new ArrayList<Vehicle>();
Vehicle Class
#ManyToOne
#JoinColumn(name="patient_id")
private Patient patient;
I'm confused on how the Vehicle class part, what is the relationship between
Vehicle Class ---- Entity
#JoinColumn(name="patient_id") ---- annotation
private Patient patient ----field
Does it say; The Vehicle Entity has a Foreign Key to Patient entity named patient_id.
Add the patient_id as a column in the Vehicle Entity table
Do the name parameter of the JoinColumn should always be a Foreign Key or Primary Key?
I have been reading this but I'm still confuse.
JPA JoinColumn vs mappedBy

A unidirectional association via a join table
#Entity
class Patient {
#OneToMany
private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
}
#Entity
class Vehicle {
}
A bidirectional association via a join table
#Entity
class Patient {
#OneToMany
private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
}
#Entity
class Vehicle {
#ManyToOne(fetch = FetchType.LAZY)
private Patient patient;
}
A unidirectional association via a foreign key
#Entity
class Patient {
#OneToMany
#JoinColumn
private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
}
#Entity
class Vehicle {
}
A bidirectional association via a foreign key
#Entity
class Patient {
#OneToMany(mappedBy = "patient")
private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
}
#Entity
class Vehicle {
#ManyToOne(fetch = FetchType.LAZY)
private Patient patient;
}
We don't need to use #JoinColumn on the Vehicle side, Hibernate assumes
it by default. Sometimes I use it just to stress it out (another case, when we want to specify a join column name).
#Entity
class Vehicle {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
private Patient patient;
}
A bidirectional association via a foreign key with a foreign column name specification
#Entity
class Patient {
#OneToMany(mappedBy = "patient")
private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
}
#Entity
class Vehicle {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="patient_id")
private Patient patient;
}
This is the basic starting point of using #JoinColumn.
To verify that the foreign key(patient_id in the Vehicle table) is really mapped in the patients table you can use #JoinColumn(nullable = false)
#Entity
class Vehicle {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="patient_id", nullable = false)
private Patient patient
}

The join column is declared with the #JoinColumn annotation which looks like the #Column annotation. It has one more parameters named referencedColumnName. This parameter declares the column in the targeted entity that will be used to the join.
In a bidirectional relationship, one of the sides (and only one) has to be the owner: the owner is responsible for the association column(s) update. To declare a side as not responsible for the relationship, the attribute mappedBy is used. mappedBy refers to the property name of the association on the owner side.
Here is Sample code :
EntityOne :
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TEST_ID")
private EntityTwo entityTwo;
EntityTwo :
// bi-directional many-to-one association to EntityOne Here TEST_ID is the Primary key
#OneToMany(mappedBy = "entityTwo")
private List<EntityOne> entityOne;

Vehicle Class ---- Entity
#JoinColumn(name="patient_id") ---- annotation
private Patient patient ----field
Above code will generate a column patient_id (a foreign key) in Vehicle class which will point to Patient Class primary key.
MappedBy - This attribute tells us that this relation will be managed by Vehicle class. Example. If we insert a vehicle, then two SQL will be injected if cascadetype is all/save. 1st SQL will inject details in Patient table and 2nd SQL will inject vehicle details in vehicle table with patient_id column of Vehicle column pointing to Patient tuple inserted.

The table in which join column will be found depends upon context.
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.

Why is that the patient_id (generated column which is a FK) in the
Vehicle Table doesn't have any value when I run my code?
All #JoinColumn does is to specify a column for joining an entity association or element collection. Since you have made #JoinColumn associated with Patient class object, that's why foreign key is created on Patient table.
For more please refer https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/JoinColumn.html

Related

JPA one-to-one mapping creates an extra column. How to remove it?

I have one-to-one mapping JPA table in my springboot application which works fine.
The Users is the parent table and in the account_no column, it stores the foreign key. Which is, child's primary key. The child is Account table.
However, when the application is started, I can see that there is one additional column (user_id) that has been created in H2 DB. I think it is something wrong with my JPA mapping. Pls help to figure it out. Below are the two classes.
#Entity
public class User extends AbstractEntity {
// Other fields related to user entity go here ..
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "account_no", referencedColumnName = "account_num")
private Account account;
}
#Entity
public class Account extends AbstractEntity{
// fields like account#, balance etc goes here..
#Column(name="account_num", unique = true)
#NotNull
private long accountNo;
#OneToOne (fetch = FetchType.LAZY)
private User user;
}
Startup log.
create table account (id bigint not null, account_num bigint not null, bal float not null, user_id bigint, primary key (id))
2021-12-22 00:09:28.765 DEBUG 25380 --- [ main] org.hibernate.SQL :
Decide which side should contain the extra column and use the mappedBy attribute. Then JPA will do what's needed
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, mappedBy = "user")
private Account account;
Considering that you have bidirectional mapping you don't need the #JoinColumn that you have used.
Just both #OneToOne annotations and the decision which would be owner entity of the relation by using the mappedBy attribute on one of those annotations.
It depends on you in which side you store the extra column which was a
foreign key.Because When You started to established oneToOne
relationship between two table it is mandatory to store user's
primary key in account table as a foreign key, or account table
primary key in user table as foreign key.You should be need to declare
mappedBy and your tables reference name.for example if you declare
mappedBy="user" in account table it create an extra account_id column
in user table as a foreign key, Same as for account
#OneToOne(optional = false, mappedBy = "user")
private Account account;
Don't need to declare these things in side OneToOne annotation cascade
= CascadeType.ALL, fetch = FetchType.LAZY, By default it always support lazy fetch
.

JPA ManyToOne Cascade On UPDATE with JPQL

I have this Parent class
#Entity
#Table(name = "category")
#NamedQuery(name = "category.findAll", query = "SELECT c FROM Category c")
public class Category implements Serializable {
public Category(){}
#Column(name = "name", nullable = false)
#Id
private String name;
#Column(name = "col2")
private Boolean col2;
}
And i have referenced the parent table in child table as follows:
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "cat_name")
private Category category
when i run this JPQL query
update Category c SET c.name=:newName ,c.termsCanHaveChildren=:canHaveChdrn where c.name=:oldName
it's return with foreign key constraint error while i have put Cascade All in child field
Cannot delete or update a parent row: a foreign key constraint fails (`terms`.`term`, CONSTRAINT `FKaykenypxci167nqioh4xx9p3a` FOREIGN KEY (`cat_name`) REFERENCES `category` (`name`))
The problem lays at the constraint being generated by your persistence provider (hibernate), for the #JoinColumn(name = "cat_name") at the child table (and not with the CascadeType that you're defining)...
The generated constraint should indicated that when the PK of Category is Updated, any reference to such column should be updated also...
I believe this configuration should work (but you need to test it first, because I always generated my database model using scripts and not using hibernate features):
#ManyToOne
#JoinColumn(
name = "cat_name",
foreignKey = #ForeingKey(
name = "fk_child_category",
foreignKeyDefinition = "FOREIGN KEY (cat_name) REFERENCES category ON UPDATE CASCADE"
)
)
private Category category;
Also you need to check if your database supports "ON UPDATE CASCADE"... According to this link, oracle does not... (What database are you using?)
If this does not work, try the suggestion of Michelle...
That's expected: you are changing the Primary Key (#Id), that's used in a Foreign Key (#JoinColumn).
Use a surrogated immutable primary key.

Hibernate Many-To-One Relationship without Foreign Key but with reverse foreign key

I have the following DB:
CREATE TABLE car_owner (
car_owner_id int(11) NOT NULL,
car_id_fk int(11) DEFAULT NULL,
PRIMARY KEY (car_owner_id),
KEY car_owner_car_fk_idx (car_id_fk),
CONSTRAINT car_owner_car_fk FOREIGN KEY (car_id_fk) REFERENCES car (car_id) ON DELETE NO ACTION ON UPDATE NO ACTION,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE car (
car_id int(11) NOT NULL AUTO_INCREMENT,
car_type varchar(45) DEFAULT NULL,
car_plates varchar(25) DEFAULT NULL,
PRIMARY KEY (car_id),
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
And In the java model:
For CarOwner I have:
#Entity
#Table(name="car_owner")
#NamedQuery(name="CarOwner.findAll", query="SELECT co FROM CarOwner co")
public class CarOwner implements Serializable {
#Id
#GeneratedValue
#Column(name="car_owner_id")
private Integer carOwnerId;
.....
//bi-directional many-to-one association to Car
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "car_id_fk", referencedColumnName = "car_id")
private List<Car> cars;
And for Car:
#Entity
#Table(name="car")
#NamedQuery(name="Car.findAll", query="SELECT c FROM Car c")
public class Car implements Serializable {
#Id
#GeneratedValue
#Column(name="car_id")
private Integer carId;
......
//bi-directional many-to-one association to car_owner
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "car_owner_id")
private CarOwner carOwner;
The problem here is that Hibernate can't relate the tables and goes creating new car_id and car_owner_id columns in car table automatically.
Can anybody help in finding the right combination in the model to relate the tables appropriately.
#JoinColumn should be in owner of relationship (in a one to many it's the many side that's regarded the owner).
So I will modify this in car
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "car_owner_id",insertable=false, updatable=false)
private CarOwner carOwner;
And this in CarOwner
#OneToMany(fetch = FetchType.EAGER,mappedBy = "carOwner")
private List<Car> cars;
As a side note I would also not be using EAGER but that has got nothing to do with question.
Both tables knowing about each other is called Bi-directional relationship.This happens when each table has a key to other table. This is what your java code is expecting.Your tables in database however have a uni-directional relationship. Meaning one table knows about the other but not both. Your car_owner knows about car because of foriegn key CONSTRAINT car_owner_car_fk FOREIGN KEY but your car does not have any idea about car_owner both are perfectly valid.
Now the problem is that in your Java code you are treating it as a bi-directional relationship.
//bi-directional many-to-one association to car_owner
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "car_owner_id")
private CarOwner carOwner;
but car does not have car_owner_id why are you treating it a Bi-directional relationship.
Now either update database to make them bi or change java code.

Hibernate - How to use a generator to create a primary key for a reference table?

I'am struggling to get Hibernate (with MySQL) to generate the primary key for a reference table from a "main table". My problem is that I got a big table with 25 mil rows and now I need to add multiple additional columns and because in the future where will be even more columns to add I choose the way to work with reference tables instead of adding the columns to the main table (the rebuild takes hours... :)
So there is a main table and a reference table. In my conception the primary key of the reference table should be generated from the primary key of the main table. I could first insert an entry into the main table, then select it and use its primary key for the insert into the reference table, but this seems not to be the best way to me. So I would like to use Hiibernate's generators, but I can't figure out how.
Here's the main table:
#Entity
#Table
public class Task {
#Id
#GeneratedValue
#Column()
private Integer id;
// ...
#OneToOne(mappedBy = "task_ref", orphanRemoval=true, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Stuff stuff;
// ...
}
And the reference table:
#Entity
#Table
public class Stuff {
#Id
#Column(name = "stuff_id")
#GeneratedValue()
private Integer stuff_id;
// ...
#OneToOne(orphanRemoval=true, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Task task_ref;
// ...
}
So.. how can I use a generator to create the primary key for the table Stuff from the primary key of the table Task ?
Any suggestions or other solutions are highly welcome!
Thanks!
This is how you should map your bidirectional OneToOne association with a shared primary key:
#Entity
#Table
public class Task {
#Id
#GeneratedValue
private Integer id;
// ...
#OneToOne(mappedBy = "task_ref", orphanRemoval=true, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Stuff stuff;
// ...
}
#Entity
#Table
public class Stuff {
#Id
#Column(name = "stuff_id")
private Integer stuff_id;
// ...
#OneToOne(fetch = FetchType.LAZY)
#MapsId
#JoinColumn(name = "stuff_id")
private Task task_ref;
// ...
}
Only the parent needs to cascade to the Child entity, not the other way around.
The Parent only has an "inverse" side of the association.
The shared primary key is both a primary key and a foreign key in the Child entity
The MapsId annotation allows you to share the primary key for both the #Id and the OneToOne association
You could use the Task object as your id, see EmbeddedId or IdClass for an example. In case Stuff represents a subclass of the Task entity in your domain model, you should model this entity as such, in which you will have to represent the Stuff entity as a Joined Subclass of the Task entity. In both cases the extra Id in the Stuff entity is not needed anymore.

JPA (hibernate) onetomany relation

I am not sure what I am missing to make a bidirectional onetomany relationship (hibernate engine). A scaled down version of the domain model:
class Person {
#OneToMany(mappedBy="personFrom", cascade = CascadeType.PERSIST)
public List<Relationship> relationships;
}
class Relationship {
#ManyToOne
public Person personFrom;
#ManyToOne
public Person personTo;
}
Some of the observations:
1. with the above mapping, there is no join table created.
2. When I remove the mappedBy (#OneToMany(cascade = CascadeType.PERSIST) ), the join table is created and i could persist Relationship through Person. "personFrom" field is empty, but I think that is normal as the relation is maintained through the join table.
I also tried by specifying join column at Relationship, didn't make any difference. Any help, highly appreciated.
thanks.
Edit:1
As per Dan's comment, if it matters to see the full content of the domain class, I have expanded them below.
class Relationship extends Model{
#ManyToOne
public RelationshipType relationshipType;
#ManyToOne
public Person personFrom;
#ManyToOne
public Person personTo;
#ManyToOne
public Person createdBy;
#ManyToOne
public Role roleFrom;
#ManyToOne
public Role roleTo;
#Override
public String toString() {
return relationshipType.toString();
}
}
class Person extends Model {
public Date dateCreated;
#Lob
public String description;
#OneToMany(cascade = CascadeType.ALL)
public List<Role> roles;
#OneToMany(mappedBy="personFrom", cascade = CascadeType.PERSIST)
public List<Relationship> relationships;
}
Role also related to Person, but I think keeping the personFrom, personTo helps to optimize my queries.
Role extends Model {
#ManyToOne
public RoleType roleType;
#ManyToOne
public Person createdBy;
}
with the above mapping, there is no join table created.
A join table is not required for a OneToMany, you'll get foreign key column in the Many side. And this is what I get when using your code:
create table Person (
id bigint not null,
primary key (id)
)
create table Relationship (
id bigint not null,
personFrom_id bigint,
personTo_id bigint,
primary key (id)
)
alter table Relationship
add constraint FK499B69164A731563
foreign key (personTo_id)
references Person
alter table Relationship
add constraint FK499B691698EA8314
foreign key (personFrom_id)
references Person
Which is the expected result (at least for me). Maybe what you actually want is a ManyToMany.
When I remove the mappedBy (#OneToMany(cascade = CascadeType.PERSIST) ), the join table is created and i could persist Relationship through Person. "personFrom" field is empty, but I think that is normal as the relation is maintained through the join table.
I wrote a small unit test using the provided code (with Hibernate's API but this doesn't change anything) and I don't get what the problem is (the session is created before the test method and the method runs inside a transaction):
Person p1 = new Person();
Person p2 = new Person();
Relationship r = new Relationship();
// create the personFrom bi-directional association
r.setPersonFrom(p1);
List<Relationship> relationships = new ArrayList<Relationship>();
relationships.add(r);
p1.setRelationships(relationships); // these four lines should be moved to some
// link management method (see update below).
// create the personTo uni-directional association
r.setPersonTo(p2);
session.persist(p2);
session.persist(p1);
assertNotNull(p2.getId());
assertNotNull(p1.getId());
assertNotNull(r.getId());
The above code results in two insert in the Person table and one insert in the Relationship table (valuing the 3 columns).
As I said, I don't get the problem. Maybe you should explain what the expected result is (both the relational model and the queries).
Update: To be totally clear, when working with bi-directional associations, you have to set both sides of the link and a common pattern is to use defensive link management methods to correctly set both sides of the association. Something like this:
public void addToRelationships(Relationship relationship) {
if (this.relationships == null) {
this.relationships = new ArrayList<Relationship>();
}
this.relationships.add(relationship);
relationship.setPersonFrom(this);
}
This is detailed in the section 1.2.6. Working bi-directional links of the Hibernate documentation.
Did you specify the foreign key column name as the name of your join column? Assuming the foreign key column is named PERSON_ID, the code should look something like this:
class Relationship {
#ManyToOne
#JoinColumn(name="PERSON_ID")
public Person personFrom;
...
}

Categories