arminas
arminas

Reputation: 421

Cascading entity save with composite key throws NullPointerException

I have this relationship structure in database, that I cannot change:

------------------------
| Person               | 
------------------------
 id : pk              
------------------------

------------------------
| PersonAddress        | 
------------------------
 person_id: pk, fk    
 address_id: pk, fk
 address_type 
------------------------

------------------------
| Address              | 
------------------------
 id: pk    
------------------------

I am trying to configure these relations with JPA and Hibernate annotations. One of the ways I've tried to do was this one:

@Entity
class Person {

  @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinColumn(name = "person_id")
  private List<PersonAddress> addresses;
}

@Entity
class PersonAddress {

  @ManyToOne
  @JoinColumn(nullable = false)
  private AddressType type;

  @EmbeddedId
  private PersonAddressId id;
}


@Embeddable
class PersonAddressId implements Serializable {

  @ManyToOne
  @JoinColumn(name = "person_id")
  private Person person;

  @ManyToOne(cascade = CascadeType.ALL)
  @MapsId("id")
  @JoinColumn(name = "address_id", nullable = false)
  private Address address;

}

@Entity
public class Address {

  @Id
  @GeneratedValue
  @Column(name = "address_id")
  private Long id;
}

When i do repository.save(person). I expect that changes would cascade down to the address.

The problem is that, when I try to save, I get NullPointerException. While debugging I found out, that Hibernate tries to save PersonAddress object, and it needs hash code of one of the primary keys it uses.

Since Address is not yet save, it has id == null. Basically the flow of save is currently:

Person -> PersonAddress -> Address

But it should be:

Person -> Address -> PersonAddress

(because it needs to get generated ids for person and adddress).

Is there a way to configure Hibernate to behave in that way?

Upvotes: 2

Views: 1785

Answers (2)

arminas
arminas

Reputation: 421

Found out the problem. When creating @Embeddable class for primary key usage, you need to provide simple objects for id columns. I.e. it should not contain objects mapped by foreign key:

@Embeddable
class PersonAddressId implements Serializable {

@Column(nullable = false)
private Long relatedPartyId;

@Column(nul
private Long addressId;

}

To support composite primary key, as well as foreign keys, you need to add @MapsId annotation:

@Entity
public class Person Address {

  @ManyToOne
  @MapsId("personId")
  @JoinColumn(name = "person_id", nullable = false, updatable = false)
  private RelatedParty relatedParty;

  @EmbeddedId
  @JsonIgnore
  private RelatedPartyAddressId id;

  @ManyToOne
  @MapsId("addressId")
  @JoinColumn(name = "address_id", nullable = false)
  private Address address;

}

Upvotes: 1

Simon Martinelli
Simon Martinelli

Reputation: 36223

Your hashCode method is wrong implemented. You must always check for null because this method can be called in various situations.

Read more about that topic: https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/

Upvotes: 0

Related Questions