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.
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);
}
I found many times on stackoverflow this issue, but nothing from them gives me clear answer. For simplicity, there are only two tables film and language bound many to one relation. Everything done according Netbeans Hibernate DVD Store tutorial. Now, how to display in first page (index.xhtml) language. It looks like very straightforward. Simply add:
<h:column>
<f:facet name="header">
<h:outputText value="Language"/>
</f:facet>
<h:outputText value="#{item.languageByLanguageId.langName}"/>
</h:column>
(Comumn in table language name was renamed on langName)
But it issues still the same LazyInitializationException. I tried to obtain languageId and in this case I was successful. It means #{item.languageByLanguageId.langName} gives exception but #{item.languageByLanguageId.languageId} not. It is strange. So what happen, when I use explicit fetch according languageId if I can obtain its.
So I added in FilmController.java method for obtaining language:
public String getLanguageById(Integer langId) {
String language = helper.getLangById(langId);
return language;
}
And in FilmHelper.java (final version):
public Film getFilmById(int filmId) {
Film film = null;
try {
session = HibernateUtil.getSessionFactory().getCurrentSession();
org.hibernate.Transaction tx = session.beginTransaction();
Query q = session.createQuery("select count(film.filmId) from Film as film where film.filmId = :filmId");
q.setParameter("filmId", filmId);
Number count = (Number) q.uniqueResult();
if (count.intValue() > 0)
film = (Film) session.load(Film.class, filmId);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
}
return film;
}
And yes, it works, I can obtain language name to modify index.xhtml:
<h:outputText value="{filmController.getLanguageById(item.languageByLanguageId.languageId)}"/>
Than I tried to modify FilmActor.hbm.xml to add lazy="false" and use origin simple solution in index.xhtml ("#{item.languageByLanguageId.langName}"):
<many-to-one name="languageByOriginalLanguageId" class="dvdrental.Language" lazy="false" fetch="select">
<column name="original_language_id" />
</many-to-one>
Again it works properly. Even if I set lazy="proxy" or lazy="no proxy". But still I don't understand, how to use this default attribute lazy="true". If I try to keep whole document in one session (don't do commit, which causes end of session), there is another Exception issue. It looks like, that lazy="true" doesn't meet in any time proper result.
With setting lazy=true attribute you are allowing hibernate to delay association retrieving. So when you disable lazy=false then hibernate will immediate do its fetching method, just after parent instance is retrieved. Yours problem will be solved if you set fetch="join".
Join fetching: Hibernate retrieves the associated instance or
collection in the same SELECT, using an OUTER JOIN.
Your example;
<many-to-one name="languageByOriginalLanguageId" class="dvdrental.Language" fetch="join">
<column name="original_language_id" />
</many-to-one>
You can look at fetch and lazy as how and when, respectively. In your example lazy=false solved your problem but still two queries where done because your fetch method was select.
Hibernate Fetching Strategies
UPDATE
Once when object is lazy initialized, you have only properties of your entity and lazy initialized association (only id you have). Then that object is passed for further(transaction is committed already) use and you want to use one of lazy initialized association(in your case Language) and got exception. This is happening because you are accessing lazy object and your transaction is already commited, so hibernate wants to execute your second query without success (fetch="select"). This can be fixed by moving the code that reads association to just before the transaction is committed.
When your object is detached and your current session is closed then you must do
Hibernate.initialize(entity)
to assign your detached entity to another session.
Thank you for explanation. I tested your advice. It works and I suppose, that join should be quicker than two distinct selects (it depends on indexes, optimizer, etc.), but still when I try combination lazy="true" and fetch="join" it again fails:
<many-to-one name="languageByOriginalLanguageId" class="dvdrental.Language" lazy="true" fetch="join">
<column name="original_language_id" />
</many-to-one>
Even if exception is different, still no succes:
java.lang.ExceptionInInitializerError
at controller.HibernateUtil.<clinit>(HibernateUtil.java:30)
However, there are clearly explained three ways, how to avoid problems with default or explicit lazy="true".
we have a big problem in our development team.
We are using Hibernate and we have some entities which are related in two transitive one-to-many relations. The main object is a Group which has a list of Property instances, and each Property containing a list of Values.
(The mappings are down ahead)
We have two main problems:
A) When making a HQL Query, Criteria Query or SQLQuery it doesn't matter the conditions applied in JOINs or WHERE clauses, Hibernate always retrieves for us all the underlying objects. For example, if I make a Criteria or SQL getting only the Group objects, Hibernate comes and (lazy or not) gets all the Property and Value instances too. We want to control this. We want to do left joins and get only the properties with no values inside (Hibernate removes these properties with no value)
B) When making the Query, for example, a SQL, it shows in the log the SQL code we want. Everything seems perfect. But after that it brings every instance in the list without applying conditions, getting them only by id, and we can assure this because with lazy="true" we see the "load many-to-one" queries in the log.
Is there something we can do in hibernate config, fetching mode/strategy, the mappings configuration or anywhere? I'm thinking on going on Result transformers now.
I would be grateful if someone coud give me a hint or tell me where to find a solution to this problem. We are confused about how to get this, but it must be a way.
Thanks in advance
Query:
Criteria lstCriterios = this.getSession().createCriteria(CardGroup.class, CARD_GROUP)
.add(Restrictions.eq(ID_CATEGORY, idCategory));
lstCriterios.createAlias("listProperty", "listProperty", CriteriaSpecification.LEFT_JOIN);
if (clusterId != null) {
lstCriterios.add(Restrictions.or(
Restrictions.isNull("listPropertyValue" + ".value"),
Restrictions.and(Restrictions.eq("listPropertyValue" + ".clusterId", clusterId),
Restrictions.eq("listPropertValue" + ".companyWarehouseId", idCompanyWarehouse))));
lstCriterios
.createAlias("listProperty" + "." + "listPropertyValue", "listPropertyValue",
CriteriaSpecification.LEFT_JOIN,
Restrictions.eq("listPropertyValue" + ".clusterId", clusterId));
} else {
lstCriterios.createAlias("listProperty" + ".listPropertyValue", "listPropertyValue",
CriteriaSpecification.LEFT_JOIN);
}
lstCriterios.add(Restrictions.eq(ID_CATEGORY, idCategory));
lstCriterios.add(Restrictions.eq("listProperty" + ".groupId", idGroup));
lstCriterios.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
/*
* Sorting
*/
lstCriterios.addOrder(Order.asc("order"));
lstCriterios.addOrder(Order.asc("listProperty" + ".order"));
lstCriterios.addOrder(Order.asc("listPropertyValue"+ ".clusterId")); // Agrupacion, podrĂa ser nulo
lstCriterios.addOrder(Order.asc("listPropertyValue"+ ".propertyId")); // Propiedad
lstCriterios.addOrder(Order.asc("listPropertyValue"+ ".id"));
return lstCriterios.list();
Group mapping:
<list name="listProperty"
table="FICHA_PROPIEDAD" schema="${db2.siglo.schema}"
inverse="false" cascade="all" >
<key column="ID_FICHA_GRUPO" not-null="false" />
<list-index column="ORDEN" base="1"/>
<one-to-many
class="com.company.aslo.appwebsiglo.model.card.property.property.CardProperty" />
</list>
Property mapping:
<bag name="listPropertyValue"
table="FICHA_PROPIEDAD_VALOR" schema="${db2.siglo.schema}"
inverse="false" cascade="all">
<key column="ID_FICHA_PROPIEDAD" not-null="false" />
<one-to-many
class="com.company.aslo.appwebsiglo.model.card.propertyvalue.propertyvalue.CardPropertyValue" />
</bag>
It seems like our model design was bad and we didn't realize that if the DB table FICHA_PROPIEDAD_VALOR has Composite Key we can't map only one of the attributes in the composite key, because it brings us unexpected results.
Because of this and the nested objects, we had also bad implementations of the hashCode() and equals() methods which Hibernate uses.
I had solved this previously with a ResultTransformer getting the rows from a SQLQuery, but we got the Hibernate solution after that refactoring and changing the design of our model.
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 two tables Item and Property and one item can have multiple properties. I have modeled it correctly (i think) in hibernate and when loading the ItemModel object, all the properties load properly.
The problem is when I am trying to delete properties and then save it, the properties just get added to the existing ones.
ItemModel m = ...;
m.getPropertySet().size() // returns 5 initially
m.getPropertySet().clear();
// some update function which adds properties
m.getPropertySet().size(); // returns 1
...currentSession().saveOrUpdate(m);
What happens is that now the database has 6 properties for that category instead of 1. What should I do to make this work?
The model for Item's mapping to properties looks something like this
<set name="propertySet" cascade="all">
<key column="item_id" not-null="true"/>
<one-to-many class="Property"/>
</set>
Use cascade="all-delete-orphan". See the first example in the reference guide for a walkthrough of relationships like this. Also, if this is a bidirectional one-to-many, then this side (the set) should be mapped with inverse="true" so that the relationship is determined solely based on the other side of the relationship.