What is @MapsId used for in JPA/Hibernate? – Part II

In the previous post , we saw how and why to use @MapsId in a One to One relationship.

In this post , lets see how it comes handy in a One to Many relationship.

Let’s take the same Musician entity as the parent object.

Let’s take an Album entity as the child object.

A Musician can have multiple Albums.

So they share a One to Many relationship.

Now let’s say the Album has a composite key.

One of the composite keys is the same as the primary key (“id”) of the parent entity (Musician).

And the other is the album name.

And,

Let’s say you want the primary key of the parent entity to be automatically assigned to the id field in the composite key of the child entity.

That’s when @MapsId is useful in a One To Many relationship.

Let’s explore it with an example.

Here is the Musician.java entity again this time with a One to Many relationship with Album entities:

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.OneToMany;
import javax.persistence.OneToOne;

@Entity
public class Musician {

	@Id
	@GeneratedValue
	private Long id;

	private String name;

	@OneToMany(mappedBy = "musician", cascade = CascadeType.ALL)
	List<Album> albums = new ArrayList<Album>();
	
	
	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;
	}

	public List<Album> getAlbums() {
		return albums;
	}

	public void setAlbums(List<Album> albums) {
		this.albums = albums;
	}

	public void addAlbum(Album album) {

		this.albums.add(album);
		album.setMusician(this);
	}


}

In the above code , notice the line :


	@OneToMany(mappedBy = "musician", cascade = CascadeType.ALL)
	List<Album> albums = new ArrayList<Album>();

This describes the one to many relationship between Musician and Album.

Again ,

the mappedBy keyword is used to denote the musician reference in the child entity Album

cascade = CascadeType.ALL makes sure that if you save the musician entity all the albums associated with it gets saved in database as well. You don’t have to manually save the album objects.

Also notice the utility method:

	public void addAlbum(Album album) {

		this.albums.add(album);
		album.setMusician(this);
	}

this helps you to add an album to a musician entity and also assign the musician to the album to maintain the bi-directional One to Many mapping.

Advertisements

Now let’s have a look at the Album entity:

package com.example.mapsid;

import java.io.Serializable;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;

@Entity
public class Album {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@EmbeddedId
	private AlbumId id;
	
	private String releaseDate;
	
	@ManyToOne
	@MapsId("musicianId")
	@JoinColumn(name="id")
	private Musician musician;

	public AlbumId getId() {
		return id;
	}

	public void setId(AlbumId id) {
		this.id = id;
	}

	public String getReleaseDate() {
		return releaseDate;
	}

	public void setReleaseDate(String releaseDate) {
		this.releaseDate = releaseDate;
	}

	public Musician getMusician() {
		return musician;
	}

	public void setMusician(Musician musician) {
		this.musician = musician;
	}



	
}

The Album entity has a composite key which is represented by @EmbeddedId annotation.

Here is the composite key class:

package com.example.mapsid;

import java.io.Serializable;

import javax.persistence.Embeddable;

@Embeddable
public class AlbumId implements Serializable{

	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private Long musicianId;
	
	private String name;

	public Long getMusicianId() {
		return musicianId;
	}

	public void setMusicianId(Long musicianId) {
		this.musicianId= musicianId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	
}

The class is annotated with @Embeddable to indicate that this is used to represent a composite key inside an entity class.

Notice the musicianId in the composite key class.

We want to populate this id with the id of the parent table(Musician).

And so the corresponding entry for the One to Many mapping in the child entity Album is as follows:


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



This is almost similar to what we saw in the previous post in One to One mapping except that the relationship here is ManyToOne denoted by @ManyToOne annotation and you pass a parameter in the @MapsId annotation.

The parameter passed here is “musicianId” which is one of the composite keys.

So musicianId will be populated with the primary key of the parent table Musician.

Advertisements

And also you need to specify the foreign column (actual database column name and not the entity field name) through @JoinColumn annotation.

Since the id of the Album entity is the same as that of the Musician entity we will use the same column name “id” and use it in @JoinColumn annotation.

This will be one of the keys in the composite key.

Now let’s try to save a record:

       Musician musician = new Musician();
		musician.setName("Beethoven");

		Album album = new Album();
		AlbumId id = new AlbumId();
		id.setName("The Spring");
		album.setId(id);
		album.setReleaseDate("20 Jan 2021");

		musician.addAlbum(album);
		
				
		repo.save(musician);
		
	

You can notice in the above code that the “musicianId” , one of the composite keys is not populated while populating the composite keys of the entity Album. Only the “name” field is populated.

The musicianId field will be automatically populated.

Here are the results:

The ID column in the above table is populated with the “ID” value from musician table.

This was made possible through @MapsId annotation.

Also if we had not specified the @JoinColumn(“id”) annotation , instead of “ID” column we would have got “MUSICIAN_ID” column which is the default behavior of hibernate.

That’s it!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s