Could not save parent first when the child has 2 FK relationships with 2 parents, FK or Parent key is null


#1

The ERD has two inheritance relationships, one is Person extends InvolvedParty, other is PostalAddress extends Location.

Basically, when I was using the restRepo in the Spring data to save the Person object in a testing environment using H2 database.

The entitles as below:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class InvolvedParty {

    private Long partyId;
    private String partyTypeCode;
    private String preferredLanguageCode;
    private String name;

    @Id
    @GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "party_generator")
    @SequenceGenerator(name="party_generator", sequenceName = "Seq_PartyId", allocationSize = 1)
    @Column(name = "PartyId", nullable = false)
    public Long getPartyId() {
        return partyId;
    }

@Entity
public class Person extends InvolvedParty {

	private String firstName;
	private String middleName;
	private String lastName;
	private LocalDate dateOfBirth;
	private LocalDate dateOfDeath;
	private String genderCode;
	private String nameSuffix;
	private String personTitle;
	private String residenceProvinceTerritoryCode;
	private String maritalStatusCode;
	private Long socialInsuranceNumber;
	private Integer socialSecurityNumber;
	private String taxExemptFlag;
	private List<PartyRole> partyRoles;
}

@Entity
public class PartyRole implements Serializable{

	private Long partyRoleId;
	private String partyRoleTypeCode;
	private LocalDate effectiveDate;
	private LocalDate endDate;
	private String statusCode;
	private String statusReasonCode;
	private List<PartyRoleLocationRelationship> partyRoleLocationRelationships = new ArrayList<>();

@Id
	@GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "party_role_generator")
	@SequenceGenerator(name="party_role_generator", sequenceName = "Seq_PartyRoleId", allocationSize = 1)
	@Column(name = "PartyRoleId", nullable = false)
	public Long getPartyRoleId() {
		return partyRoleId;
	}

@OneToMany(mappedBy = "partyRole", cascade = CascadeType.ALL, orphanRemoval = true)
	@JsonManagedReference
	public List<PartyRoleLocationRelationship> getPartyRoleLocationRelationships() {
		return partyRoleLocationRelationships;
	}
}

@Entity
public class PostalAddress extends Address {
@OneToMany(mappedBy = "postalAddress", cascade = CascadeType.ALL, orphanRemoval = true)
	@JsonManagedReference
	public List<PartyRoleLocationRelationship> getPartyRoleLocationRelationships() {
		return partyRoleLocationRelationships;
	}
}

@Entity
public class PartyRoleLocationRelationship implements Serializable {

	private PartyRole partyRole;
	private PostalAddress postalAddress;
	private String partyRoleLocationRelationshipTypeCode;
	private LocalDate effectiveDate;
	private LocalDate endDate;

@Id
	@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
	@JoinColumn(name = "PartyRoleId", referencedColumnName = "PartyRoleId")
	@JsonBackReference
	public PartyRole getPartyRole() {
		return partyRole;
	}

@Id
	@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
	@JoinColumn(name = "LocationId", referencedColumnName = "LocationId")
	@JsonBackReference
	public PostalAddress getPostalAddress() {
		return postalAddress;
	}

@Id
	@Column(name = "PartyRoleLocationRelationshipTypeCode", nullable = false, length = 15)
	public String getPartyRoleLocationRelationshipTypeCode() {
		return partyRoleLocationRelationshipTypeCode;
	}

@Id
	@Column(name = "EffectiveDate", nullable = false)
	public LocalDate getEffectiveDate() {
		return effectiveDate;
	}
}

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Location {

	private Long locationId;
	private String locationTypeCode;
	private String locationStatusCode;
	
	@Id
	@GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "location_generator")
	@SequenceGenerator(name="location_generator", sequenceName = "Seq_LocationId", allocationSize = 1)
	@Column(name = "LocationId", nullable = false)
	public Long getLocationId() {
		return locationId;
	}
}

@Entity
public class Address extends Location {

	private String electronicAddressTypeCode;
	private String addressTypeCode;
	private String invalidAddressFlag;
}

The calling code:

person = new Person();
		person.setLastName("tan");
		person.setSocialInsuranceNumber(123456789L);
		person.setDateOfBirth(LocalDate.parse("1998-07-26"));
		person.setPartyTypeCode(PartyTypeCode.PERSON.toString());

		PartyRole partyRole = new PartyRole();
		partyRole.setPartyRoleTypeCode("dummy");
		partyRole.setEffectiveDate(LocalDate.parse("1998-07-26"));

		PostalAddress postalAddress = new PostalAddress();
		postalAddress.setPostalCode("123");
		postalAddress.setCpValidationFlag("Y");
		postalAddress.setMunicipalityName("Montreal");
		postalAddress.setProvinceTerritoryCode("QC");
		postalAddress.setLocationTypeCode("dummy");

		PartyRoleLocationRelationship partyRoleLocationRelationship = new PartyRoleLocationRelationship();
		PartyRoleLocationRelationshipPK relId = new PartyRoleLocationRelationshipPK();
		relId.setPartyRoleLocationRelationshipTypeCode("d");
		relId.setEffectiveDate(LocalDate.of(2018, 3, 6));
		relId.setPartyRole(partyRole);
		relId.setPostalAddress(postalAddress);

		partyRoleLocationRelationship.setEndDate(LocalDate.of(2018, 3, 22));
		partyRoleLocationRelationship.setId(relId);

		partyRole.getPartyRoleLocationRelationships().add(partyRoleLocationRelationship) ;
		postalAddress.getPartyRoleLocationRelationships().add(partyRoleLocationRelationship);

		List<PartyRole> partyRoles = new ArrayList<>();
		partyRoles.add(partyRole);

		person.setPartyRoles(partyRoles);

		//create new person in the database
		person = personRepository.save(person);

Hibernate threw some errors:

java.lang.NullPointerException at org.hibernate.type.descriptor.java.AbstractTypeDescriptor.extractHashCode(AbstractTypeDescriptor.java:65) at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:187) at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:191) at org.hibernate.type.EntityType.getHashCode(EntityType.java:355) at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:245) at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:59) at org.hibernate.engine.spi.EntityKey.(EntityKey.java:54) at org.hibernate.internal.AbstractSessionImpl.generateEntityKey(AbstractSessionImpl.java:338) at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:158) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:121) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:765) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:398) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162) at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:431) at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:363) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111)

I debugged the process, the error was originated from when JPA trying to save PartyRoleLocationRelationship which has embedded Ids, it would need to get the id from PartyRole but would also need get the id from PostalAddress table, since PostalAddress extends Location so the cause was probably PostalAddress could not get the id from its parent Location.

PartyRoleLocationRelationship, PartyRoleId was not null but the LocationId was null that’s why it threw nullpointexception.

There must be some mistakes in my mappings that caused the issue. probably I was not dealing the relations between PartyRoleLocationRelationship, PartyRole, PostalAddress correctly?


#2

You need to add the entities and the data access code to your post to be clear for others reading the question even after you will remove the archive from Google Drive.


#3

Thanks for the reminder, I posted the entitles code here.


#4

You forgot to add the PartyRoleLocationRelationshipPK class. That’s where the problem originates. Did you implement equals/hashCode properly?

Check out the User Guide for more details.


#5

Thanks, I included it. Yes, I used the IDE to auto generated all the equals/hashCode in all entitles.

I traced the error via IDE, the PartyRoleLocationRelationship needed partyRoleId and LocationId, partyRoleId was not null but the LocationId was null, when Hibernate calls LocationId.hashCode(), since locationId is null, that was the time I got the nullPointException error.


#6

The idea is since PartyRole and Location has many-to-many relation via PartyRoleLocationRelationship. Hibernate should save its parents which were PartyRole and Location First since we needed their ids before inserting into PartyRoleLocationRelationship.

But the current behavior is Hibernate tried to save PartyRole first, then save PartyRoleLocationRelationship, at last save Location.

Since PartyRoleLocationRelationship needed LocationId from Location, so that was the reason it failed?


#7

The IdClass has more fields than the class where it is used. You need to read the documentation and the JPA spec to see how to use IdClass properly.


#8

Thanks for the hints, I read the IdClass yesterday, I will read it again. You mentioned PartyRoleLocationRelationshipPK has more fields than the PartyRoleLocationRelationship ? I am confused on this part, PartyRoleLocationRelationshipPK has 4 fields which composited as the primary where PartyRoleLocationRelationship has 5 fields which includes one extra field.


#9

I understood now, my mistake, since original entitles has many fields, I omitted some fields in the post, now I added them back.


#10

I reposted the entitles, hopefully it’s more clear about the table relationships.


#11

Try with @Embeddeable and @EmbeddedId. @IdClass is weird mapping inspired from Entity Beans days anyway.

And the model is also extra complicated too. Instead of using inheritance to reuse field, better use composition via FK as RDBMS were designed to work. Inheritance is for varying behavior as explained in this article.

Also, PartyRoleLocationRelationship could better off use an id which simplifies associations. Use a unique key for those columns instead.

Always strive for simple domain model that match RDBMS database modeling principles. The more complicated the model, the more surprises you’ll have.


#12

Thanks, original I used @ Embeddeable and @ EmbeddedId, it was kind of working, but the code looks a bit weird then I decided to switch to @ IdClass.

I will try @ Embeddeable and @ EmbeddedId again.

I don’t have control over the data model, it is done by DBA.

from the log file:
call next value for Seq_PartyId
call next value for Seq_PartyRoleId

then it failed, it looks it couldn’t get the locationId from Seq_LocationId sequence. it needs both the PartyRoleId and LocationId before inserting PartyRoleLocationRelationship.


#13

I added back the calling code to do the unit test int the post, also switched back to @ Embeddeable and @ EmbeddedId.

LOL, I got the same error as before, the order trying to persist those objects were wrong, it tried to save PartyRoleLocationRelationship before Location without the LocationId, but it couldn’t get the LocationId from Seq_LocationId sequence.


#14

PostalAddress extends Address extends Location

I tried insert PostalAddress first using restRepo then insert PartyRoleLocationRelationship, it worked, because the first we inserted PostalAddress, we’ve got the locationId which was needed for the PartyRoleLocationRelationship.

Looks inheritance relationship in the PostalAddress and Location, did not pass down the id from Location table.


#15

The PartyRoleLocationRelationship entity does not cascade the persist operation to Location, which is the parent.

So, you always need to save the PostalAddress first or use cascade in;

@Id
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "LocationId", referencedColumnName = "LocationId")
public PostalAddress getPostalAddress() {
	return postalAddress;
}

#16

@vlad
I tried adding back cascade = CascadeType.PERSIST on the child side, still got the same nullPointException which means Hibernate was still trying to save child first before its parent but the child needed both ids from its parents first before inserting.

I’ve removed the PartyRoleLocationRelationshipPK class which was the composited primary key but no longer needed.

@Entity
public class PartyRole implements Serializable{

	private Long partyRoleId;
	private String partyRoleTypeCode;
	private LocalDate effectiveDate;
	private LocalDate endDate;
	private String statusCode;
	private String statusReasonCode;
	private List<PartyRoleLocationRelationship> partyRoleLocationRelationships = new ArrayList<>();

@Id
	@GeneratedValue(strategy= GenerationType.SEQUENCE, generator = "party_role_generator")
	@SequenceGenerator(name="party_role_generator", sequenceName = "Seq_PartyRoleId", allocationSize = 1)
	@Column(name = "PartyRoleId", nullable = false)
	public Long getPartyRoleId() {
		return partyRoleId;
	}

@OneToMany(mappedBy = "partyRole", cascade = CascadeType.ALL, orphanRemoval = true)
	@JsonManagedReference
	public List<PartyRoleLocationRelationship> getPartyRoleLocationRelationships() {
		return partyRoleLocationRelationships;
	}
}

@Entity
public class PostalAddress extends Address {
@OneToMany(mappedBy = "postalAddress", cascade = CascadeType.ALL, orphanRemoval = true)
	@JsonManagedReference
	public List<PartyRoleLocationRelationship> getPartyRoleLocationRelationships() {
		return partyRoleLocationRelationships;
	}
}

@Entity
public class PartyRoleLocationRelationship implements Serializable {

	private PartyRole partyRole;
	private PostalAddress postalAddress;
	private String partyRoleLocationRelationshipTypeCode;
	private LocalDate effectiveDate;
	private LocalDate endDate;

@Id
	@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
	@JoinColumn(name = "PartyRoleId", referencedColumnName = "PartyRoleId")
	@JsonBackReference
	public PartyRole getPartyRole() {
		return partyRole;
	}

@Id
	@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
	@JoinColumn(name = "LocationId", referencedColumnName = "LocationId")
	@JsonBackReference
	public PostalAddress getPostalAddress() {
		return postalAddress;
	}

@Id
	@Column(name = "PartyRoleLocationRelationshipTypeCode", nullable = false, length = 15)
	public String getPartyRoleLocationRelationshipTypeCode() {
		return partyRoleLocationRelationshipTypeCode;
	}

@Id
	@Column(name = "EffectiveDate", nullable = false)
	public LocalDate getEffectiveDate() {
		return effectiveDate;
	}
}

#17

If you can replicate it with this test case, you should open a Jira issue so we can investigate it.