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);
}
Related
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 have a simple scenario in many places in my webapp - when showing a list of objects, I don't want to query ALL the details (ie child objects), but when I show the user a single object for them to edit, I DO want to query the entire object. So I let Hibernate default to lazy-fetching these child objects for getting the list, and want to override that with fetch = JOIN at runtime. I tried 2 methods, both of which should work but dont!
Here is my mapping file :
<hibernate-mapping>
<class name="User" table="User">
<id name="objectId" type="java.lang.Integer">
<column name="Object_ID" />
<generator class="identity" />
</id>
<many-to-one name="address" class="Address" cascade="save-update" >
<column name="Address_ID" not-null="true"/>
</many-to-one>
... other User properties ...
</hibernate-mapping>
First, I tried querying the default lazy User object, and then using Hibernate.initialize() to load the lazy child object :
User user = session.get(User.class, (Serializable) id);
if ( !Hibernate.isInitialized(user.getAddress()) )
Hibernate.initialize(user.getAddress());
}
Hibernate recognized that the child Address object wasnt loaded but initialize() STILL didn't load the Address. Why?
Next I tried a fetch-profile, by adding this to the Hibernate mapping file :
<fetch-profile name="returnEntireUser">
<fetch entity="User" association="address" style="join"/>
</fetch-profile>
And then using the code:
User u1 = session.get(User.class, (Serializable) id);
session.enableFetchProfile("returnEntireUser");
User u2 = session.get(User.class, (Serializable) id);
And both u1 and u2 objects are the same - both without the Address object filled in. I know Hibernate recognizes the fetch profile, but still doesnt do anything.
Can anyone figure out WHY these methods dont work and what I can do to get them to work
OK, I feel a little bit stupid. Both of the above methods DO actually work, however you will NOT see the results in an Eclipse debugger session. In another section of code I used Criteria and the
setFetchMode("field",FetchMode.JOIN);
method and in this case the debugger object DOES contain all the data, so I expected to see the full object in the debugger using the above strategies, but it didn't happen.
Also for ohers to note you cannot run test cases in the code back to back on the same object because of Hibernate caching the object. The debugger might or might not show anything meaningful.
I have a java object set to update. I used SaveOrUpdate method to do that.it works fine for updating children, but there is an issue with my requirement.
because I need to delete a child from database if one child is deleted.
please anyone have an good idea to implement this ?
Ex :-
project has a one to many relationship with people.
project has a atribute like,
class Project {
long id;
Set<People> people;
......
}
public void updateProject(Project project) {
try {
getCurrentSession().saveOrUpdate(project);
} catch (Exception ex) {
logger.error(ex);
}
project object contains people set but deleted some people Ids from that.
I need to update project with deleting unwanted people Ids
Thank you
I assume you have enabled cascading on your Project entity so my guess is that you need to add the orphanRemoval attribute to the relation.
Example for your case would be:
#OneToMany(cascade={CascadeType.ALL}, orphanRemoval=true)
Ref Code: Configure cascade with 'all-delete-orphan' for removing orphan data...
<hibernate-mapping>
<class name="User" table="UTABLE" >
<id name="UID" column="UID"/>
<property name="Name" column="F_Name"/>
<set name="phones" table="PHONE_NUMBERS" cascade="all-delete-orphan" lazy="true">
<key column="UID"/>
<one-to-many class="PhoneNumber" />
</set>
</class>
</hibernate-mapping>
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?).
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.