JPA insert issue - OneToMany/ManyToOne with EmbeddedId - spring

I've an issue where parent is generating multiple insert statements thereby having challenge with saving child. Code:
#Entity
#Table(name="PARENT")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ParentPK id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
// bi-directional many-to-one association to ParDepend
#OneToMany(fetch=FetchType.EAGER, mappedBy="parent", cascade={CascadeType.ALL}, orphanRemoval=true)
private List<ParDepend> parDepends;
//getters, setters, add (for parDepends), remove (for parDepends)
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Borrower other = (Borrower) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
#Embeddable
public class ParentPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#SequenceGenerator(name = "parSeq", sequenceName = "PARENT_SEQ", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "parSeq")
#Column(name="PAR_SK_SEQ")
private long parSkSeq;
#Column(name="PAR_REV_NUM")
private long parRevNum;
//getter, setters
//JPA generated equals and hashcode
}
#Entity
#Table(name="PAR_DEPEND")
public class ParDepend implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ParDependPK id;
private BigDecimal age;
//bi-directional many-to-one association to Parent
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="PAR_REV_NUM", referencedColumnName="PAR_REV_NUM"),
#JoinColumn(name="PAR_SK_SEQ", referencedColumnName="PAR_SK_SEQ")
})
private Parent parent;
//getters, setters
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BorrDepend other = (BorrDepend) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
#Embeddable
public class ParDependPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="PAR_SK_SEQ", insertable=false, updatable=false, nullable=false)
private long parSkSeq;
#Column(name="PAR_REV_NUM", insertable=false, updatable=false, nullable=false)
private long parRevNum;
#SequenceGenerator(name = "parDependSeq", sequenceName = "PAR_DEPEND_SEQ", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "parDependSeq")
#Column(name="SEQ_NUM")
private long seqNum;
#Column(name="REV_NUM")
private long revNum;
//getter setter
//JPA generated equals and hashcode
}
I'm using dozer to convert UI returned data to JPA entity mapping. Then using Spring Repository to save and flush Borrower entity. It is generating borrower sequence twice and inserting nulls for borrower, instead of actual value. My assumption is that actual values are in memory to be inserted with first sequence, while second sequence will null data is getting inserted. Any suggestions on issue with JPA mappings? I do not see dozer an issue as logs shows JPA entity is property created as
{parent=Parent [id=ParentPK [parSkSeq=0, parRevNum=0], firstName=ABCD, lastName=XYZ, parDepends=[ParDepend [id=ParDependPK [parSkSeq=0, parRevNum=0, seqNum=0, revNum=0], age=31]]]}
JPA Ecliselink version 2.5.0-RC1
Spring Data JPA framework version 1.7.1.RELEASE
Please, let me know of any other information required for this question.
Edit: update dozer logs.

Related

Spring Data JPA transient field returns wrong value

I have two models: Deck and Word with a one-to-many relationship. The Deck model has a transient field showWordsToRevise. I am trying to initialize that field with the amount of words in List<Words> with a statusRepeating == true field.
public class Deck {
#Id
#Column(name = "deck_id")
private Long id;
#Transient
private Boolean showWordsToRevise;
#JsonManagedReference
#OneToMany(mappedBy = "deck", cascade = CascadeType.ALL)
private List<Word> words;
public Boolean getShowWordsToRevise() {
System.out.println(this.words.stream().map(w -> w.getStatusRepeating()).count() + " words to revise" + java.time.LocalDateTime.now().toString());
return this.words.stream().map(w -> w.getStatusRepeating()).count() > 0;
}
public void setShowWordsToRevise(Boolean showWordsToRevise) {
this.showWordsToRevise = showWordsToRevise;
}
}
and
public class Word {
#Id
#Column(name = "word_id")
private Long id;
private LocalDate last_checked;
#Enumerated(EnumType.STRING)
private WordGroup wordGroup = WordGroup.newUnseen;
#Transient
private Boolean statusRepeating = false;
#ManyToOne
#JsonBackReference
#JoinColumn(name="deck_id",referencedColumnName="deck_id")
private Deck deck;
public Boolean getStatusRepeating() {
if (this.wordGroup == WordGroup.first && Period.between(this.last_checked,LocalDate.now()).getDays() > 1) return true;
if (this.wordGroup == WordGroup.second && Period.between(this.last_checked,LocalDate.now()).getDays() > 2) return true;
return this.wordGroup == WordGroup.third && Period.between(this.last_checked, LocalDate.now()).getDays() > 7;
}
public void setStatusRepeating(Boolean statusRepeating) {
this.statusRepeating = statusRepeating;
}
}
The problem is that when there are no words in List<Words> with statusRepeating == true the showWordsToRevise in Word still returns true.
JSON
Hibernate logs twice for the same request, first prints the wrong number, then prints the right number. upd it shows 0 at second print because there is no words in List<Words>
Hibernate:
select
words0_.deck_id as deck_id8_3_0_,
words0_.word_id as word_id1_3_0_,
words0_.word_id as word_id1_3_1_,
words0_.body as body2_3_1_,
words0_.deck_id as deck_id8_3_1_,
words0_.definition as definiti3_3_1_,
words0_.example as example4_3_1_,
words0_.last_checked as last_che5_3_1_,
words0_.transcription as transcri6_3_1_,
words0_.word_group as word_gro7_3_1_
from
word words0_
where
words0_.deck_id=?
1 words to revise2021-10-07T10:28:30.059128400
Hibernate:
select
words0_.deck_id as deck_id8_3_0_,
words0_.word_id as word_id1_3_0_,
words0_.word_id as word_id1_3_1_,
words0_.body as body2_3_1_,
words0_.deck_id as deck_id8_3_1_,
words0_.definition as definiti3_3_1_,
words0_.example as example4_3_1_,
words0_.last_checked as last_che5_3_1_,
words0_.transcription as transcri6_3_1_,
words0_.word_group as word_gro7_3_1_
from
word words0_
where
words0_.deck_id=?
0 words to revise2021-10-07T10:28:30.060126900
You need to update your Deck class and change getShowWordsToRevise() method so that you count the words that have statusRepeating == true:
public class Deck {
#Id
#Column(name = "deck_id")
private Long id;
#Transient
private Boolean showWordsToRevise;
#JsonManagedReference
#OneToMany(mappedBy = "deck", cascade = CascadeType.ALL)
private List<Word> words;
public Boolean getShowWordsToRevise() {
long wordsToReviseCount = this.words.stream().filter(WordLadder::getStatusRepeating).count();
System.out.println(wordsToReviseCount + " words to revise " + java.time.LocalDateTime.now());
return wordsToReviseCount > 0;
}
public void setShowWordsToRevise(Boolean showWordsToRevise) {
this.showWordsToRevise = showWordsToRevise;
}
}

Spring JPA hibernate how to persist children (remove, add, or update) from #OneToMany parent column?

I'm trying to solve this problem since a while and I haven't achieved a 100% solution.
First of all I have to describe my problem. I'm developping a restaurant application, and amoung the Entities, I have the Entity Ingredient and as you know Ingredient can consist of other Ingredient with a specific quantity. So I created an Entity SubIngredient with an Embedded Id.
And to persist subIngredients list I tried a combinations of Cascade and orphanRemoval, each combination worked for some operation but not for the others.
I started by using CascadeType.ALL and the new subIngredient persisted successfuly from the #OneToMany propertiy, But if I try to remove an subIngredient from the subIngredients list and save this error appear.
java.lang.StackOverflowError: null
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.23.jar:8.0.23]......
I loked in the net for a solution and I find the I have to use orphanremoval = true I tried it but it didn't work until I changed cascade from CascadeType.ALL to CascadeType.PERSIST. But this one make the persistance of new SubIngredient this error aprear
Caused by: javax.persistence.EntityNotFoundException: Unable to find com.example.Resto.domain.SubIngredient with id com.example.Resto.domain.SubIngredientKey#51b11186........
These are my Enities:
#Entity
public class Ingredient {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
#Column(name="ID")
private long id;
#NotNull
#Column(unique=true)
private String name;
private String photoContentType;
#Lob
private byte[] photo;
#JsonIgnoreProperties({"photoContentType","photo"})
#ManyToOne
private IngredientType ingredientType;
#OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER,
cascade = CascadeType.ALL /*or orphanRemoval = true, cascade = CascadeType.PERSIST*/ )
private Set<SubIngredient> subIngredients = new HashSet<SubIngredient>();
getters and setters.....
And
#Entity
#AssociationOverrides({
#AssociationOverride(name = "embId.ingredientId",
joinColumns = #JoinColumn(name = "ING_ID")),
#AssociationOverride(name = "embId.subIngredientId",
joinColumns = #JoinColumn(name = "SUB_ING_ID")) })
public class SubIngredient {
#EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();
private double quantity;
getters and setters....
And
#Embeddable
public class SubIngredientKey implements Serializable{
#ManyToOne(cascade = CascadeType.ALL)
private Ingredient ingredientId;
#ManyToOne(cascade = CascadeType.ALL)
private Ingredient subIngredientId;
getters and setters...
The stackoverflow happen because you use a Set<> with Hibernate. When Hibernate retrieves the entities from your DB, it will fill up the Set<> with each entities. In order to that, hashode/equals will be used to determine wether or not the entitie is already present in the Set<>. By default, when you call the hashcode of Ingredient, this happen:
hashcode Ingredient -> hashcode SubIngredient -> hashcode Ingredient
which will result in an infinite call of hashcode method. That's why you have a stackoverflow error.
The same thing will happen with equals/toString.
So to avoid such an issue, it's best to override hashcode, equals and toString.
I have solved the problem by making some changes to may Entities and override equals/hashcode methods thanks Pilpo.
#Embeddable
public class SubIngredientKey implements Serializable{
private Long ingredientId;
private Long subIngredientId;
/**
* #return the ingredientId
*/
#Override
public int hashCode() {
return Objects.hash(ingredientId, subIngredientId);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SubIngredientKey)) {
return false;
}
SubIngredientKey other = (SubIngredientKey) obj;
return Objects.equals(ingredientId, other.ingredientId)
&& Objects.equals(subIngredientId, other.subIngredientId);
}
}
#Entity
public class SubIngredient {
#EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("ingredientId")
private Ingredient ingredient;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("subIngredientId")
private Ingredient subIngredient;
private double quantity;
#JsonIgnore
public SubIngredientKey getId() {
return embId;
}
public void setId(SubIngredientKey id) {
this.embId = id;
}
#JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getIngredient() {
return ingredient;
}
public void setIngredient(Ingredient ingredient) {
this.ingredient = ingredient;
}
#JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getSubIngredient() {
return subIngredient;
}
public void setSubIngredient(Ingredient subIngredient) {
this.subIngredient = subIngredient;
}
public double getQuantity() {
return quantity;
}
public void setQuantity(double quantity) {
this.quantity = quantity;
}
#Override
public String toString() {
return "subIngredient= " + getSubIngredient().getName() + " , quantity= " + getQuantity();
}
#Override
public int hashCode() {
return Objects.hash(ingredient,subIngredient);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SubIngredient)) {
return false;
}
SubIngredient other = (SubIngredient) obj;
return Objects.equals(ingredient, other.ingredient) && Objects.equals(subIngredient, other.subIngredient);
}
}
#Entity
public class Ingredient {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
#Column(name="ID")
private long id;
#NotNull
#Column(unique=true)
private String name;
private String photoContentType;
#Lob
private byte[] photo;
#JsonIgnoreProperties({"photoContentType","photo"})
#ManyToOne
private IngredientType ingredientType;
#OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER, cascade =
CascadeType.ALL, orphanRemoval = true)
private Set<SubIngredient> subIngredients = new HashSet<SubIngredient>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPhotoContentType() {
return photoContentType;
}
public void setPhotoContentType(String photoContentType) {
this.photoContentType = photoContentType;
}
public byte[] getPhoto() {
return photo;
}
public void setPhoto(byte[] photo) {
this.photo = photo;
}
public IngredientType getIngredientType() {
return this.ingredientType;
}
public void setIngredientType(IngredientType ingredientType) {
this.ingredientType = ingredientType;
}
public Set<SubIngredient> getSubIngredients() {
return subIngredients;
}
public void setSubIngredients(Set<SubIngredient> subIngredients) {
this.subIngredients = subIngredients;
}
public void addSubIngredient(SubIngredient subIngredient) {
this.subIngredients.add(subIngredient);
}
#Override
public String toString() {
String subIngsText = "";
for(var subIngredient:this.subIngredients) {
subIngsText = subIngsText + ", " + subIngredient.toString();
}
return "{id= "+id+",name=" + name +", ingredients="+subIngsText+"}";
}
#Override
public int hashCode() {
return Objects.hash(name);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Ingredient)) {
return false;
}
Ingredient other = (Ingredient) obj;
return Objects.equals(name, other.name);
}
}

Many to One Relationship with #IdClass

Using Spring Data JPA & Hibernate, I am saving an object Company, that has 0 to Many AccountMapping. The AccountMappings Primary Key is a composite of a String accountNumber and the Company Primary Key. When I save a new company the COMP_NUM from the Company Object is not set into the AccountMapping object. When I use long companyNumber it is zero, and Long it is NUM. Hibernate is executing the insert statement first, but how to get it to set the primary key from company into child object ?
#Entity
#Table(name = "COMPANY")
public class Company implements Serializable {
#Id
#Column(name = "COMP_NUM")
#SequenceGenerator(name = "comp_num_seq", sequenceName = "comp_num_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "comp_num_seq")
private long number;
#OneToMany(mappedBy = "companyNumber", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<AccountMapping> accountMappings;
public Company() {
super();
}
public long getNumber() {
return this.number;
}
public void setNumber(long id) {
this.number = id;
}
public List<AccountMapping> getAccountMappings() {
return accountMappings;
}
public void setAccountMappings(List<AccountMapping> accountMappings) {
this.accountMappings = accountMappings;
}
}
#Entity
#IdClass(value = AccountMappingPK.class)
#Table(name = "ACCOUNT_MAPPING")
public class AccountMapping implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ACCNT_NUM")
private String accountNumber;
#Id
#Column(name = "COMP_NUM")
private Long companyNumber;
#Column(name = "IS_PRIMARY")
private Boolean isPrimary;
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public Long getCompanyNumber() {
return companyNumber;
}
public void setCompanyNumber(Long companyNumber) {
this.companyNumber = companyNumber;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean isPrimary) {
this.isPrimary = isPrimary;
}
}
public class AccountMapping implements Serializable {
#Column(name = "EA_ACCNT_NUM", nullable = false)
private String accountNumber;
#Column(name = "COMP_NUM", nullable = false)
private Long companyNumber;
public AccountMapping() {
// default constructor
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNum(String accountNumber) {
this.accountNumber = accountNumber;
}
public Long getCompanyNumber() {
return companyNumber;
}
public void setCompanyNumber(Long companyNumber) {
this.companyNumber = companyNumber;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof AccountMappingPK) {
AccountMappingPK accntPk = (AccountMappingPK) obj;
if (!(accountNumber.equals(accntPk.getAccountNumber()))) {
return false;
}
if (!(accntPk.getCompanyNumber() == (companyNumber))) {
return false;
}
return true;
}
return false;
}
#Override
public int hashCode() {
int hash = (accountNumber == null ? 1 : accountNumber.hashCode());
return (int) (hash * companyNumber);
}
}
#Entity
#IdClass(value = AccountMappingPK.class)
#Table(name = "ACCOUNT_MAPPING")
public class AccountMapping implements Serializable {
#Id
#Column(name = "ACCNT_NUM")
private String accountNumber;
#Id
#ManyToOne
#JoinColumn(name = "COMP_NUM")
private Company company;
...
}
// No annotations in this class
public class AccountMappingPK implements Serializable {
private String accountNumber;
private Company company;
...
// All the getter/setter, constructors, and so on ...
}
The Hibernate ORM documentation has more details about mapping with #IdClass: See Example 134. IdClass with #ManyToOne

Spring boot - many to many association not removing join table data

I have an issue with a many-to-many relation in Spring Boot. Code is as follows:
public class Task {
#Id
#GeneratedValue
private Long id;
#ManyToMany(cascade = {PERSIST, MERGE}, fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")}
)
#Builder.Default
private Set<Tag> tags = new HashSet<>();
public void addTags(Collection<Tag> tags) {
tags.forEach(this::addTag);
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTasks().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
public void removeTags() {
for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
Tag tag = iterator.next();
tag.getTasks().remove(this);
iterator.remove();
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
return id != null && id.equals(((Task) o).getId());
}
#Override
public int hashCode() {
return id.intVal();
}
}
and
public class Tag {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(unique = true)
private String name;
#ManyToMany(cascade = {PERSIST, MERGE}, mappedBy = "tags", fetch = EAGER)
#Builder.Default
private final Set<Task> tasks = new HashSet<>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return id.intVal();
}
}
Of course, I have the task_tag table where, after inserting a tag in a task and saving that task, an entry appears. However, when I delete a tag (or clear them), the entries do not get deleted from the join table. This is the test:
#Test
void entityIntegration() {
Task task = taskRepo.save(...);
Tag tag1 = Tag.builder().name(randomString()).build();
Tag tag2 = Tag.builder().name(randomString()).build();
Tag tag3 = Tag.builder().name(randomString()).build();
Tag tag4 = Tag.builder().name(randomString()).build();
final List<Tag> allTags = Arrays.asList(tag1, tag2, tag3, tag4);
tagRepo.saveAll(allTags);
task.addTag(tag1);
taskRepo.save(task);
final Long task1Id = task.getId();
assertTrue(tag1.getTasks().stream().map(Task::getId).collect(Collectors.toList()).contains(task1Id));
task.clearTags();
task = taskRepo.save(task);
tag1 = tagRepo.save(tag1);
assertTrue(task.getTags().isEmpty());
assertTrue(tag1.getTasks().isEmpty());
task.addTags(allTags);
task = taskRepo.save(task); // FAILS, duplicate key ...
}
I delete tag1 but when I try to add it back to the task, I get
The task_tag table does have a composite index formed on those two (and only) columns.
What am I doing wrong? I followed each and every suggestion and advice - using set instead of lists, having helper methods, cleaning up etc...
I can't find the bug.
Thank you!
The biggest thing that sticks out to me is that your Tag's equals and hash-code aren't matched with each other.
Your "equals" drives equality based on the object's name being the same, which makes logical sense to the mantra of "A tag is a name". But the hash-code drives based on the "id" being equivalent and doesn't use the name at all.
Forgetting JPA/Hibernate for a moment, just plain old Collections themselves get very unpredictable when these two are out of synch.
You can read more about that here and specifically why a hash-code that doesn't match equality would end up hashing to the wrong bucket and resulting in confusing keeping it all straight in HashSets: Why do I need to override the equals and hashCode methods in Java?
There are many ways to put them back in synch (using libraries like Lombok and code-generation tools in your IDE come to mind), but instead of prescribing one, I will simply point to this web-resource that, conveniently, created a Tag with the exact same concept for his example, so I suspect you can just use this exact same pattern yourself.
https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
Here's another helpful SO thread I found that talks about relationships and identity/equals/hashCode as it impacts JPA: The JPA hashCode() / equals() dilemma
Kindly add DELETE keyword to the cascade property of many to many annotation . And i believe ur annotation for task property of Tag class should be changed as below .
You can give this below mapping a try
public class Task {
#Id
#GeneratedValue
private Long id;
#ManyToMany(cascade = {PERSIST, MERGE,DELETE}, fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")}
)
#Builder.Default
private Set<Tag> tags = new HashSet<>();
public void addTags(Collection<Tag> tags) {
tags.forEach(this::addTag);
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTasks().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
public void removeTags() {
for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
Tag tag = iterator.next();
tag.getTasks().remove(this);
iterator.remove();
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
return id != null && id.equals(((Task) o).getId());
}
#Override
public int hashCode() {
return id.intVal();
}
}
public class Tag {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(unique = true)
private String name;
#ManyToMany(cascade = {PERSIST, MERGE,DELETE}, mappedBy = "tags", fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")}
)
#Builder.Default
private final Set<Task> tasks = new HashSet<>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return id.intVal();
}
}

org.hibernate.PropertyAccessException: Could not set field value [1] value by reflection

Hi guys I am new to Spring and I am getting this error in my project:
org.hibernate.PropertyAccessException: Could not set field value [1] value by
reflection : [class com.**.domain.identities.NurseAgencyIdentity.agencyId]
setter of com.**.domain.identities.NurseAgencyIdentity.agencyId
There are some classes involved in this process: Nurse , Agency, Named(abstract), NurseAgency and NurseAgencyIdentity. There is a many-to-many relationship between Nurse--Agency with an extra column nurse record. The Named class is an abstract class that contains the fields id and name and is being used by many tables in my design being id the identifier of the descendant table. To implement the many-to-many I had to use the #Embeddable annotation in the last class NurseAgencyIdentity which is the id of my NurseAgency join table. Here is the code:
NurseAgencyIdentity
#Embeddable
#Data
public class NurseAgencyIdentity implements Serializable {
#Column(name="nurse_id")
private Long nurseId;
#Column(name="agency_id")
private Long agencyId;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NurseAgencyIdentity that = (NurseAgencyIdentity) o;
return Objects.equals(nurseId, that.nurseId) &&
Objects.equals(agencyId, that.agencyId);
}
#Override
public int hashCode() {
return Objects.hash(nurseId, agencyId);
}
}
NurseAgency
#Entity
#Data
public class NurseAgency {
#EmbeddedId
private NurseAgencyIdentity id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("nurseId")
private Nurse nurse;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("agencyId")
private Agency agency;
private String nurseRecord;
}
Nurse
#Entity
#Data
public class Nurse {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private License license;
#OneToMany(mappedBy = "nurse", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NurseAgency> agencies = new ArrayList<>();
// need the extra column
public void addAgency(Agency agency) {//, String nurseRecord) {
NurseAgency nurseAgency = new NurseAgency();
nurseAgency.setAgency(agency);
nurseAgency.setNurse(this);
//nurseAgency.setNurseRecord(nurseRecord);
agency.getNurses().add(nurseAgency);
}
public void removeAgency(Agency agency) {
for (Iterator<NurseAgency> iterator = agencies.iterator(); iterator.hasNext(); ) {
NurseAgency nurseAgency = iterator.next();
if (nurseAgency.getNurse().equals(this) && nurseAgency.getAgency().equals(agency)){
iterator.remove();
nurseAgency.getAgency().getNurses().remove(nurseAgency);
nurseAgency.setNurse(null);
nurseAgency.setAgency(null);
}
}
}
#Override
public String toString() {
return id + " " + firstName + " " + middleName + " " + lastName;
}
}
Named
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
#Data
public abstract class Named implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Agency
#Entity
#Data
public class Agency extends Named {
private String description;
#OneToMany(mappedBy = "agency", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NurseAgency> nurses = new ArrayList<>();
}
And I am having this error when trying to seed the join table:
BootStrapData
#Component
public class BootStrapData implements CommandLineRunner {
#Autowired
private final NurseRepository nurseRepository;
#Autowired
private final AgencyRepository agencyRepository;
private final NurseAgencyRepository nurseAgencyRepository;
public BootStrapData(NurseRepository nurseRepository, AgencyRepository agencyRepository, NurseAgencyRepository nurseAgencyRepository) {
this.nurseRepository = nurseRepository;
this.agencyRepository = agencyRepository;
this.nurseAgencyRepository = nurseAgencyRepository;
}
#Override
public void run(String... args) throws Exception {
System.out.println("Loading agencies ");
ArrayList<Agency> agencies = GetAgencies();
System.out.println("Loading Nurses ");
ArrayList<Nurse> nurses = GetNurses(agencies);
nurses.stream().forEach( n -> nurseRepository.save(n));
agencies.stream().forEach( a -> agencyRepository.save(a));
//Nurses Agencies
ArrayList<NurseAgency> nurseAgencies = new ArrayList<>(1);
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(0), new Agency[]{agencies.get(0), agencies.get(1), agencies.get(2)}));
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(1), new Agency[]{agencies.get(0), agencies.get(1)}));
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(2), new Agency[]{agencies.get(1), agencies.get(2)}));
for (int i=0; i<nurseAgencies.size();i++){
nurseAgencyRepository.save(nurseAgencies.get(i)); // I've got the error in first iteration in this line
}
}
private ArrayList<Agency> GetAgencies() {
ArrayList<Agency> agencies = new ArrayList<>(3);
Agency a1 = new Agency();
a1.setName("Agency 1");
agencies.add(a1);
Agency a2 = new Agency();
a2.setName("Agency 2");
agencies.add(a2);
Agency a3 = new Agency();
a3.setName("Agency 3");
agencies.add(a3);
return agencies;
}
private ArrayList<Nurse> GetNurses(ArrayList<Agency> agencies) {
ArrayList<Nurse> nurses = new ArrayList<>(3);
Nurse n1 = new Nurse();
n1.setFirstName("Mario");
n1.setLastName("Perez");
nurses.add(n1);
Nurse n2 = new Nurse();
n2.setFirstName("Luis");
n2.setLastName("Ruiz");
nurses.add(n2);
Nurse n3 = new Nurse();
n3.setFirstName("Maria");
n3.setLastName("Crez");
nurses.add(n3);
return nurses;
}
private ArrayList<NurseAgency> SetNurseAndAgencies(Nurse nurse, Agency[] agencies) {
ArrayList<NurseAgency> nurseagencies = new ArrayList<>(agencies.length);
for (int i=0; i<agencies.length; i++){
NurseAgency na = new NurseAgency();
na.setNurse(nurse);
na.setAgency(agencies[i]);
na.setNurseRecord(nurse.getFirstName() + agencies[i].getName());
nurseagencies.add(na);
}
return nurseagencies;
}
}
Where is the problem?
Try changing the NurseAgencyIdentity declaration on NurseAgency from:
#EmbeddedId
private NurseAgencyIdentity id;
to:
#EmbeddedId
private NurseAgencyIdentity id = new NurseAgencyIdentity();
I didn't see the full stack trace but the root cause can be a NullPointerException when hibernate tries to set fields (generated agencyId [ 1 ] in your case) via reflection on NurseAgencyIdentity and it's null.
See org.hibernate.tuple.entity.AbstractEntityTuplizer#getIdentifier

Resources