What is @MapsId used for in JPA/hibernate? – Part 1

@MapsId annotation.

If you ever wondered what this annotation is for , here is an in depth analysis of it:

When is this annotation used?

It is used if the primary key of a child table is the same as the primary key of a parent table

Why is this used?

By specifying the annotation you can automatically populate the primary key of a child table from the parent table

How is this used?

Let’s see this through an example.

Let’s consider two tables:

A musician table which stores musician details.

A musician contact table which stores the contact details of the musician.

Let us assume that the musician can have only contact , so the relationship between the two tables is One-to-One.

Let’s create the corresponding JPA/hibernate entities:

Musician.java for the musician table:

package com.example.mapsid;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class Musician {
	@Id
	@GeneratedValue
	private Long id;
	private String name;
	
	@OneToOne(mappedBy="musician",cascade=CascadeType.ALL)
	private MusicianContact contact ;
	
	
	public MusicianContact getContact() {
		return contact;
	}
	public void setContact(MusicianContact contact) {
		this.contact = contact;
		contact.setMusician(this);
	}
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

MusicianContact.java for the table which stores musician contact details:

package com.example.mapsid;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
@Entity
public class MusicianContact {
	@Id
	private Long id;
	private String contactDetails;
	@OneToOne
	private Musician musician;
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getContactDetails() {
		return contactDetails;
	}
	public void setContactDetails(String contactDetails) {
		this.contactDetails = contactDetails;
	}
	public Musician getMusician() {
		return musician;
	}
	public void setMusician(Musician musician) {
		this.musician = musician;
	}
	
	
}

I have just set a bi-directional One to One relationship between Musician and MusicianContact entities.

The One to One relationship code in Musician.java:

@OneToOne(mappedBy="musician",cascade=CascadeType.ALL)
	private MusicianContact contact ;

mappedBy keyword is used to map the field name in MusicianContact entity which represents the parent Musician entity

cascase=CascadeType.ALL is used so that if you save musician object after assigning a musician contact to the musician , the musician contact object automatically gets saved. You don’t have to save musician and musician contact objects separately.

Also notice the way Musician Contact object is added to Musician object:

	public void setContact(MusicianContact contact) {
		this.contact = contact;
		contact.setMusician(this);
	}

Since it is a bi-directional One to One relationship we are setting the objects both ways (assign contact to musician and assign musician to contact)

Now let’s look at the relationship in Musician Contact entity:

	@OneToOne
	private Musician musician;

A simple @OneToOne annotation.

Also notice the Id attribute:

	@Id
	private Long id;

It is not generated automatically , so you have to manually assign an id and save it.

Now let’s try to save a musician along with a contact:

	Musician musician = new Musician();
		musician.setName("Beethoven");
		
		
		MusicianContact contact = new MusicianContact();
		contact.setContactDetails("phone:83934");
		
		musician.setContact(contact);
		
		
		repo.save(musician);
		

The below error gets thrown:

This is because we didnot assign a value to the musician contact id.

We can generate this automatically by adding @GeneratedValue annotation to the id column like this :

@Id
@GeneratedValue
private Long id;

It will work then.

BUT,

what if we want the id of the Musician Contact entity to be the same as of the Musician.

In other words what if we want the primary key of the parent entity and the child entity to be the same.

That’s when @MapsId annotation comes to the rescue.

Adding this annotation will automatically pull the id from the parent and assign it to the child.

Let’s modify the relationship in MusicianContact entity class:

	@OneToOne
	@MapsId
	private Musician musician;

I have just added the @MapsId annotation.

Now if I try to save the Musician object populated as before , it works!:

I used H2 in memory database through spring boot and here is the record viewed through H2 console:

The record gets inserted. The primary key of Musician object (1 in the above case) is picked and assigned to Musician Contact object automatically.

BUT WAIT.

Why is the column MUSICIAN_ID created instead of ID.

Internally in Spring DATA(which I have used to test these), every field name in the java entity object is converted to a table column name by turning them into upper case letters and using underscore.

So contactDetails becomes CONTACT_DETAILS

id becomes ID

So since the MusicianContact entity has two fields (id and contactDetails) , we should have

two columns ID and CONTACT_DETAILS.

Instead a new column named MUSICIAN_ID is created automatically by hibernate.

Why?

This is because we have not specified what the foreign key column should be in MusicianContact entity (musician_contact table)

So hibernate automatically creates a foreign key column by joining the field name you mention in the one to one mapping in the child entity (musician in this case) and the primary key of the parent entity (id in this case) – musician_id

Once it creates this column it populates it with the primary key value of the parent entity .

Also this column is considered as the primary key of the child entity (since we are using @MapsId annotation)

Alright.

But what if we don’t want to create a new column to represent the foreign key .

Both the primary keys of the parent and the child are the same in our case.

We can use the primary key as the foreign key in the child entity.

How to do this in hibernate?

By using @JoinColumn annotation.

The column value you pass to this annotation is chosen as the foreign key column.

So let’s rewrite the one to one relationship in child entity (MusicianContact) as below:

	@OneToOne
	@MapsId
	@JoinColumn(name="id")
	private Musician musician;

Now we are telling hibernate that “id” is the column that should be considered as the foreign key column.

Now let’s save the parent and child entries:

It works as expected!

The column names are in sync with the field names which we created in hibernate entity class.

Also no new column is created to represent the foreign key.

That’s it!

In short , @MapsId is used to automatically populate the primary key of a child entity if it has the same primary key as of the parent entity.

In this post, We saw how @MapsId can be used in a One to One relationship when the primary keys of both the parent and child are the same.

There is one more use case for @MapsId annotation.

That is in OneToMany relationship.

That is explored in the next post:

MapsId in One To Many relationship


Posted

in

, ,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading