I have a class hierarchy that i've mapped to a database with Hibernate using the single table per class hierarchy strategy.
Here is the UML of my class Hierarchy
class hierarchy UML diagram
The Hibernate mapping is as follows
#Entity
#Table(name = "FRUIT_HIERARCHY")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
public abstract class Fruit{
#Id
#Column(name = "id_registred_user")
private long id;
}
#DiscriminatorValue(value = "Banana")
public class Banana extends Fruit{}
#DiscriminatorValue(value = "Orange")
public class Orange extends Fruit{
#OneToMany(mappedBy = "orange", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Note> noteList;
}
Here is the details concerning my Postgresql database
database schema
Here is the database
create table FRUIT_HIERARCHY (
ID_FRUIT SERIAL not null,
DISCRIMINATOR VARCHAR(50) null,
constraint PK_FRUIT_HIERARCHY primary key (ID_FRUIT)
);
create table NOTE (
ID_NOTE SERIAL not null,
ID_FRUIT INT4 not null,
constraint PK_NOTE primary key (ID_NOTE)
);
alter table NOTE
add constraint FK_NOTE_FRUIT foreign key (ID_FRUIT)
references FRUIT_HIERARCHY (ID_FRUIT)
on delete restrict on update restrict;
The problem comes when i have to implement the hibernate association between Orange class and Note. An Orange has 0 to n Notes and a Note belong to one Orange. And Orange is a subclass of Fruit.
#Entity
#Table(name = NOTE)
public class Note{
#Id
#Column(name = "id_note")
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "id_fruit")
private Orange orange
}
Is the this Hibernate association mapping correct knowing that Orange is a subclass of Fruit? Will hibernate retrieve an Orange object from the table FRUIT_HIERARCHY? should i map it by putting a Fruit attribute and using casting when it comes to use an Orange object?
Thank you.
Related
I am using eclipselink 2.5.1.
Let's say I have these two class.
JAVA
#Entity
public class Car implements Serializable {
#EmbeddedId
protected CarPK carPK;
private String color;
#ManyToOne(fetch = FetchType.LAZY)
private Manufacturor manufacturor;
//constructors, getters & setters...
}
#Embeddable
public class CarPK implements Serializable {
#NotNull
private int idManufacturor;
#Temporal(javax.persistence.TemporalType.DATE)
private Date date;
//constructors, getters & setters...
}
Car has a composite primary key (idManufacturor and date) and idManufacturor is also a foreign key referencing the class Manufacturor.
I'm having issue with the mapping. EclipseLink understand the manufacturor object as a column in my Car table.
Error
Internal Exception: com.microsoft.sqlserver.jdbc.SQLServerException: invalid column nameĀ : 'manufacturor'.
I know the problem will be solved if I add a column manufacturor FK but it would be repeating.
Please feel free to ask for any precision if I'm not clear enough.
Thank you for your help.
Add the JoinColumn Annotation
#JoinColumn(name = "id_manufacturor", referencedColumnName = "id")
Name is the FK column name in your database (not entity).
The referencedColumnName "id" must correspond to the defined id in manufacturer table.
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
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.
I started studing JPA by reading a book and they gave an unidirection mapping which is like this:
table vehicle
(vehicleId, brand, model, dev_year, extraId)
vehicleId is the PK,
extraId is the FK
table travel_extra
(id, allowSmoke, allowFood, allowDrinks, airConditioner)
id is the PK
Then the java objects:
public class Vehicle {
#Id
private long vehicleId;
private String brand;
private String model;
#OneToOne
#JoinColumn(name = "vehicleId")
private TravelExtra travelExtra;
}
public class TravelExtra {
#Id
private id;
private boolean allowSmoke;
private boolean allowFood;
private boolean allowDrinks;
private boolean airConditioner;
}
When i persist and commit the transaction it works like a charm.
However! for my case i don't want the vehicle table to have a foreign key but i want the foreign key to be in the travel_extra table and link to the primary key of vehicle table. BUT when i do that the code doesnt work and no matter what i try to do i can not make it work.
If somebody has ever experianced such thing i will be happy for some assitance and examples of how to make it works.
Thanks in advance.
Try with #JoinColumn(name = "vehicleId", insertable = false, updateable = false), and create a vehicleID column in TravelExtra table. insertable = false, updateable = false should instruct ORM to look for the column in target table.
EDIT
My reference is this, where it states:
In JPA the JoinColumn defines an insertable and updatable attribute, these can be used to instruct the JPA provider that the foreign key is actually in the target object's table
If it doesn't work (since it is not guaranteed by the specification), you can make the relation bidirectional (just add a mapping in TravelExtra entity, no further database changes needed)
public class Vehicle {
...
#OneToOne(mappedBy = "vehicle", cascade = CascadeType.ALL)
private TravelExtra travelExtra;
...
}
public class TravelExtra {
...
#OneToOne
#JoinColumn(name = "vehicleId")
private Vehicle vehicle;
...
}
You dont need to inflate your Vehicle class with reference to VehicleExtra information (id), since Vehicle is completely independent of VehicleExtra, only VehicleExtra needs to be aware of Vehicle.
answer here
I have two subclasses of MyObject: SubObject1 and SubObject2. Now i have one more Entity: OtherClass. There is a 1:1 relationship between OtherClass und SubObject1 and a 1:n relationship between OtherClass and SubObject2.
MyObject:
#Entity
#Table( name = "MYOBJECT" )
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn( name = "OBJ_TYP", discriminatorType = DiscriminatorType.STRING )
public abstract class MyObject{
#Id
#Column( name = "OBJ_ID")
private Long id;
...
}
SubObject1:
#Entity
#Table( name = "MYOBJECT" )
#DiscriminatorValue( value = "SUB1" )
public class SubObject1 extends MyObject{
#OneToOne(mappedBy="subObject1")
private OtherClass otherClass;
...
}
SubObject2:
#Entity
#Table( name = "MYOBJECT" )
#DiscriminatorValue( value = "SUB2" )
public class SubObject2 extends MyObject{
#OneToMany(mappedBy="subObject2")
private Set<OtherClass> otherClassList;
...
}
OtherClass:
#Entity
#Table( name = "OTHER")
public class OtherClass{
#OneToOne
#JoinColumn(name = "OTHER_OBJ_ID", referencedColumn = "OBJ_ID", nullable=true)
private SubObject1 subObject1;
#ManyToOne
#JoinColumn( name = "OTHER_OBJ_ID", referencedColumn = "OBJ_ID", nullable = true)
private SubObject2 subObject2;
}
Now, when i have a hql query like:
FROM OtherClass c LEFT JOIN FETCH c.subObject1 LEFT JOIN FETCH c.subObject2
i get:
java.lang.RuntimeException: org.hibernate.MappingException: Repeated column in mapping for entity: ...OtherClass column: OTHER_OBJ_ID (should be mapped with insert="false" update="false")
I know that the column "OTHER_OBJ_ID" is repeated. But why i can't do it like that? I thought that hibernate could find out which type of entity should be there because of the declaration for the discriminator value. I really don't want to add a column in the OTHER Table. Is there a possibility to do it without adding a new column and seperate obj1 and obj2?
Please help me!
You have no choice but to use distinct columns in your mapping or use a single OneToMany collection of MyObject and have your Java code or HQL get the subclasses you're looking for.