Consider the following:
User.java
class User{
...
private Set<Community> communities = new HashSet<Community>();
...
}
Community.java
class Community{
...
private Set<User> users = new HashSet<User>();
...
}
User.hbm.xml
<set name="communities" cascade="save-update" lazy="false" table="tbl_user_community">
<key column="user_id" />
<many-to-many class="Community" column="community_id"/>
</set>
Community.hbm.xml
<set name="users" lazy="false" cascade="save-update" table="tbl_user_community" inverse="true">
<key column="community_id" />
<many-to-many class="User" column="user_id"/>
</set>
both has many-to-many relationship.
Code for adding community to user:
HibernateTemplate template = null;
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
template = new HibernateTemplate(sessionFactory);
User user = template.get(User.class, "100");
Community community = template.get(Community.class,1);
user.getCommunities().add(community);
template.saveOrUpdate(user);
Scenario:
community1 is assigned to user1 (inserted into database)
community2 is assigned to user1 (inserted into database)
community1 is assigned to user2 (inserted into database)
community2 is assigned to user2 (not inserted into database, throws NonUniqueObjectException)
if i use template.merge(user) instead of template.saveOrUpdate(user).. it works .. why???
saveOrUpdate() and update() take a detached object and attach this object instance to the session. So if an entity with the same type and ID is already attached to the session, Hibernate can't attach another instance: it would break the guarantee that only one entity with a given ID exists in a session. So it throws this exception.
merge() is different. It takes an detached entity, loads the entity with the same ID in the session if not already loaded, and copies the state (i.e. the fields) of the detached entity to the attached one. The detached entity stays deteached and unmodified.
So, in general, merge() should be preferred. BTW, the other methods don't exist in the JPA API. If you use saveOrUpdate(), you should generally call it as the first instruction of the transaction, to avoid being in the situation causing the NonUniqueException.
Related
I have an EMF containment hierarchy: An House contains a list of Rooms, represented using an ecore file.
Before persisting, when I initially create my house and populate it with rooms, the eContainer value for a Room object correctly show the containing house. I then use Hibernate (no Teneo involved) to save the house (which cascade-saves the rooms).
When I reload the House object, I get references to all the Rooms. However, the eContainer value for a Room is now null. This is relevant because I use EcoreUtil.delete(bathroom) to delete a Room from its containing house, and that uses EObject.eContainer().
Relevant Hibernate Mapping:
<class name="House" table="house" lazy="false">
<bag name="rooms" lazy="true" cascade="save-update, delete">
<key update="true">
<column name="house_id" unique="false"/>
</key>
<one-to-many class="Room"/>
</bag>
</class>
<class name="Room" table="room" lazy="false">
</class>
What should I do to delete EMF objects? Should I call something other than EcoreUtil.delete(obj)? Should I manually add the eContainer references on load? Should I change my hibernate mapping?
Hibernate requires every property to have a getter and setter. In EMF (not using Teneo at least), there is a getter for Rooms but no setter.
public EList<Room> getRooms() {
if (rooms == null) {
rooms = new EObjectContainmentEList<Room>(Room.class, this, housePackage.HOUSE__ROOM);
}
return rooms;
}
I provided my own setter below.
public void setRooms(List<Room> roomList) {
this.rooms = new BasicEList<>();
this.rooms.addAll(roomList);
}
This was incorrect because a BasicEList does not do any provide EMF notification, so the eContainer was not getting set. I changed to the following, which in the underlying implementation uses a EObjectContainmentEList and so far it seems to work.
public void setRooms(List<Room> roomList) {
ECollections.setEList(getRooms(), roomList);
}
This is my OrderSet.hbm file. It has OrderSetMembers as it's child (one-to-many) relationship.
<list name="orderSetMembers" lazy="true" cascade="all-delete-orphan" inverse="true">
<key column="order_set_id" not-null="true"/>
<list-index column="sequence_number"/>
<one-to-many class="OrderSetMember" />
</list>
This is my OrderSetMember.hbm file. OrderSetMember has a many-to-one relationship with its parent. I wanted a bi-directional mapping.
<many-to-one name="orderSet" class="OrderSet">
<column name="order_set_id"/>
</many-to-one>
Can the parent and the child both be saved with one session-save command?
Or do I need to have another session save to save the child as well?
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(orderSet);
These are my data-models:
public class OrderSet {
private List<OrderSetMember> orderSetMembers;
}
public class OrderSetMember {
private OrderSet orderSet;
}
cascade and inverse have two completely different purposes.
cascade tells which entity lifecycle operation should be automatically applied on child(ren) when applied on parent.
inverse means that child is the owner of the association. That further means that if you don't associate child with the parent on the child side, the relationship information is not going to be synchronized with the database.
For example, if you add an OrderSetMember to the orderSetMembers collection in the parent entity instance, but you leave orderSet field null in the OrderSetMember instance, and then invoke session.saveOrUpdate(orderSet), the outcome will be:
Both OrderSet and OrderSetMember instances are saved to the database (save is cascaded to the children).
order_set_id (foreign key in the table to which OrderSetMember is mapped) is set to null. This is because OrderSetMember is the owner of the association, and there was no associated OrderSet entity when Hibernate inspected the owner of the association at dirty-check time.
When you read the above OrderSet instance in a new session, you'll notice that the OrderSetMember is not present in the orderSetMembers collection either.
I am trying to delete all the referenced entity in a collection of a parent entity by setting null on the collection.
For eg :
A is the parent class having one-to-many relation with class B.
Class A {
private Set<B> setB = new HashSet<B>();
}
The mapping is as follows :
<set name="setB " table="B" cascade="save-update" inverse="true">
<key column="FKey"></key>
<one-to-many class="B" />
</set>
a.setB(null);//a is persistent instance of A
The above call to set the collection as null is not deleting the entries in B. Has this anything to do with inverse = true.
Is it illegal to delete the child entities this way?
What about a.getB().clear();?
I have the following class diagram and I want to map it to a database (note that Person has a list with objects of class Vehicle).
Also my database looks like:
All tables in the database that represent a subclass of the Vehicle class have all the fields of the superclass Vehicle. Also, all the relations show a one-to-many relationship from Person to Vehicle, Car and Motorcycle.
My hibernate mapping files are the following:
Person.hbm.xml
<hibernate-mapping package="....">
<class name="Person" table="Persons">
<id name="key" column="Person_ID">
<generator class="native"/>
</id>
<list name="ownedVehicles" inverse="false" cascade="all">
<key column="Person_ID" not-null="true" />
<list-index column="idx"/>
<one-to-many class="Vehicle"/>
</list>
</class>
</hibernate-mapping>
Vehicle.hbm.xml
<hibernate-mapping package="...">
<class name="Vehicle" table="Vehicles" polymorphism="implicit">
<id name="id" type="int" column="Vehicle_ID">
<generator class="increment"/>
</id>
<property name="numOfSeats"/>
<union-subclass name="Car" table="Cars"></union-subclass>
<union-subclass name="Motorcycle" table="Motorcycles"></union-subclass>
</class>
</hibernate-mapping>
The problem (error I get) is the following:
Hibernate: insert into Persons (Person_ID) values (default)
2013-06-26 15:41:52 WARN JdbcCoordinatorImpl:424 - HHH000386: ResultSet had no statement associated with it, but was not yet registered
Hibernate: update Car set numOfSeats=? where Vehicle_ID=?
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
I get this error when I run:
Car car = new Car();
car.setNumOfSeats(5);
Person person = new Person();
person.getOwnedVehicles().add(car);
ManagePerson managePerson = new ManagePerson();
Integer personID = managePerson.store(person);
The store() function of ManagePerson actually creates a session and a transaction and then uses the save() method provided by Hibernate to persist the objects into the database.
As far as I understand Hibernate usually will do insert into Persons, then insert into Cars and finally update Cars (the update is done to save the foreign keys on Cars table that will reference the Person that owns the cars). However, here this is not the case and the insert into Cars seems to be getting skipped. I understood how Hibernate works here by trying person.getOwnedVehicles().add(vehicle); instead of person.getOwnedVehicles().add(car); on the code given above.
As you might understand, I am trying to see if Hibernate actually understands in which "subclass" table a record should go, depending on the class of the object contained in the ownedVehicle list of the Person class. For example, if the ownedVehicles has an object of class Car and one of class Motorcycle, then each of these should go to Cars and Motorcycle tables respectively.
Note: I am using Hibernate 4.2.2 and HSQLDB 2.2.9.
I would appreciate any help with this.
Thanks.
I think it is just a matter of incorrect use of the implicit polymorphism of Hibernate.
Implicit polymorphism for your case can only work by changing your list to have
inverse="true". This can be done of course if your Vehicle class also 'knows' about the relationship with the Person class (e.g. by adding an 'Owner' property and the corresponding mapping).
(Have a look at this table and the case of "table per concrete-class (union-subclass)" and one-to-many associations.
If you enable logging and raise the log level to DEBUG you would see that currently Hibernate tries to update the Vehicles table with the Person_ID instead of the Car table like you meant it to. This is because of the inverse="true" and the limitations of the combination of the Table-per-concrete-class mapping strategy and implicit polymorphism (have a look at the documentation).
So, by having the Vehicle class know about its Owner and using inverse="true" you should be able to succeed in what you are trying to do. Either this or try one of the other inheritance mapping strategies (again have a look at the documentation).
If the managePerson.store(...) method doesn't have a recursive call to the objects in "getOwnedVehicles()" such that it can then call their "store" methods then you shouldn't expect that the created "car" object would be inserted into the table.
You are in fact calling "managePerson.store" not "manageCar.store", I'd have to see the code in the .store(...) method to be sure though but I would expect that it is not doing an iteration of the Vehicles and is not doing an insert for any discovered ones (why should it unless you built it explicitly to do that?).
I have 3 Entities named, Discipline, DisciplineMembership, DisciplineManagement:
one Discipline has many DisciplineMemberships
one Discipline has many DisciplineManagements
a DisciplineManagement has one to one relation with DisciplineMembership
the related part of associated hbm file for Discipline looks like this:
Discipline.hbm:
<class name="Discipline" table="DISCIPLINES">
<id name="id" type="java.lang.Long">
<generator class="increment" />
</id>
<set name="memberships" inverse="false" cascade="all-delete-orphan" >
<key column="disciplineId" not-null="true" />
<one-to-many class="DisciplineMembership" />
</set>
<set name="managements" inverse="false" cascade="all-delete-orphan">
<key column="disciplineId" not-null="true" />
<one-to-many class="DisciplineManagement" />
</set>
</class>
in a function called A, I want to iterate over DisciplineMemberships and DiscipilineManagements of a Discipline:
public void A(){
Discipline discipline = this.getObject();
for(DisciplineMembership mem: discipline.getMembers()){
System.out.print(mem);
}
for(DisciplineManagement man: discipline.getManagements()){
System.out.print(man);
}
}
first for statement runs correctly, but on second one, I catch LazyInitializationException as follows:
13-04-27 13:54:26,115 [hibernate.LazyInitializationException (<init>:42)] ERROR: failed to lazily initialize a collection of role: Discipline.managements, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Discipline.managements, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:383)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:375)
at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:368)
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
at org.hibernate.collection.PersistentSet.iterator(PersistentSet.java:186)
at ObjectManagementPart_Edit.A(ObjectManagementPart_Edit.java:6)
I am confused how can this be possible? the Discipline Session is open, I can iterate through its memberships, but when I want to Iterate through its managements, hibernate can not lazyInitialize them.
what should I do?
UPDaTE:
I tried to use Hibernate.initialize() function, so the function A is now like this:
public void A(){
Discipline discipline = this.getObject();
Hibernate.initialize(discipline.getMembeships());
for(DisciplineMembership mem: discipline.getMemberships()){
System.out.print(mem);
}
Hibernate.initialize(discipline.getManagements());
for(DisciplineManagement man: discipline.getManagements()){
System.out.print(man);
}
}
the first use of Hibernate.initialize() for Membeships has no problem,
but in second use for Managements I get this exception:
org.hibernate.HibernateException: collection is not associated with any session
at org.hibernate.collection.AbstractPersistentCollection.forceInitialization(AbstractPersistentCollection.java:474)
at org.hibernate.Hibernate.initialize(Hibernate.java:431)
at discipline.ObjectManagementPart_Edit.A(ObjectManagementPart_Edit.java:6)
Now I am more Confused :/
Just because you can iterate over memberships does not tell you that it actually just fetched them lazy. If the memberships where loaded before (where the session was open) they are keept. Hibernate.initialize(...) will not do anything if the list was already initialised.
For a more precise answer, you need to describe your session management strategy. Most problems with hibernate are rooted in the fact that the session management is not good.