120

Can someone please explain to me @MapsId in hibernate? I'm having a hard time understanding it.

It would be great if one could explain it with an example and in what kind of use cases is it most applicable?

Krzysztof Atłasik
  • 21,985
  • 6
  • 54
  • 76
brainydexter
  • 19,826
  • 28
  • 77
  • 115

5 Answers5

63

Here is a nice explanation from Object DB.

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. The value element specifies the attribute within a composite key to which the relationship attribute corresponds. If the entity's primary key is of the same Java type as the primary key of the entity referenced by the relationship, the value attribute is not specified.

// parent entity has simple primary key

@Entity
public class Employee {
   @Id long empId;
   String name;
   ...
} 

// dependent entity uses EmbeddedId for composite key

@Embeddable
public class DependentId {
   String name;
   long empid;   // corresponds to primary key type of Employee
}

@Entity
public class Dependent {
   @EmbeddedId DependentId id;
    ...
   @MapsId("empid")  //  maps the empid attribute of embedded id
   @ManyToOne Employee emp;
}

Read the API Docs here.

ManuPK
  • 11,623
  • 10
  • 57
  • 76
  • 17
    But what's so nice about it? Even without @MapsId, one can use JoinColumn to same effect, can he not? And if so, this example does not really tell what's this annotation really good for. – Maksim Gumerov Mar 23 '18 at 14:06
  • 6
    Since both entities will be sharing the same primary key, the entity with a column designated by `@MapsId` will in the persistence layer (database) only have primary key column. The idea is to share the primary key between the two entities. – johanwannheden Jun 07 '18 at 05:32
  • 1
    What is an EmbeddedId primary key? How is it different from normal Primary Key? – sofs1 Jun 09 '18 at 20:40
  • "The value element specifies the attribute within a composite key to which the relationship attribute corresponds. " 1) What is meant by value element, please give an example? 2) What is a composite key? 3) What is the relationship attribute and give an example? – sofs1 Jun 09 '18 at 21:09
  • @MaksimGumerov it's efficient because you can fetch `Dependent` just by knowing identifier of `Employee`. – Emmanuel Osimosu Jun 28 '18 at 19:30
  • 1
    "just by knowing identifier of Employee" - how so? There's ManyToOne, thus many Dependent may be associated with the same Employee. – Maksim Gumerov Jun 29 '18 at 09:13
  • "The idea is to share the primary key between the two entities" - but can't we just use derived identifiers? I.e. write DependentId not like this, but like "... Employee emp"? No need to use MapsId then. And anyway, I am not saying the approach is inherently bad, I am saying that the cited explanation isn't particularly "nice" :) – Maksim Gumerov Jun 29 '18 at 09:26
  • I have similar scenario @ManuPK , This mapping worked out for me but how do we save Parent and chil entity in single go with JPA repository. For example I want to save Employee with its list of dependents just by employeeRepo.save(employee) method. Challenge I am facing is what do we set for employeeId attribute of dependent table even before saving .. please assist, have been struck from 3 days – user3678624 Aug 30 '23 at 14:35
35

I found this note also useful: @MapsId in hibernate annotation maps a column with another table's column.

It can be used also to share the same primary key between 2 tables.

Example:

@Entity
@Table(name = "TRANSACTION_CANCEL")
public class CancelledTransaction {
    @Id
    private Long id; // the value in this pk will be the same as the
                     // transaction line from transaction table to which 
                     // this cancelled transaction is related

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ID_TRANSACTION", nullable = false)
    @MapsId
    private Transaction transaction;
    ....
}

@Entity
@Table(name = "TRANSACTION")
@SequenceGenerator(name = "SQ_TRAN_ID", sequenceName = "SQ_TRAN_ID")
public class Transaction  {
    @Id
    @GeneratedValue(generator = "SQ_TRAN_ID", strategy = GenerationType.SEQUENCE)
    @Column(name = "ID_TRANSACTION", nullable = false)
    private Long id;
    ...
}
Hadi J
  • 16,989
  • 4
  • 36
  • 62
Tonsic
  • 890
  • 11
  • 15
  • 2
    Where do I put @MapsId in a bidirectional association? Should both classes have `@MapsId`. Does it even make a difference? – marcus Oct 02 '18 at 21:32
  • I think one table will be the "owner" of the original pk (the one with `@Id` and `@GeneratedValue` and `@Column`) and have a `@OneToOne` and `@JoinColumn` with the other table, and the other table will have the `@MapsId` . However this probably wouldn't work if you wanted to INSERT into the 'other table' first. – Tonsic Nov 19 '18 at 22:02
  • 2
    Nice article about this way of usage (using id from another table as id of other entity) is here https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ – Lubo Oct 23 '19 at 05:03
  • But if you want filter out transactions from canceled ones.. which is a common cause. How is this more efficient? I mean for SQL you just have to say NOT NULL for TRANSACTION.fk_cancelled_id but in this case, will be more operations. – M_F May 10 '20 at 08:04
  • In this specific case here, it is common to use a column like 'type' in the base class and table, where you'd identify if the transaction is of type 'cancel' or other. – Tonsic May 11 '20 at 16:03
  • Ah, found another useful blog post about this subject https://www.baeldung.com/hibernate-inheritance – Tonsic May 11 '20 at 16:06
12

IMHO, the best way to think about @MapsId is when you need to map a composite key in a n:m entity.

For instance, a customer can have one or more consultant and a consultant can have one or more customer:

enter image description here

And your entites would be something like this (pseudo Java code):

@Entity
public class Customer {
   @Id
   private Integer id;

   private String name;
}

@Entity
public class Consultant {
   @Id
   private Integer id;

   private String name;

   @OneToMany
   private List<CustomerByConsultant> customerByConsultants = new ArrayList<>();

   public void add(CustomerByConsultant cbc) {
      cbc.setConsultant(this);
      this.customerByConsultant.add(cbc);
   }
}

@Embeddable
public class CustomerByConsultantPk implements Serializable {
    
    private Integer customerId;

    private Integer consultantId;
}

@Entity
public class CustomerByConsultant{
   
   @EmbeddedId
   private CustomerByConsultantPk id = new CustomerByConsultantPk();
   
   @MapsId("customerId")
   @JoinColumn(insertable = false, updatable = false)
   private Customer customer;

   @MapsId("consultantId")
   @JoinColumn(insertable = false, updatable = false)
   private Consultant consultant;
}

Mapping this way, JPA automagically inserts Customer and Consultant ids in the EmbeddableId whenever you save a consultant. So you don't need to manually create the CustomerByConsultantPk.

wlfbck
  • 554
  • 8
  • 22
Jaumzera
  • 2,305
  • 1
  • 30
  • 44
8

As he explained Vladimir in his tutorial, The best way to map a @OneToOne relationship is to use @MapsId. This way, you don’t even need a bidirectional association since you can always fetch the Child entity by using the Parent entity identifier.

Apostolos
  • 10,033
  • 5
  • 24
  • 39
BERGUIGA Mohamed Amine
  • 6,094
  • 3
  • 40
  • 38
5

MapsId lets you use the same primary key between two different entities/tables. Note: when you use MapsId, the CASCADE.ALL flag becomes useless, and you will need to make sure that your entities are saved manually.

Janac Meena
  • 3,203
  • 35
  • 32