Hibernate @ManyToOne Referenced Class using @NaturalId instead of @Id
Asked Answered
C

1

6

I have some Hibernate/JPA annotations (still don't know the difference to be honest) that are allowing me to create an association class. This class is combining two items that are related into one object. I originally was using @JoinTable but realized I needed a lot more meta data with the associations, so had to convert the code over into another object type.

For now I am using @Id to mark the ID column for my objects, and using @NaturalId (mutable = false) for a String uuid.

My association class is using @ManyToOne and creates the table just fine, but when I look into it the table is using the @Id field as the mapping column. I would prefer to have this association class use the @NaturalId uuid for ease of transferring relationship/associations across to other systems.

How can I get the relationship to use the correct identifier?

For reference, my DB's and Java code look like this:

AssociationsTable
----------------------------------------------
| ID | META DATA | ID ASSOC. 1 | ID ASSOC. 2 |
----------------------------------------------
| 1  |  stuff    |    1        |     2       |
----------------------------------------------

Objects
------------------------------
| ID | META DATA | UUID      | 
------------------------------
| 1  |  stuff    |  FOO-123  |
------------------------------
| 2  |  stuff    |  BAR-456  |
------------------------------

Association Class{
  ObjA main;
  ObjA sub;

  @ManyToOne
  getMain()

  @ManyToOne
  getSub()
}

ObjA Class{
  Long id;
  @Id
  @GeneratedValue(generator="increment")
  @GenericGenerator(name="increment", strategy = "increment")
  @XmlElement
  @Column(name = "ID", unique = true, nullable = false)
  getId()

  String uuid;    
  @NaturalId (mutable = false)
  @GeneratedValue(generator = "uuid")
  @GenericGenerator(name = "uuid", strategy = "uuid2")
  @Column(name = "uuid", unique = true)
  getUUID()
}
Chesterchesterfield answered 14/8, 2015 at 15:9 Comment(1)
I aware of your previous question. So I decided to clarify: why are you worried about linking tables? I mean entities in database level can be linked with @Id, then they can be serialized with reset key values, then transmitted to other instance, deserialized with all relations at java-bean level, then flushed in new database with new values for @Id properties... Primary keys for entities will be different but you will still get them by @NaturalId field. Did I miss something?..Modla
B
5

You can use the referencedColumnName attribute of @JoinColumn, as mentioned here. So for your example, the mappings should be as follows:

ObjA.java:

package hello;

import org.hibernate.annotations.NaturalId;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class ObjA {
    Long id;
    String uuid;

    public ObjA() {
    }

    public ObjA(String uuid) {
        this.uuid = uuid;
    }

    @Id
    @GeneratedValue
    @Column(name = "obj_a_id", unique = true, nullable = false)
    Long getId() {
        return id;
    }

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

    @NaturalId(mutable = false)
    @Column(name = "uuid", unique = true, nullable = false)
    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }
}

Association.java:

package hello;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class Association {
    Long id;
    ObjA main;
    ObjA sub;

    public Association() {
    }

    public Association(ObjA main, ObjA sub) {
        this.main = main;
        this.sub = sub;
    }

    @Id
    @GeneratedValue
    @Column(name = "association_id", unique = true, nullable = false)
    Long getId() {
        return id;
    }

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

    @ManyToOne
    @JoinColumn(name = "main_uuid", referencedColumnName = "uuid", nullable = false)
    public ObjA getMain() {
        return main;
    }

    public void setMain(ObjA main) {
        this.main = main;
    }

    @ManyToOne
    @JoinColumn(name = "sub_uuid", referencedColumnName = "uuid", nullable = false)
    public ObjA getSub() {
        return sub;
    }

    public void setSub(ObjA sub) {
        this.sub = sub;
    }
}

These mappings were successfully tested to store values from the uuid column of the obja table in the main_uuid and sub_uuid columns of the association table. This was tested using org.hibernate.common:hibernate-commons-annotations:4.0.5.Final, org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final, org.hibernate:hibernate-core:4.3.10.Final, and org.hibernate:hibernate-entitymanager:4.3.10.Final against PostgreSQL 9.4.4.

Bradstreet answered 17/8, 2015 at 16:57 Comment(2)
I tried this as well as using referencedTable in addition, no luck.Chesterchesterfield
@Chesterchesterfield I just tried using referencedColumnName on my local machine and it worked for me. I updated my answer with the exact mappings I used. If you're not seeing the UUIDs in your association table, make sure that you're pointing at the right database and that you're also regenerating it.Bradstreet

© 2022 - 2024 — McMap. All rights reserved.