1

I got the @PrimaryKeyJoinColumn working before , now I'm trying with spring boot and I can't figure what I'm missing , it's very weird as it seems I have done everything right :

Person class :

    @Table(name = "PERSON")
@Entity

@Getter
@Setter
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;


    @Column(name = "NAME")
    private String name;

    @OneToOne(cascade = CascadeType.PERSIST)
    @PrimaryKeyJoinColumn
    private Department department;
}

Department class :

    @Table(name = "DEPARTMENT")
@Entity
@Getter
@Setter
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column(name = "NAME")
    private String name;


    @OneToOne(mappedBy = "department")
    private Person person;
}

The service class :

   @Transactional
    public void addPerson() {
        Person person = new Person();
        person.setName("person 1");
        Department department = new Department();
        department.setName("test");
        department.setPerson(person);
        person.setDepartment(department);
        personRepository.save(person);
    }

That's what I get in the DB : Person table :

ID   Name
13  person 1

Department table :

ID  Name
18  test

Desired output : both ID should be identical ( Person ID should be 18 not 13) any idea ?


Update: hibernate always tries to insert Person without an ID , so if I remove auto increment from Person ID and tries to insert the person with existing department I get :

Hibernate: insert into person (name) values (?)

Field 'id' doesn't have a default value

Update 2: it seems the @PrimaryKeyJoinColumn won't handle the ID generation to be as the department class, so I need to use generator. I wonder because the same annotation works in Inheritance joined without doing anything on the ID of subclass. So I'm expecting an answer explaining why ID generation works in Inheritance joined , while OneToOne needs a generator

Mohammad Karmi
  • 1,403
  • 3
  • 26
  • 42

1 Answers1

3

Although it is at least bad naming to make whole Department for just ONE Person (Department usually groups many people), I assume you really are looking for OneToOne relation with shared PRIMARY KEY.

The best solution (optimal memory, speed and maintenance) is to use @MapsId:

@Getter
@Setter
@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
    private Department department;
}

@Getter
@Setter
@Entity
public class Department {
    @Id // Note no generation
    private Long id;

    private String name;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name = "id", foreignKey = @ForeignKey(name = "department_belongs_to_person"))
    private Person person;
}

Here Department is owning relation (owning usually means that it would have column with FK in database, but here they just share primary key) and owns FK that binds it's PK to one generated by Person.

Note that relation is OneToOne, bidirectional, with shared PK as FK. This is considered best practice, but has one small drawback of having to write one getter. :)

Sources: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/

Also - I highly recommend spending few days reading posts on this site and even implement few of them before designing anything bigger than few tables. :)

EDIT

I might be wrong here (I am sure now - look comments), but to my knowledge keeping IDs equal is not something JPA specifies. What it can specify is basically:

"Hey, you guys (Person, Department) are brothers (OneToOne) and both know it (bidirectional with mappedBy="person" in Person entity). You will be older bro that will generate IDs (Person has @GeneratedValue), and you will be youger that should have same. You will use those fields (IDs, one generated, second not) to connect (@PrimaryKeyJoinColumn)."

What I am trying to say is that just because you say "this connects you", it doesn't mean those are synchronized - YOU have to ensure it.

Now as to how to ensure it - @MapsId is known to be best.

If you are looking for different approaches there is also manually setting ID to be same as other with #setDepartment(Department) where you would set Department's ID to be same as calling Person (but this only works if said Person already has an ID generated, which basically breaks the idea).

The other known to me is using foreign generator strategy.

Person:

@Id
@GeneratedValue
private long id;

@OneToOne(mappedBy="person")
private Department department;

Department:

@Id
@GeneratedValue(generator="gen")
@GenericGenerator(name="gen", strategy="foreign", parameters=@Parameter(name="property", value="person"))
private long id;

@OneToOne
@PrimaryKeyJoinColumn
private Person person;

Now - this uses hibernate-specific annotations and is not pure JPA.

Ernio
  • 948
  • 10
  • 25
  • Thank you very much. I will try it, eventhough Im aware of that solution. Im curious to know why @primarykeyjoincolumn not picking the id of department ? I wrote this example to try it on OneToOne , this is just sample. Can you please explain why its not working? – Mohammad Karmi Nov 30 '18 at 22:34
  • It really boils down to - can one exist without other, which one? One of them musn't have generated value and must have setter that will take PK of other. This then boils down to using MapsId because that forces everything I wrote before. I'd say PrimaryKeyJoinColumn is weaker link if we are really talking about true OneToOne. Unless you allow actual FKs (not shared PKs). – Ernio Nov 30 '18 at 22:47
  • @Erino MapsId works, but I'm still can't figure why the primarykeyjoincolumn doesn't handle ID generation. the same works in inheritance joined, can you explain ? – Mohammad Karmi Dec 01 '18 at 16:28
  • @MohammadKarmi Tried my best according to my knowledge (edited answer). My example was turned around (I think you wanted Dept to be parent?) but thats still sample code. :) – Ernio Dec 01 '18 at 18:23
  • thank you very much, the generator absolutely works , MapsId also handles the ID . but the primarykeyjoincolumn the only one which doesn't take care of ID. if there is no answer I will accept this, – Mohammad Karmi Dec 01 '18 at 18:28
  • @MohammadKarmi I recently stumbled upon something I was 90% sure when I made edit. Now I am 100% sure. Look at end of section (example 178 and comment after): http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#identifiers-derived-mapsid – Ernio Dec 07 '18 at 01:14