As per Hibernate documentation, the explanation for #MapsId annotation is given as :
In the embedded id object, the association is represented as the
identifier of the associated entity. But you can link its value to a
regular association in the entity via the #MapsId annotation. The
#MapsId value correspond to the property name of the embedded id
object containing the associated entity's identifier. In the database,
it means that the Customer.user and the CustomerId.userId properties
share the same underlying column (user_fk in this case).
#Entity
class Customer {
#EmbeddedId CustomerId id;
boolean preferredCustomer;
#MapsId("userId")
#JoinColumns({
#JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
#JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
#OneToOne User user;
}
#Embeddable
class CustomerId implements Serializable {
UserId userId;
String customerNumber;
//implements equals and hashCode
}
#Entity
class User {
#EmbeddedId UserId id;
Integer age;
}
#Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
Also it says:
While not supported in JPA, Hibernate lets you place your association
directly in the embedded id component (instead of having to use the
#MapsId annotation).
#Entity
class Customer {
#EmbeddedId CustomerId id;
boolean preferredCustomer;
}
#Embeddable
class CustomerId implements Serializable {
#OneToOne
#JoinColumns({
#JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
#JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
String customerNumber;
//implements equals and hashCode
}
#Entity
class User {
#EmbeddedId UserId id;
Integer age;
}
#Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
//implements equals and hashCode
}
I tried to generate the tables using Hibernate itself (hbm2ddl.auto=create) to understand how the #MapsId annotation is used. Here are my observations:
If my entity declaration for Customer and User are like this:
#Entity
#Table(name="TBL_CUSTOMER")
public class Customer {
#EmbeddedId CustomerId id;
boolean preferredCustomer;
#MapsId("userId")
#JoinColumns({
#JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
#JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
#OneToOne User user;
}
#Entity
#Table(name="TBL_USER")
class User {
#EmbeddedId UserId id;
Integer age;
}
Then the DDL statements generated by Hibernate says:
Hibernate: create table TBL_CUSTOMER (customerNumber varchar2(255 char) not null, preferredCustomer number(1,0) not null, userfirstname_fk varchar2(255 char) not null, userlastname_fk varchar2(255 char) not null, primary key (customerNumber, userfirstname_fk, userlastname_fk))
Hibernate: create table TBL_USER (firstName varchar2(255 char) not null, lastName varchar2(255 char) not null, age number(10,0), primary key (firstName, lastName))
Hibernate: alter table TBL_CUSTOMER add constraint UK_chvh5mukc81xk9t6fis3skab unique (userfirstname_fk, userlastname_fk)
Hibernate: alter table TBL_CUSTOMER add constraint FK_chvh5mukc81xk9t6fis3skab foreign key (userfirstname_fk, userlastname_fk) references TBL_USER
Now if I change my Customer entity to:
#Entity
#Table(name="TBL_CUSTOMER")
public class Customer {
#EmbeddedId CustomerId id;
boolean preferredCustomer;
#OneToOne User user;
}
Then the DDL statements are:
Hibernate: create table TBL_CUSTOMER (customerNumber varchar2(255 char) not null, firstName varchar2(255 char), lastName varchar2(255 char), preferredCustomer number(1,0) not null, user_firstName varchar2(255 char), user_lastName varchar2(255 char), primary key (customerNumber, firstName, lastName))
Hibernate: create table TBL_USER (firstName varchar2(255 char) not null, lastName varchar2(255 char) not null, age number(10,0), primary key (firstName, lastName))
Hibernate: alter table TBL_CUSTOMER add constraint FK_et3bgekef237d4kov7b9oqt85 foreign key (user_firstName, user_lastName) references TBL_USER
In this case, I see 2 extra columns (firstname & lastname) for TBL_CUSTOMER if I remove #MapsId and #JoinColumn annotations. Also an extra alter command is not there in this case.
I am new to Hibernate, so I am finding it difficulty in understanding the explanation given in Hibernate docs, what is the purpose of #MapsId, when we have to use it and how it affects the underlying database schema.
Also I have gone through this SO post - can someone please explain me #MapsId in hibernate? but I am not able to get clear information on this annotation.
#MapsId is used to tell hibernate (or any JPA provider, actually) to use the same id of another entity with a 1-to-1 relationship with this one.
This will avoid to use an extra column to store the reference between the two entitities, while at the same being able to have a bidirectional relationship.
Related
I'm new to JPA and trying to understand if there's a way to make an Entity where one column is coming from another table that is linked by a foreign key. For example, consider the following tables:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `jobs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11),
PRIMARY KEY (`id`),
CONSTRAINT `fk_jobs_users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
);
Now I want to make an Entity for the "jobs" table that will include the user.email. I know I can do something like
#Entity
#Table(name = "jobs")
public class JobEntity {
#Id
#Column(name = "id")
private Long id;
#Column(name = "user_id")
private Long userId;
#Formula("(select user.email FROM user WHERE user.id = user_id)")
private String userEmail;
But I feel there's a way I can better leverage the foreign key relationship, but I'm not sure how. I was looking into #JoinColumn but was not seeing the result I wanted since the foreign key is a different column in my Entity. Is there a better way rather than using #Forula to do this?
I don't really understand this. I'm sure #JoinColumn can accomplish the behavior you're looking for.
I was looking into #JoinColumn but was not seeing the result I wanted since the foreign key is a different column in my Entity
Example:
#Entity
#Table(name = "jobs")
public class KronosFileEntity {
#Id
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumn = "id")
private User user;
}
Then you can access the email like job.getUser().getEmail()
Or add a convenience method if that helps
public String getUserEmail() {
return user.getEmail();
}
Then
job.getUserEmail()
I am studying JPA and Hibernate to build a Spring Boot webapp, and there's something that bugs me.
It is related to impedance mismatch in One To One relationships.
Let's say I have two domain entities, A and B, that have a one to one relationship.
This is what I would like to have:
in the Java Classes, I would like to have A hold a reference to B;
in the Database, I would like to have the table for "b" objects have a column with the foreign key to "a" keys.
Is there a way to do this with JPA and Hibernate in Spring Boot?
I report here the problem with real-world classes and code.
In my domain I have basically people and signatures.
Therefore, in my Java Code, I have Person #Entity and a Signature #Entity.
In Java, it makes sense to have the Person object own a Signature object.
So, here is the Person class:
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="first_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String firstName;
#Column(name="last_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String lastName;
// ??? which annotations?
private Signature signature;
// I omit constructors, getters and setters for brevity
And this is the Signature class:
#Entity
#Table(name = "signatures")
public class Signature {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="name")
#NotNull
#NotBlank
private String name;
#Column(name="type")
#NotNull
private String type;
#Column(name="image")
#NotNull
#NotEmpty
#Lob
#Type(type="org.hibernate.type.MaterializedBlobType")
private byte[] image;
// I omit constructors, getters and setters for brevity
As you can see, Ids should be generated automatically, and I would like my Person class to have a reference to its Signature, and not vice-versa.
On the contrary, this is the DB schema I'd like to use:
CREATE SCHEMA signatures;
CREATE TABLE signatures.people (
id BIGSERIAL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE signatures.signatures (
id BIGSERIAL,
type VARCHAR[16] NOT NULL,
name VARCHAR[100] NOT NULL,
image BYTEA NOT NULL,
person BIGINT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_signature_people FOREIGN KEY (person) REFERENCES signatures.people (id) ON DELETE CASCADE ON UPDATE CASCADE
);
As you can see here, I would like the Signatures table to have a foreign key to the People table, and not vice-versa.
Is this possible?
The #OneToOne mapping is a bit of an odd-ball. When the relationship is bi-directional, you can decide the owning side, but in a unidirectional relationship the declaring entity will always be the one with the foreign key.
One option is to make the relationship bi-directional, but hide the other direction in code.
The other way is to use a #OneToMany mapping, which will create the foreign key in the "many" table. This is also consistent with the database schema, as multiple child table rows could then link to the same parent row at least theoretically, especially if there's not a constraint to make sure they're unique.
I'm new to Hibernate and I'm trying to establish a OneToMany/ManyToOne bidirectional relationship between Person and Vehicle classes. In my example a Person can have many Vehicles and a Vehicle belongs to only one Person. I need a join table: PERSON_VEHICLE with PERSON_ID and VEHICLE_ID as columns and a PERSON_ID column in VEHICLE table. Here's my class design:
Person class:
#Entity
public class Person {
#Id
#GeneratedValue
#Column(name = "PERSON_ID")
private int id;
private String name;
#OneToMany(cascade=CascadeType.ALL, mappedBy="person")
private Collection<Vehicle> vehicleList = new ArrayList<>();
Vehicle class:
#Entity
public class Vehicle {
#Id
#GeneratedValue
#Column(name = "VEHICLE_ID")
private int id;
private String name;
#ManyToOne
#JoinColumn(name="PERSON_ID")
#JoinTable(name="PERSON_VEHICLE", joinColumns=#JoinColumn(name="VEHICLE_ID"),
inverseJoinColumns=#JoinColumn(name="PERSON_ID"))
private Person person;
Here are the DDLs generated by Hibernate.
create table Person (
PERSON_ID integer not null auto_increment,
name varchar(255),
primary key (PERSON_ID)
)
create table Vehicle (
VEHICLE_ID integer not null auto_increment,
name varchar(255),
primary key (VEHICLE_ID)
)
create table PERSON_VEHICLE (
PERSON_ID integer,
VEHICLE_ID integer not null,
primary key (VEHICLE_ID)
)
alter table PERSON_VEHICLE
add index FK_h3d046x5uvbo53p8ms41hwqx (PERSON_ID),
add constraint FK_h3d046x5uvbo53p8ms41hwqx
foreign key (PERSON_ID)
references Person (PERSON_ID)
alter table PERSON_VEHICLE
add index FK_mtm2mn29hel3lbpl6i526w40v (VEHICLE_ID),
add constraint FK_mtm2mn29hel3lbpl6i526w40v
foreign key (VEHICLE_ID)
references Vehicle (VEHICLE_ID)
The VEHICLE table doesn't have PERSON_ID column. Something is wrong but I cannot find what the problem is.
Join table is not required for oneToMany relationship. Only two tables Person and Vehicle is sufficient for this mapping. For detailed example, see This Example
I am trying to create OneToOne relation between a Person and Auth table. The problem is when the DB table "Auth" is generated, I'm not seeing the foreign key in the AUTH table that should reference Person. The object is to have the Auth table use the same Primary Key of the Person Table.
#MappedSuperclass
public abstract class DomainBase {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Version
#Column(name="OPLOCK")
private Integer version;
}
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
---------------------------------
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Person person;
}
My Database scripts after hibernate DB generation.
CREATE TABLE auth
(
person_id integer NOT NULL,
activate boolean,
activationid character varying(255),
last_login_attempt_date timestamp without time zone,
last_login_attempt_timezone character varying(255),
last_login_date timestamp without time zone,
last_login_timezone character varying(255),
nonlocked boolean,
num_login_attempts integer,
CONSTRAINT auth_pkey PRIMARY KEY (person_id),
CONSTRAINT uk_d68auh3xsosyrjw3vmwseawvt UNIQUE (activationid)
)
WITH (
OIDS=FALSE
);
ALTER TABLE auth
OWNER TO postgres;
It seems that the problem is you declare twice the #OneToOne annotation between "person" table and "auth" table, without specify the relation between them. Take a look at the hibernate documentation, at the point 2.2.5.1, there is some examples about using one-to-one association.
For me, the best way is to set up the association in one table, the one that declare the foreing key column, and to use the mappedBy parameter in the other object. In your code, this will be :
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
#OneToOne(mappedBy = "auth")
private Person person;
....
}
This is the second example in the hibernate documentation, introduce just after the sentence "In the following example, the associated entities are linked through an explicit foreign key column". I tested this code, and the "auth_id" column appeared.
I have defined a collection in Hibernate like this:
...
public class Item {
...
#ElementCollection
List<Object> relatedObjects;
}
It creates a mapping table with colums item_id and object_id.
The problem is that object_id seems to be unique. In other words I can not have two different items being related to the same object. But that is what I want.
I would like the combination of item_id and object_id to be unique. How do I do that?
That's not what I'm experiencing. For the following entity:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue
private Integer id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private Gender gender;
#ElementCollection
private Set<String> nicknames = new HashSet<String>();
private String dept;
// getters, setters
}
The following tables get created:
create table Person (id integer generated by default as identity, dept varchar(255), firstName varchar(255), gender varchar(255), lastName varchar(255), primary key (id))
create table Person_nicknames (Person_id integer not null, nicknames varchar(255))
alter table Person_nicknames add constraint FK24F0D97B19ACB65E foreign key (Person_id) references Person
There is no unique constraint. But I can't say more without seeing your "Object" class (it's an embeddable class, right?).
PS: ElementCollection can't be a ManyToMany, this is more a OneToMany.