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.
Related
I have an owning entity class with some associated entities, e.g.
#Entity
public class Parent {
#Id
#GeneratedValue
private UUID id;
#OneToMany(mappedBy = "parentId", cascade = CascadeType.ALL)
List<Child> children;
...
}
#Entity
public class Child {
#Id
#GeneratedValue
private UUID id;
private UUID parentId;
...
}
I am using Spring Boot Starter Data JPA. I traced the code to the SimpleJpaRepository class, I noticed that on calling save(parent), it checks if isNew() returns true, Spring will call persist(); otherwise it calls merge(). This makes total sense, as persist() will generate only an INSERT, while merge() will generate a SELECT (if it hasn't done before) and then followed by an INSERT if the SELECT returns nothing; otherwise an UPDATE.
The above works well when saving a new Parent with new Child, only INSERTs are generated without any SELECT.
However, my problem is, when creating some new Child and adding them to an existing Parent, then on saving the parent, somehow I noticed Spring JPA is still generating an extra SELECT for each of these new Child entities before the INSERTs, which I found unnecessary.
Is there a way to avoid these SELECT queries?
Further investigation I found that if I leave the id of the Child null (i.e. let it auto generates a new id), then only INSERT is generated. However, if I manually assign an id to a Child, then a SELECT will be generated before an INSERT. Is there a way I can assign id to Child while avoiding the extra SELECT?
On the basis of your code i think because of OnetoMany mapping and you did't provide cascade type then it not happen
select query running two times
provide more information
full code of Entity classes
properties file
I am using hibernate with JPA annotations for relationship mapping.
I have three entities in my code User Group & User_Group
User & Group are in a ManyToMany relationship.
User_Group is a kinda bridge table but with some additional fields. So here is the modified mapping code.
User
#Entity
#Table(name = "USERS")
public class User {
#OneToMany(mappedBy = "user")
private Set<UserGroup> userGroups
}
Group
#Entity
#Table(name = "GROUPS")
public class Group {
#OneToMany(mappedBy = "group")
private Set<UserGroup> userGroups
}
UserGroup
#Entity
#Table(name = "USERS_GROUPS")
public class UserGroup {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "GROUP_ID")
private Group group;
}
When I set the user & group object to the usergroup & save it.
User user = new User("tommy", "ymmot", "tommy#gmail.com");
Group group = new Group("Coders");
UserGroup userGroup = new UserGroup();
userGroup.setGroup(group);
userGroup.setUser(user);
userGroup.setActivated(true);
userGroup.setRegisteredDate(new Date());
session.save(userGroup);
Things work fine. With CascadeType.ALL the group object & user object are updated too. But when I delete the userGroup object. The child object are deleted too.
Deletion of child objects is a strict no no.
There is no CascadeType.SAVE-UPDATE in JPA, which just does save or update but no delete. How do I achieve this.
If I remove the CascadeType.ALL from the mapping the child objects don't get updated & I need them to be updated.
SAVE_UPDATE is for save(), update(), and saveOrUpdate(), which are 3 Hibernate-proprietary methods. JPA only has persist() and merge(). So, if you want to use cascading on Hibernate-proprietary methods, you'll need to use Hibernate-proprietary annotations. In this case, Cascade.
Or you could stop using the Hibernate Session, and use the standard JPA API instead.
CascadeType.ALL includes CascadeType.REMOVE too.
The solution is to use all CascadeType.* you need except CascadeType.REMOVE, like so:
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE}))
in your UserGroup definitions.
It's almost always a code smell when propagating from child to parent entity, it should be the other way round.
From Cascading best practices:
Cascading only makes sense only for Parent – Child associations (the
Parent entity state transition being cascaded to its Child entities).
Cascading from Child to Parent is not very useful and usually, it’s a
mapping code smell.
From Hibernate best practices:
Avoid cascade remove for huge relationships
Most developers (myself included) get a little nervous when they see a
CascadeType.REMOVE definition for a relationship. It tells Hibernate
to also delete the related entities when it deletes this one. There is
always the fear that the related entity also uses cascade remove for
some of its relationships and that Hibernate might delete more
database records than intended. During all the years I’ve worked with
Hibernate, this has never happened to me, and I don’t think it’s a
real issue. But cascade remove makes it incredibly hard to understand
what exactly happens if you delete an entity. And that’s something you
should always avoid. If you have a closer look at how Hibernate
deletes the related entities, you will find another reason to avoid
it. Hibernate performs 2 SQL statements for each related entity: 1
SELECT statement to fetch the entity from the database and 1 DELETE
statement to remove it. This might be OK, if there are only 1 or 2
related entities but creates performance issues if there are large
numbers of them.
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.
I have three tables with simple structure:
pub [id, name]
days [id, name]
pub_days [id, pub_id, days_id]
For some unholy reason, somebody thought that compound identity for pub_days table (that would be pub_id + days_id) is not enough and added own primary key. I can't change it now, other and larger system depends on that. #sigh
I am trying to map this to Hibernate with standard #ManyToMany JPA annotation like so (I omitted getters, setters, #Entitiy annotations and other clutter):
class Pub {
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "pub_days",
joinColumns = {#JoinColumn(name = "pub_id")},
inverseJoinColumns = {#JoinColumn(name = "days_id")})
#OrderBy("id")
private List<Day> pubOpeningDays;
}
class Day {
#Id Long id;
String name.
}
when I execute following code:
Day day = repository.find(Day.class, id);
pub.getPubOpeningDays().add(day);
repository.persist(pub);
I get this error:
ERROR: ORA-01400: cannot insert NULL into ("PUB"."pub_days"."id")
Sadly, that makes perfect sense, because I haven't mapped that ID anywhere. The thing is, I don't even want to. I want it to be generated, but not sure how do I overcome this issue with #ManyToMany mapping. Any ideas?
What you can do is like I mentioned in my comments you can create a separate entity CD which will in turn connect with two classes A and B, Now relationship would be many to many between A and B, and hence A (many to many) CD (many to many) B. Now as per your requirement whenever you need to fetch the instance of A or B, what you can do is simply fire a query in the DB with proper parameters i.e id of a or id of b this will help you get your required result.
I only see two choices, either you change your mapping to a list of PubDay as samwise-gamgee told you in the comments or you add a trigger on insert on table pub_days which set a value for the column id if it is null (it could be using a sequence). But this approach depends on the features supported by your DB.
I'm having a problem setting up a bidirectional association with the one-to-many side as the owner. I will describe my issue using an example from the Hibernate documentation slightly modified.
#Entity
public class Troop {
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne()
#JoinColumn(name="troop_fk", insertable=false, updatable=false, nullable = false)
public Troop getTroop() {
...
}
I understand that in the world of databases the owner of the above relationship would be soldier since it has the column with the foreign key, however this does no make sense in real world logic since a soldier is part of a troop and a troop owns a soldier.
I would like to be able to use CascadeType.ALL to automatically save all Soldier when I persist their troop.
The hibernate documentation indicates that:
This solution is not optimized and will produce additional UPDATE statements.
eg. log
Insert Into Troop(id) Values(1)
Insert Into Soldier(id) Values(1)
Insert Into Soldier(id) Values(2)
Update Soldier Set troop_fk = 1 Where id = 1
Update Soldier Set troop_fk = 1 Where id = 2
The problem is that when we first try to insert soldier without the troop_fk an exception is thrown since that column is not nullable.
Why does Hibernate not just add the troop_fk to the insert instead of updating the record later with it?
Is there a way for me to do what I described above?
The problem is the insertable=false, updatable=false.
This is explicitly telling Hibernate to NOT update the troop_fk when the Soldier entity is inserted or updated.
But you still won't be able to insert a new Soldier entity without first associating it with a troop because the fk column is not nullable.
If you want to be able to create Soldiers without a Troop, then you need to make the association optional: #ManyToOne(optional=true).
And I would change the Troop's #OneToMany association to use the mappedBy. In the 300-odd entities in our system we've never used a #JoinColumn on the #OneToMany side.