JPA @OneToOne with Shared ID -- Can I do this Better?
To map one-to-one association using shared primary keys use @PrimaryKeyJoinColumn
and @MapsId
annotation.
Relevant sections of the Hibernate Reference Documentation:
PrimaryKeyJoinColumn
The PrimaryKeyJoinColumn annotation does say that the primary key of
the entity is used as the foreign key value to the associated entity.
MapsId
The MapsId annotation ask Hibernate to copy the identifier from
another associated entity. In the Hibernate jargon, it is known as a
foreign generator but the JPA mapping reads better and is encouraged
Person.java
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "person_id")
private Long id;
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private VitalStats vitalStats;
}
VitalStats.java
@Entity
public class VitalStats
{
@Id @Column(name="vitalstats_id") Long id;
@MapsId
@OneToOne(mappedBy = "vitalStats")
@JoinColumn(name = "vitalstats_id") //same name as id @Column
private Person person;
private String stats;
}
Person Database Table
CREATE TABLE person (
person_id bigint(20) NOT NULL auto_increment,
name varchar(255) default NULL,
PRIMARY KEY (`person_id`)
)
VitalStats Database Table
CREATE TABLE vitalstats
(
vitalstats_id bigint(20) NOT NULL,
stats varchar(255) default NULL,
PRIMARY KEY (`vitalstats_id`)
)
@OneToOne Mapping with Hibernate shared primary key User - Account
You can achieve this more simply in JPA 2+ as follows:
public class User{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(unique=true, nullable=false)
private int id;
@OneToOne(cascade=CascadeType.PERSIST, mappedBy = "user")
private Account account;
}
public class Account{
@Id
@OneToOne
@JoinColumn(name = "user_id")
private User user;
}
See:
https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_and_ManyToOne_Relationships
JPA @Id on @OneToOne Entity with a Composite Key
This is what JPA calls a "derived identity". You might try something like this:
ReportDetails:
@Entity
public class ReportDetails implements Serializable {
// all attributes map by the relationship: AttributeOverride is not allowed
@EmbeddedId
private MachineLine.Id id;
@MapsId
@JoinColumns({
@JoinColumn(name="machineId", referencedColumnName="machineId"),
@JoinColumn(name="machineLineIndex", referencedColumnName="index")
})
@OneToOne
private MachineLine machineLine;
// ...
}
MachineLine:
@Entity
public class MachineLine {
@EmbeddedId
private Id id;
@MapsId("machineId") // maps machineId attribute of embedded id
@ManyToOne
private Machine machine;
// ...
@Embeddable
public static class Id implements Serializable {
private long machineId; // corresponds to PK type of Machine
private long index;
// ...
}
}
Machine:
@Entity
public class Machine {
@Id
private long id;
@OneToMany(mappedBy = "machine")
private List<MachineLine> lines;
// ...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
OneToOne relationship with shared primary key generates n+1 selects; any workaround?
Imagine 2 tables in a relational database, e.g. Person and Billing. There is a (non-mandatory) OneToOne association defined between these entities,
Lazy fetching is conceptually not possible for non-mandatory OneToOne by default, Hibernate has to hit the database to know if the association is null
or not. More details from this old wiki page:
Some explanations on lazy loading (one-to-one)
[...]
Now consider our class B has
one-to-one association to Cclass B {
private C cee;
public C getCee() {
return cee;
}
public void setCee(C cee) {
this.cee = cee;
}
}
class C {
// Not important really
}
Right after loading B, you may call
getCee()
to obtain C. But look,
getCee()
is a method of YOUR class
and Hibernate has no control over it.
Hibernate does not know when someone
is going to callgetCee()
. That
means Hibernate must put an
appropriate value into "cee
"
property at the moment it loads B from
database. If proxy is enabled for
C
, Hibernate can put a C-proxy
object which is not loaded yet, but
will be loaded when someone uses it.
This gives lazy loading for
one-to-one
.But now imagine your
B
object may or
may not have associatedC
(constrained="false"
). What should
getCee()
return when specificB
does not haveC
? Null. But remember,
Hibernate must set correct value of
"cee" at the moment it setB
(because it does no know when someone
will callgetCee()
). Proxy does not
help here because proxy itself in
already non-null object.So the resume: if your B->C mapping
is mandatory (constrained=true
),
Hibernate will use proxy for C
resulting in lazy initialization. But
if you allow B without C, Hibernate
just HAS TO check presence of C at the
moment it loads B. But a SELECT to
check presence is just inefficient
because the same SELECT may not just
check presence, but load entire
object. So lazy loading goes away.
So, not possible... by default.
Is there a workaround for this potential performance disaster (other than not using a shared primary key at all)? Thank you for all your ideas.
The problem is not the shared primary key, with or without shared primary key, you'll get it, the problem is the nullable OneToOne.
First option: use bytecode instrumentation (see references to the documentation below) and no-proxy fetching:
@OneToOne( fetch = FetchType.LAZY )
@org.hibernate.annotations.LazyToOne(org.hibernate.annotations.LazyToOneOption.NO_PROXY)
Second option: Use a fake ManyToOne(fetch=FetchType.LAZY)
. That's probably the most simple solution (and to my knowledge, the recommended one). But I didn't test this with a shared PK though.
Third option: Eager load the Billing using a join fetch
.
Related question
- Making a OneToOne-relation lazy
References
- Hibernate Reference Guide
- 19.1.3. Single-ended association proxies
- 19.1.7. Using lazy property fetching
- Old Hibernate FAQ
- How do I set up a 1-to-1 relationship as lazy?
- Hibernate Wiki
- Some explanations on lazy loading (one-to-one)
Unidirectional one-to-one relationship with shared primary key and @OneToOne on child side
That can be done with the help of MapsId:
Designates a ManyToOne or OneToOne relationship attribute that
provides the mapping for an EmbeddedId primary key, an attribute
within an EmbeddedId primary key, or a simple primary key of the
parent entity.
In this case appliance
should be annotated with MapsId
:
@OneToOne
@JoinColumn(name="applianceId")
@MapsId
private Appliance appliance;
Also nullable=false
was dropped, because by definition primary key is not nullable.
One to One unidirectional mapping with same primary key in hibernate
You can change your mapping with @mapsId
as suggested by Hadi J to have a single pk/fk column in RowCount
@Entity
@Table(name = "row_count")
public class RowCount implements Serializable {
@OneToOne
@JoinColumn(name = "file_id")
@MapsId
private MetaFile file;
@Id
@Column(name="file_id")
private int file_id;
private int rows;
public RowCount(MetaFile file, int rows) {
this.file = file;
this.rows = rows;
}
// getters, setters...
}
I would use a bidirectional relationship to simplify saving:
@Entity
@Table(name = "file")
public class MetaFile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "file_id")
private int fileId;
// getters, setters, constructors...
@OneToOne(cascade = {CascadeType.ALL}, mappedBy = "file")
private RowCount rowCount;
}
This way you can just set the relationship and save
RowCount rowCount = new RowCount(metaFile, getNumberOfRows(file));
metaFile.setRowCount(rowCount);
metaFile = fileRepository.save(metaFile);
Change OneToOne link into another entity with another id
packingRepository.findById(id)
.ifPresent(packing -> packing.setPackingDictionary(packingDictionaryRepository.getOne(dictionaryId))
Do self referencing entities need @OneToOne JPA annotations or not?
No you always will need the annotation. If it is optional:
@OneToOne(optional = true)
private DepositAccountTransaction destinationTransaction;
I wouldn't declare any property as public btw. You may want to use projectlombok to generate getters and setters.
How to map one to one relation with no foreign key in JPA?
What you are looking for is bidirectional one to one mapping. Now the mapping is done only one way. In bidirectional mapping, you need to put File
reference in FileContent
and vice versa. Check the following code snippet -
File:
@Entity
@Table(name="FILE", schema="COMMON")
public class File {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ID")
private Long id;
@Column(name = "FILE_NAME")
private String fileName;
@OneToOne(mappedBy = "file", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private FileContent details;
//constructors getters and setters
}
FileContent:
@Entity
@Table(name="FILE_CONTENT", schema="COMMON")
public class FileContent{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ID")
private Long id;
@Column(name = "FILE_NAME")
private String fileName;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "FILE_ID")
private File file;
//constructors getters and setters
}
Notes:
- The above code snippet will create the table named
FILE_CONTENT
with a column namedFILE_ID
. FILE_ID
is the foreign key referencing the primary key ofFILE.ID
.Here in the association, the
File
entity is the parent, while theFileContent
is the child. Because the foreign key is located in theFILE_CONTENT
table.Now from your code, you can access both entities from any side like -
file.getFileContent()
orfileContent.getFile()
Related Topics
In Java, How to Call a Base Class's Method from the Overriding Method in a Derived Class
How to Hide Cursor in a Swing Application
Ssl Peer Shut Down Incorrectly in Java
Invalid Signature File Digest for Manifest Main Attributes Exception While Trying to Run Jar File
Cannot Instantiate the Type List<Product>
Custom Objectmapper with Jersey 2.2 and Jackson 2.1
How to Convert Timestamp to Date in Java
Advantage of Set and Get Methods VS Public Variable
Creating an X509 Certificate in Java Without Bouncycastle
Java Web Service Client Basic Authentication
Can a Private Method in Super Class Be Overridden in the Sub-Class
Superclass Reference Not Able to Call Subclass Method in Java
Retrofit 2.0 How to Get Deserialised Error Response.Body
Why Are New Java.Util.Arrays Methods in Java 8 Not Overloaded for All the Primitive Types
Totally Confused with Java.Exe
What Is the Best Library for Java to Grid/Cluster-Enable Your Application