Search code examples
hibernatespring-data-jpaspring-datahibernate-mapping

Using @PrimaryKeyJoinColumn annotation in spring data jpa


I try to use the @PrimaryKeyJoinColumn annotation. When doing this, I get an error - attempted to assign id from null one-to-one property.

The user is saved, but the address is not saved. I want to create a common primary key for the User and Address table. I found an example here.

Please, tell me what I'm doing wrong, why doesn't this example work for me?

https://github.com/mytestPercon/TestHiber

User.java

@Entity
@Table(name = "user", schema = "TestKeyJoin")
public class User implements Serializable {

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

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

   @OneToOne(mappedBy = "user")
   @PrimaryKeyJoinColumn
   private Address activated;

   // Getter and Setter ...
}

Address.java

@Entity
@Table(name = "Address", schema = "TestKeyJoin")
public class Address implements Serializable {

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

   @Basic
   @Column(name = "city")
   private String city;

   @OneToOne
   @MapsId
   @JoinColumn(name = "id")
   private User user;

   // Getter and Setter ...

}

SaveController.java

@Controller
public class SaveController {

   @Autowired
   ServiceJpa serviceJpa;

   @GetMapping(value = "/saveUser")
   public String getJpa () {
      User user = new User();
      user.setId(1L);
      user.setName("Michael Joseph Jackson");
      serviceJpa.saveUser(user);

      Address address = new Address();
      address.setId(1L);
      address.setCity("Los Angeles");
      serviceJpa.saveActivated(address);

      return "/saveUser";
   }
}

Solution

  • Try to correct your mapping in the following way:

    @Entity
    @Table(name = "user", schema = "TestKeyJoin")
    public class User implements Serializable {
    
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       @Column(name = "id")
       private Long id;
    
       @Column(name = "name")
       private String name;
    
       @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
       private Address address;
    
       // ...
    }
    
    @Entity
    @Table(name = "Address", schema = "TestKeyJoin")
    public class Address implements Serializable {
    
       @Id
       private Long id;
    
       @Column(name = "city")
       private String city;
    
       /* You can use @PrimaryKeyJoinColumn instead of the @MapsId here.
          See the Example 153. Derived identifier @PrimaryKeyJoinColumn
          from the hibernate documentation.
       */
       @OneToOne
       @MapsId
       @JoinColumn(name = "id")
       private User user;
    
       // ...
    }
    

    and then save user in the following way:

    @GetMapping(value = "/saveUser")
    public String getJpa () {
    
       User user = new User();
       user.setName("Michael Joseph Jackson");
    
       Address address = new Address();
       address.setCity("Los Angeles");
    
       // make both sides of the bidirectional @OneToOne in-sync
       user.setAddress(address);
       address.setUser(user);
    
       serviceJpa.saveUser(user);
    
       return "/saveUser";
    }
    

    And several notes:

    1. Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.

    2. You should not save your entities separately, you just should use a proper cascading on your association.

    3. You should not set value for the generated identifier.

    4. When you use @MapsId on the Address.user, it actually means that the Address entity will borrow the identifier from the one-to-one association. So, you should not use @GeneratedValue annotation for the Address.id. (See this)