Child entity is not getting inserted/updated on parent entity update with onetomany - spring-data-jpa

I am using #OneToMany annotation to save parent and child entities but I am facing issues while saving child entity in a particular case.
Child entity is getting saved in two cases:
During first insert of a parent with child.
During update of a parent with child when there was no child inserted/saved in database
in first insert
But When parent is inserted with child 1 and then during update of a parent I try to insert child 2 then I am not able to save the child 2
it is failing with below exception:
o.h.e.jdbc.spi.SqlExceptionHelper - ORA-01407: cannot update ("app_Name"."Child_entity"."REFERENCE_ID") to NULL\n
23:22:06.068 ERROR o.h.i.ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
Please see my code as below:
#Data
#Entity
#Table("Parent_table")
public class Parent_entity implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval =true)
#JoinColumn(name="REFERENCE_ID")
private Set<Child_Entity> childrens ;
}
#Data
#Entity
#Table("child_table")
public class Child_entity implements Serializable {
#Id
#GeneratedValue(generator = "seq_gen", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "seq_gen", sequenceName = "child_SEQ",allocationSize=1)
#Column(name ="col_name")
private Integer asSeq;
#Column(name ="REFERENCE_ID")
private String referenceid;
}
In mapper class, I am explicitly setting primary key of the parent table.
Oracle database side I have below foreign key constraint added
ALTER TABLE child_table
ADD CONSTRAINT FK_parent_table
FOREIGN KEY (REFERENCE_ID)
REFERENCES Parent_table(REFERENCE_ID);
I have browsed through similar question on stackoverflow, which suggests that if you are using existing column for foreign key then existing values for that column should not be null.
But in my case column "REFERENCE_ID" is already non nullable.
Please let me know or suggest if I need to add something else to make it work.
Thank you.
Edit:
In update scenario, Hibernate is generating below query:
update child_table set reference_id=null where reference_id=? and child_seq=?
where reference_id is Parent's primary key and child_seq is Child's primary key
Any idea why hibernate is trying to update Parent's primary key
I am explicitly setting Parent Primary key's value in Child's entity

There are actually three problems here:
Apparently "update scenario" inserts two new children instead of keeping one and adding one.
Unidirectional OneToMany relationship with a non-nullable join column
Lombok-generated equals and hashCode
TL;TR: GOTO 2
1. Update scenario
Hibernate is trying to update reference_id to NULL because it wants to "detach" a child entity from the parent. That means, that during update, you are adding two new children instead of keeping one and adding one. I haven't seen the relevant piece of code of yours, but I assume it might look more or less like this:
ParentEntity parent = new ParentEntity();
parent.setId("test");
ChildEntity child1 = new ChildEntity();
child1.setReferenceid(parent.getId());
parent.setChildrens(new HashSet<>(Arrays.asList(child1)));
repository.save(parent);
ChildEntity child2 = new ChildEntity();
child2.setReferenceid(parent.getId());
parent.getChildrens().add(child2);
repository.save(parent);
It ends up with a ConstraintViolationException. In the second save call, child1 is still a "detached" instance,
its id is NULL and Hibernate treats both children as they were new. So first it adds them to the child_table and later tries to remove the "old" one,
by setting its referenceId to NULL (orphan removal hapens later, and is kind of unrelated).
It could be easily fixed:
// ...
parent = repository.save(parent); // <- save(parent) returns updated object
ChildEntity child2 = new ChildEntity();
child2.setReferenceid(parent.getId());
parent.getChildrens().add(child2);
repository.save(parent);
No exceptions anymore but it doesn't solve the problem. Sooner or later you are going to remove a child from the children set and
it will always result in an exception.
2. Unidirectional OneToMany relationship with a non-nullable join column
The canonical way of modeling it would be as follows:
ParentEntity
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "REFERENCE_ID", nullable = false)
private Set<ChildEntity> childrens;
ChildEntity
#Column(name = "REFERENCE_ID", insertable = false, updatable = false)
private String referenceid;
It should work but Hibernate will generate unnecessary 'update' queries:
select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
select nextval ('child_seq')
select nextval ('child_seq')
insert into child_table (name, reference_id, id) values (?, ?, ?)
insert into child_table (name, reference_id, id) values (?, ?, ?)
update child_table set reference_id=? where id=?
update child_table set reference_id=? where id=?
delete from child_table where id=?
Not a big deal with one or two items, but with 100?
This happens because the ParentEntity is the
owner of the relationship (due to the #JoinTable annotation). It knows nothing about child_table foreign key and doesn't know how to deal with it.
2b. "Half of" bidirectional OneToMany relationship
Alternatively, we can try to make ChildEntity the owner of the relationship by removing #JoinColumn and adding mappedBy. In theory, there should be a corresponding #ManyToOne on the other side of the relationship, but it seems to work without it. This solution is optimal, might be not portable though (to different JPA providers or different Hibernate versions).
ParentEntity
#OneToMany(mappedBy = "referenceid", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ChildEntity> childrens;
ChildEntity (no changes, same as in the question)
#Column(name = "REFERENCE_ID")
private String referenceid;
On update Hibernate generates following queires:
select parententi0_.parent_id as parent_i1_1_1_, childrens1_.reference_id as referenc3_0_3_, childrens1_.id as id1_0_3_, childrens1_.id as id1_0_0_, childrens1_.name as name2_0_ ...
select nextval ('child_seq')
select nextval ('child_seq')
insert into child_table (name, reference_id, id) values (?, ?, ?)
insert into child_table (name, reference_id, id) values (?, ?, ?)
delete from child_table where id=?
3. Lombok-generated equals and hashCode
This is not directly related to your question but I think you will face this problem sooner or later. You are using #Data annotations (I assume they are Lombok's, if not, ignore this section). They will generate equals and hashCode methods from all the fields by default, including ids. It is fine in the ParentEntity, where the id is set manually. But in the ChildEntity, where the id (aka asSeq) is generated by the database, it breaks the hashCode()/equals() contract. It may lead to really sneaky bugs. From Hibernate documentation:
The issue here is a conflict between the use of the generated identifier, the contract of Set, and the equals/hashCode implementations. Set says that the equals/hashCode value for an object should not change while the object is part of the Set. But that is exactly what happened here because the equals/hasCode are based on the (generated) id, which was not set until the JPA transaction is committed.
You may want to read more about it here:
Implementing equals() and hashCode()
The JPA hashCode() / equals() dilemma

Related

PSQLException: ERROR: Duplicate key value violates uniqueness constraint

I have two tables
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "car")
public class CarEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "manufacture_id")
private ManufactureEntity manufactureEntity;
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "manufacture")
public class ManufactureEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
There is already an entry named AUDI in the manufacture table.
When I try to create a new record
ManufactureEntity manufactureEntity = new ManufactureEntity();
manufactureEntity.setName("AUDI");
CarEntity car = new CarEntity();
car.setName("a6");
car.setManufactureEntity(manufactureEntity);
carService.create(car);
Then I get an error that such a record already exists
org.postgresql.util.PSQLException: ERROR: Duplicate key value violates the "manufacture_name_key" uniqueness constraint
Details: The key "(name)=(AUDI)" already exists.
It turns out that the call to this function is trying to add a new entry to the manufacture table.
How do I make this record be created only if it is not there, and if such a record already exists, then the manufacture_id field was simply assigned the key value from the manufacture table?
When adding a new CarEntity record, how can I specify that its manufacture Entity field will be assigned to an existing record in the manufacture "AUDI" table?
But if there is no "AUDI" entry in the manufacture table, then you need to create it.
Or is it impossible to implement it at the same time? But only one of the points.
Or do I misunderstand the OneToOne annotation? One entry in the car "a6" table corresponds to only one entry in the manufacturer "AUDI" table. Let's move on. One entry in the car "a8" table corresponds to only one entry in the manufacturer "AUDI" table. Or am I wrong? And here it is necessary to implement ManyToOne.
If I change the OneToOne annotation to ManyToOne, I get the same error
The key "(name)=(AUDI)" already exists.
If I change CascadeType
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
adding to the car table works, but adding to the manufacture table does not work if there is no such record there.
I.e. when I specify a specific manufacturer
manufactureEntity.setId(1L);
then writing to the car table occurs without errors.
But if I specify an unknown manufacturer
manufacture Entity.setName("AUDIM");
Then I get the error
NULL value in the "manufacture_id" column
With the CascadeType.ALL annotation, it works only if there is no such record in the manufacture table and does not work if there is one.
With the CascadeType.MERGE annotation, it works only if there is such an entry in the manufacture table and does not work if there is none.
The question remains open.
How do I make an entry in the car table and an entry in the manufacture table work?
Or is it simply not possible and I need to manually check the existing records in the manufacture table first and if it is not there, then add it, and then write to the car table?
It seems that your problem is related to trying to add a new name of "AUDI" to the column of name. Since this column contains the property of unique, you can only have one of "AUDI" in the whole column, which means that even if the id changes, it is not possible to add a new "AUDI" value.
If you still want to be able to add the same values to the column of name (which seems like you want to do), you can do so by removing the column from the table and adding a new column without the unique constraint. Or simply remove the constraint from the table.
Remove uniqueness of index in PostgreSQL

spring data JPA association in Map

I'm trying to use Map in Spring Data JPA to handle the relationship to store records of equipment quantity.
I followed this guide to create the entity.
Meeting{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "meeting_id", updatable = false)
#JsonIgnore
private int id;
#ElementCollection
#MapKeyColumn(name = "equipment_type")
#MapKeyEnumerated(EnumType.STRING)
private Map<EquipmentType, Integer> equipment = new HashMap<>();
}
EquipmentType is an Enum.
This is the table for the property:
CREATE TABLE IF NOT EXISTS meeting_equipment (
meeting_id INTEGER NOT NULL REFERENCES meeting (meeting_id),
equipment_type VARCHAR(10) NOT NULL,
quantity INTEGER NOT NULL DEFAULT 0
);
Once I try to create a meeting entity, I get error ERROR:column "meeting_meeting_id" of relation "meeting_equipment" does not exist
May I know what's the problem here?
Your table meeting_equipment does not match what JPA is expecting.
It has a column meeting_id but your JPA implementation expects meeting_meeting_id
Either rename the column to the expected meeting_meeting_id or configure your mapping to use the current column name. I think this might do the trick:
#JoinTable(joinColumns={#JoinColumn(name="meeting_id")}
Of course, you probably can create your own naming strategy if you have many cases like this and want to keep your column names as they are.

How do I properly annotate two JPA entities which are in a parent child relationship?

Maybe this is a question with an easy answer ... but I don't get it running. At persist() I get the exception that the referential key in the child table is null (which of course is not allowed by the database). I have a recipe and some steps for preparation.
I'm using EclipseLink 2.4.1
Recipe.java (rcpid is autoset by JPA)
#Entity
public class Recipe {
#Id
long rcpid;
List<Recipestep> recipesteps = new ArrayList<>();
#OneToMany(
cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="recipe",
targetEntity=Recipestep.class )
// This does NOT work. Following line tries to access a join-table !!!
// #JoinColumn(name="rcpid", referencedColumnName="rcpid")
public List<Recipestep> getRecipesteps() { return recipesteps; }
// some more attributes, getters and setters
}
Recipestep.java (rpsid is autoset by JPA)
#Entity
public class Recipestep {
#Id
long rpsid;
Recipe recipe;
#ManyToOne( targetEntity=Recipe.class )
#JoinColumn( name="rcpid" )
public Recipe getRecipe() { return recipe; }
// some more attributes, getters and setters
}
The code above is a valid workaround. However to have clean (and supportable) code, the relationship should be only one-way with a collection in the parent which references all its children.
You have mapped this as a unidirectional one to many, but have two mappings for the recipestep rcpid database column. Try changing the long rcpid to
#ManyTOne
Recipe rcp;
And then remove the joincolumn definition from the oneToMany and make it bidirectional by marking it as mappedby the rcp manyToOne relation. An example is posted here http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Relationship_Mappings/Collection_Mappings/OneToMany
Eclipselink will always insert nulls on unidirectional oneToMany relations using a joincolumn when first inserting the target entity, and then update it later when it processes the Recipe entity. Your rcpid mapping in Recipestep is also likely null, which means you have two write able mappings for the same field which is bad especially when they conflict like this.
You are experiencing the default JPA behaviour. Adding an entity to the recipesteps list is not sufficient to create a bidirectional relation.
To solve the issue you need to set the rcpid explicitly on every element in the list.
EDIT: I think the issue is that JPA does not know where to store the id of the Recipe in the Recipestep table. It assumes a name ("recipebo_rcpid"), but your table seems to lack it.
Try adding the column "recipe_id" to the Recipestep table and a mappedBy attribute to the #OneToMany annotation:
#OneToMany(
cascade=CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "recipe" )
You probably do not need the targetEntity attribute in the annotation- the List is typed already.

Why won't my EJB Entity work with a non-relational table, when using createNativeQuery?

I'm trying to use entities with a MySQL ndbcluster table. This table type doesn't allow foreign keys, but up until now it hasn't been a problem with my entities.
However, I have run into a bit of a problem, when I try to load an entity using the EntityManager's createNativeQuery method. I need to use this method due to my inability to do this: How to make a CriteriaBuilder join with a custom "on" condition?
My MySQL table looks like this:
CREATE TABLE `category` (
`id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
`category_id` SMALLINT(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `category_id` (`category_id`)
)
COLLATE='utf8_general_ci'
ENGINE=ndbcluster
ROW_FORMAT=DEFAULT
If I change the table engine to innodb, and add foreign keys, the createNativeQuery method works fine.
My entity class looks like this:
#Entity
#Table(name = "category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Short id;
#OneToMany(mappedBy = "categoryId")
private List<Category> categoryList;
#JoinColumn(name = "category_id", referencedColumnName = "id")
#ManyToOne
private Category categoryId;
public Category() {
}
// getters and setters
}
Even without foreign keys, this entity works fine when I use the CriteriaBuilder for a query, but unfortunately not everything is possible with the CriteriaBuilder.
I get an error when I call getResultList on a Query object created with createNativeQuery. I don't know if this is a bug, or if something should be added to my entity class to make this work.
The error says:
Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [ArrayRecord(
=> 2519
=> 2463
=> Tools)] during the execution of the query was detected to be null. Primary keys must not contain null.
Query: ReadAllQuery(referenceClass=Category sql="select * from `category`")
at org.eclipse.persistence.exceptions.QueryException.nullPrimaryKeyInBuildingObject(QueryException.java:895)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:584)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:560)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.buildObject(ObjectLevelReadQuery.java:717)
at org.eclipse.persistence.queries.ReadAllQuery.registerResultInUnitOfWork(ReadAllQuery.java:769)
...
My table contains 1 row, where id=1 and category_id=null, so there are no primary keys with a null-value, despite what the error says. If I remove that row or set category_id=1, I don't get an error.
Need help, please.
Managed to make it work by switching from EclipseLink (JPA 2.0) to OpenJPA (JPA 2.0). Seems like there is a bug somewhere in EclipseLink 2.3.2 and/or GlassFish 3.1.2.2.
I've used EclipseLink (JPA 2.0) in another project of mine, using a slightly different version Netbeans + GlassFish 3.1.1, where I used createNativeQuery on an entity class for a non-relational myisam table. This never caused any problem. It really must be a bug.
But problem solved. Bye, bye EclipseLink, hello OpenJPA.
The issue is case sensitivity. In MySQL your column "id" will be defined in the database as "ID" unless you quote it. If you switch your mappings to upper case it should fix the issue (i.e. "ID").
You could also quote the column name ("'id'")
or set the persistence unit property,
"eclipselink.jpa.uppercase-column-names"="true"

JPA/Eclipselink poor performance batch update entity on ManyToOne relationship changes

I have problem with batch writing in eclipselink when i update an entity properties which link to other entity.
I have a Cardholder entity with #ManyToOne relationship with Card Entity.
#Entity
#Table(name = "...")
#NamedQueries({...})
public class Cardholder implements Serializable {
...
#JoinColumn(name = "card_number", referencedColumnName = "...")
#ManyToOne(fetch=FetchType.LAZY)
private Card card;
}
and a Card with #OneToMany relationship with cardholder
#Entity
#Table(name = "cms_card")
#NamedQueries({...})
public class Card implements Serializable {
#OneToMany(mappedBy = "card")
private List<Cardholder> cardholderList;
}
I already have List of child (persisted cardholders). Now i want to add some card to them,
so :
// cardholderList is a managed entity list.
for (Cardholder cardholder : cardholderList) {
Card newCard = new Card();
...
cardholder.setCard(newCard);
List<Cardholder> cardCardholders = new ArrayList<Cardholder>();
cardCardholders.add(cardholder);
newCard.setCardholderList(cardCardholders);
cardsToBePersisted.add(newCard);
++i;
}
I configured my Persistence.xml to use batch-writing, but performance is horribly slow for +-15000 list update. Now, when i check into generated SQL, i found that Eclipselink is creating one batch for one query, its like :
FINER: Begin batch statements
FINE: INSERT INTO cms_card (card_number, status, chip_serial_number, dwh_status, valid_until, card_holder_id, file_perso_history_id, feedback_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
FINE: bind => [9030002005890011, ACTIVE, null, false, 2015-12-10, null, 241, null]
FINER: End Batch Statements
FINER: Begin batch statements
FINE: UPDATE cms_cardholder SET card_number = ? WHERE (id = ?)
FINE: bind => [9030002005890011, 176075]
FINER: End Batch Statements
FINER: Begin batch statements
FINE: INSERT INTO cms_card (card_number, status, chip_serial_number, dwh_status, valid_until, card_holder_id, file_perso_history_id, feedback_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
FINE: bind => [9030002005889908, ACTIVE, null, false, 2015-12-10, null, 241, null]
FINER: End Batch Statements
I think this is because i set a new parent property (card) to existing children.
I also try to play with reversing the parent - child relationship (cardholder->card instead of card->cardholder).
Batch insert is correct after i reverse the relationship in entity and database, but still Eclipselink would query the database (SELECT * from card where cardholder.id = ?), so for 15000 record i got 15000 select statement. Better than above, but still very very slow.
Is there any mistake i made in setting up batch-writing?
Thank you very much.
What are all of the relationships in the Card and Cardholder?
You seem to have a cycle between the two classes, so EclipseLink must issue the update to resolve the cycle.
You have a card_holder_id in your Card and a card_number in your CardHolder? Why do you have a foriegn key in both tables to the other? If you fix your cycle, then the inserts can be grouped and allow optimal batching.
Not sure what you are doing to get the select? These are new objects are they note? Or are you updating as well?
Im not really sure about the cause of your problem, but i would suggest doing this :
If i have a oneToMany and manyToOne situation, and it involves lots of collection data, i'll avoid using the collection to add detail[s], since it could trigger populating the collection when it's not necessary, which could be the source of performance problem.
A simple example that will help illustrating what i meant :
Consider 2 Entities of Team and Player,
A Team has a list of Player (one to many)
A Player has a Team (many to one)
Whenever i want to add a new player, i would do :
Team team = getTeamFromDbUsingEntityManager(teamId);
Player player = new Player();
...
player.setTeam(team);
entityManager.persist(player);
Notice that it doesnt do team.addPlayer(player); which means, you're not 'touching' the collection.