Why hibernate generates insert and update for OneToMany mapping - java

I am trying to understand the one-to-many mapping in Hibernate with a small example. I have a Product with a set of Part's. Here are my entity classes:
Part.java
#Entity
public class Part {
#Id
#GeneratedValue
int id;
String partName;
//Setters & Getters
}
Product.java
#Entity
public class Product {
private String serialNumber;
private Set<Part> parts = new HashSet<Part>();
#Id
public String getSerialNumber() {
return serialNumber;
}
#OneToMany
#JoinColumn(name = "PRODUCT_ID")
public Set<Part> getParts() {
return parts;
}
// Setter methods
}
Then I tried to save some parts and products in my database and observed below queries generated by hibernate:
Hibernate: insert into Product (serialNumber) values (?)
Hibernate: insert into Part (partName, id) values (?, ?)
Hibernate: update Part set PRODUCT_ID=? where id=?
Here to add a record in Part table, hibernate generates 2 DML operations - insert and update. If a single insert command is sufficient to add a record in table then why hibernate uses both insert and update in this case? Please explain.

I know this is crazy old but I had the same problem and Google brought me here, so after fixing it I figured I should post an answer.
Hibernate will switch the insert/update approach to straight inserts if you make the join column not nullable and not updatable, which I assume in your case it is neither anyways:
#JoinColumn(name = "PRODUCT_ID", nullable = false, updatable = false)

If Part as composite element list then only two query will come. Please check and revert.
If its not a composite element , hibernate try to insert individual as a separate query and it will try to create relationship between them.
In earlier case hibernate will insert with relationship key.

**Hibernate: insert into Product (serialNumber) values (?)
Hibernate: insert into Part (partName, id) values (?, ?)**
In these two queries hibernate is simply inserting a record into the database.
At that stage hibernate is not creating any relationship between the two entities.
Hibernate: update Part set PRODUCT_ID=? where id=?
Now after making entity tables,hibernate is going to make a relationship between the two
by using the above third query...

The association is uni-directional, so Product is the owning side (because it's the only side).
Make the association bidirectional and make Part the association owner. That way you will avoid redundant updates because the foreign key values will be specified as part of insert statements for Part.

Related

Duplicate Records using Bidrectional Mapping in Hibernate

I want to store workers in an Organization object, For this, I have an Organization class having One to Many Mapping for workers.
#OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
#JoinTable(name = "ORGANIZATION_WORKER", joinColumns = { #JoinColumn(name = "orgIdPK") }, inverseJoinColumns = { #JoinColumn(name = "id") })
public Set<Worker> workerList;
"ORGANIZATION_WORKER is a temporary table that holds the mapping"
Then I have a Worker class where I have Many to One Mapping as follow.
#ManyToOne(cascade=CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name = "orgIdPK")
public Organization organization;
Now when I save the organization after inserting workers into it using hibernate session, i-e
session.saveOrUpdate(org);
insert query runs twice (N+1 issue). `
Hibernate: insert into ORGANIZATION_WORKER (orgIdPK, id) values (?, ?)
Hibernate: insert into ORGANIZATION_WORKER (orgIdPK, id) values (?, ?)
So when I expose Organization data on rest API, all records are shown twice because of which pagination gets wrong on the rest API. Anyone knows how to solve this issue?
The problem is that you are creating a circular reference. You are inserting a organization which has workers in it, and those workers have that same organization as a parameter, so it is inserting twice.
To solve this issue, Hibernate has some keywords applicable to columns: "insertable" and "updatable", that specify whether or not the value of that column will be inserted or updated in DB.
In general, the proper thing to do is to make the OneToMany relationship non-insertable and non-updatable; if you want to change the Organization of a Worker, you will probably do it from that Worker; the class responsible for updating that value is the Worker itself, that is, the class with the ManyToOne association.
So you need to do this:
#OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER, insertable=false, updatable=false)
This way all changes in this column will be ignored when the entity inserts or updates in DB.
Remove the annotations from Worker.
#ManyToOne(cascade=CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name = "orgIdPK")
http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#collections-map-unidirectional

JPA - One to many - duplicate key value error

I need implement join table for users which will have 2 columns USER_CONTACTS(user_id, contact_id)
I need store for contacts lists for users. I need to make possible that user with id 1 can have in contacts user with id 5, but user 5 can also have in contacts user with id 1 (but he musn't).
I need to be possible do following inserts:
INSERT INTO USERS_CONTACTS(user_id, contact_id) VALUES(1, 5);
INSERT INTO USERS_CONTACTS(user_id, contact_id) VALUES(5, 1);
Now I got this error:
Unsuccessful: INSERT INTO USERS_CONTACTS(user_id, contact_id)
VALUES(2, 3) 2017-12-05 15:00:15.146 ERROR 14268 --- [ main]
org.hibernate.tool.hbm2ddl.SchemaExport : ERROR: duplicate key value
violates unique constraint "uk_j9ggomsdbjte1eqfo5e61vh8a"
Here is my relationship implementation of User in JPA:
#Entity
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", columnDefinition = "serial")
private long id;
#OneToMany
#JoinTable(name = "USERS_CONTACTS",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "CONTACT_ID"))
private List<User> contacts;
// ...
}
Can you tell me how to fix it? I am using Postgre 9.5. Thanks.
EDIT
For hibernate I have these properties:
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Try to solve this task yourself.
Enable Hibernate SQL log
Create a database schema with Hibernate on a test database (a tablespace) or using H2 database. You can download this project and play with mappings using unit tests: https://github.com/v-ladynev/hibernate-experimental.
Check generated SQL.
Perhaps, you will notice that contact_id is made unique by uk_j9ggomsdbjte1eqfo5e61vh8a constraint. The reason is that for #OneToMany relation child must has only one parent.
I think you need #ManyToMany relation.
I assume that uk_j9ggomsdbjte1eqfo5e61vh8a is a leftover from previous version.
If it were so then just drop the constraint:
alter table USERS_CONTACTS drop constraint uk_j9ggomsdbjte1eqfo5e61vh8a;
There must not be an unique index on that table! Remove that index and it should work.

Soft delete on many-to-many association EclipseLink

I want to rewrite the call delete operation (on association table) on a many-to-many association sending by EclipseLink when we use only java code.
Let me explain the goal.
I have 3 tables, person, unit and an associative one : PerInUnit, so a person can be in multiple units and a units can contains many people. But I have some dependances on the PeInUnit table (If the person was present or not on a specific date, another table (Participations)), so I can't (and I don't want) delete a record. For that, I make softs deletes, so I can keep records to make some statistics.
I read already about the Customizer and AdditionalCriteria and I setted them to the PerInUnit class. It works perfectly => when I make an em.remove(myPerInUnit); the sql query sent to the db is Update PER_IN_UNIT SET STATUS='delete' WHERE id = #id; and the specified row as "delete" for status. Also, when I read all records, I don't have them with status "delete". But I use explicitly the PeeInUnit class.
Here is the code :
#Entity
#Table(name = "PER_IN_UNIT")
#AdditionalCriteria("this.status is null")
#Customizer(PIUCustomizer.class)
public class PerInUnit implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GEN_SEQ_PIU")
#SequenceGenerator(name = "GEN_SEQ_PIU", sequenceName = "SEQ_PIU", initialValue = 1, allocationSize = 1)
#Column(name = "ID")
private Long id;
#ManyToOne(cascade=javax.persistence.CascadeType.PERSIST)
#JoinColumn(name = "PER_ID")
private Person person;
#ManyToOne(cascade=javax.persistence.CascadeType.PERSIST)
#JoinColumn(name = "UNI_ID")
private Unit unit;
#Column(name = "STATUS")
private String status;
//Constructor, getters, setters
}
And the code for the PIUCustomizer :
public class PIUCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) {
descriptor.getQueryManager().setDeleteSQLString("UPDATE PER_IN_UNIT SET STATUS = 'delete' WHERE ID = #ID");
}
}
Here come the problem : As I use EclipseLink with bidirectionnal relationship I want to make some instruction like myUnit.getPeople.remove(currentPerson); (remove the current person from the unit "myUnit"). But EclipseLink sent the following instruction (during commit !) :
DELETE FROM PER_IN_UNIT WHERE ((UNI_ID = ?) AND (PER_ID = ?))
instead of the
Update PER_IN_UNIT SET STATUS='delete' WHERE ((UNI_ID = ?) AND (PER_ID = ?))
that I expected and raise (obviously, because of dependances (FKs)) the following exception :
Query: DataModifyQuery(sql="DELETE FROM PER_IN_UNIT WHERE ((UNI_ID = ?) AND (PER_ID = ?))")
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:157)
at test.Crud.update(Crud.java:116)
at test.Test.runTest(Test.java:96)
at test.Test.main(Test.java:106)
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-02292: integrity constraint (PEOPLE.FK_PAR_PIU) violated - child record found
Other problem (in the same kind), when I make something like System.out.prinln(myUnit.getPeople()) I have all the people in the unit "myUnit", including them having status 'delete'.
Is it possible to change some code/instructions/Customizer/etc in eclipseLink to change the delete call from person for PerInunit table, or I have to make my own queries and use them instead of using powerful of orm ?
Thanks for your answers and please forgive me for my poor english !
Fab
You should not be getting a delete when you call myUnit.getPeople.remove(currentPerson) unless you mapped Unit to Person with a ManyToMany using the PER_IN_UNIT table. Since you have an entity for the PER_IN_UNIT table, this would be wrong, as it really should be a Unit-> PerInUnit OneToMany mapping and then a PerInUnit -> Person ManyToOne mapping. The myUnit.getPeople.remove(currentPerson) call would then simply be getting the PerInUnit instance and marking its status as deleted, or dereferencing it and letting JPA call remove, thereby using your soft delete SQL query.
By using a ManyToMany mapping for the PER_IN_UNIT table, this mapping is completely independent to your PerInUnit entity mapping, and knows nothing about the entities that maybe cached or the soft deletes required to remove them. If you don't want to map the PER_IN_UNIT table as an entity, see http://www.eclipse.org/forums/index.php/t/243467/ which shows how to configure a ManyToMany mapping for soft deletes.

Hibernate OneToMany with JoinTable ID generation

Could somebody help me in setting appropriate annotation in hibernate for following case:
I have three tables:
Account, Card and AccountCard.
AccountCard is joining table for OneToMany relationship between Card and Account (account has many cards, card is attached to only one account).
I need to add to Account a List cards property and to Card model Account account property. This is the easy thing.
The problem is that I get "Cannot insert null value to AccountCard.id" while persisting Account with Cards.
Also I need to use sequence to generate IDs for joining table but don't know how.
Any help would be very appreciated.
Here is the code in Card:
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "account_card", joinColumns = #JoinColumn(name = "crd_id"), inverseJoinColumns = #JoinColumn(name = "acc_id"))
private Account account;
I don't want to have a mapping in Account class so List cards is not added.
In your #JoinTable annotation, I see reference to an account_name table and not AccountCard. Is there actually an AccountCard table somewhere?
A join table usually doesn't need an id key of its own, and if you have hibernate autogenerate your table DDL it won't include one.
If you do indeed need an id on the join table, I don't think there's a way or a need to make hibernate aware of it, but you should make the column NOT NULL AUTO_INCREMENT in your SQL DDL.
Did you try to generate the tables first in the database (in my case mysql) and then create the entity with an ide like nebans? An auto increment id column in mysql then ends with:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ID")
private Integer id;
Check out http://netbeans.org/kb/docs/javaee/ecommerce/entity-session.html for a sample how to use netbeans to create entities from database

Hibernate One to One mapping not updating the child table

I have following Associated classes with one to one mapping.
#Entity
public class EmployeeEntity
{
#Id
private String id;
private String name;
#OneToOne(mappedBy = "employeeEntity", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(FetchMode.SELECT)
#JoinColumn(name = "empid")
private AddressEntity addressEntity;
...
...
getters & setters
}
#Entity
public class AddressEntity
{
#Id
#Column(unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="employeeEntity"))
private String empId;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private EmployeeEntity employeeEntity;
...
getters & setters
}
I am using postgres and having tables (employeeentity, addressentity) with following foriegn key constraint on addressentity table:
Foreign-key constraints:
"fkakhilesh" FOREIGN KEY (empid) REFERENCES employeeentity(id) ON DELETE CASCADE
I have following requirements with different REST calls:
POST Rest call - should create an employee with address.
POST Rest call - should create an employee without address.
GET Rest call - should retrieve an employeee. Address should also come if it exist.
PUT Rest call - should update an employee and address (if address exists).
PUT Rest call - should update an employee and address (when address is passed and it already exists in addressentity table for empid)
PUT Rest call - should update an employee and create the address (when address is passed and it does not exists in addressentity table for empid)
I am able to perform operations 1 to 5 without any issues.
The main problem is in 6 and following questions come to my mind:
1. when i do "getSession().update(object)" , I get hibernate's StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1.
is this not possible with "update" if address does not exists? can't I create a new address during update?
do i need to change my ServiceImpl call to "getSession().merge(object) ? is this case can only be handled by calling "merge" ? how it impacts performance?
If i do merge, i get hibernate's IdentifierGenerationException: attempted to assign id from null one-to-one property.
Am i missing something here?
this can be solved by changing hibernate mapping? or somethin related to cascade.
what is the importance of #GeneratedValue(generator="gen") here? why is #parameter used in #GenericGenerator
I am new to hibernate and trying to get into the depth of hibernate mapping.
Also, I would be delighted if you could suggest me on the design as what should be the best way to handle this.
I got the fix for this. This one-one mapping is somewhat tricky and not simple as i thought initially.
I have used bidirectional one to one mapping, so it is important to call the setters of both EmployeeEntity and AddressEntity to set each other during update. for example:
employeeEntity.setAddressEntity(addressEntity) and addressEntity.setEmpoyeeEntity(empoyeeEntity) has to explicitly called.
setting alone employeeEntity.setAddressEntity(addressEntity) will not work.
Always use integer Id and use .getSession.saveOrUpdate(entity); for save or update.
In the One to One Mapping you should mention constrained=true on the child. It makes Child Id the same as Parent Id.
Use these lines for child id. I don't know Java attributes syntax.
<generator class="foreign">
<param name="property">employeeEntity</param>
</generator>
Also remove Fetch type and Cascade.All from child. I think the default fetch mode is Select which is fine. Cascade is usally used for the Parent part which is responsible for the parent-child relation.

Categories

Resources