I am sure there is a hibernate term for this but I am not sure what it is (and I could therefore probably look up the answer) but here goes.
I have a Product Pojo where one of the fields is a Set of Suppliers (also a Pojo). When I call to get a list of Products, by default it queries to get the list of Suppliers- which is what I would expect.
However, sometimes I do not require the data in the Set and the querying it is taking too long to just bear it. Is there a way to sometimes avoid querying the data for the Set?
Or is my design incorrect?
My Product.hbm.xml mapping file has:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field" default-lazy="false" package="model">
<class name="model.Product" table="PRODUCT">
<id column="PRODUCT_ID" name="id" type="long">
<generator class="native" />
</id>
...
<set name="suppliers" sort="unsorted" table="SUPPLIERS">
<key column="PRODUCT_ID" />
<one-to-many class="model.Suppliers" />
</set>
...
</class>
</hibernate-mapping>
The default behavior of Hibernate is what you are expecting: All collections mapped for an entity, and all associated entities, be it through to-many or to-one associations, are not loaded by default.
But if you have defined the lazy="false" for the Set of Suppliers, Hibernate will fetch the associated collection eagerly along with the Product.
Change the suppliers mapping as below, so that Hibernate will not fetch it eagerly:
<set name="suppliers" sort="unsorted" table="SUPPLIERS" lazy="true">
<key column="PRODUCT_ID" />
<one-to-many class="model.Suppliers" />
</set>
EDIT:
The above mapping is the default fetching strategy for suppliers. Now if you want to load suppliers along with the Product, you can override this default strategy at runtime in code:
String productSelect = "select p from Product "
+ "p left join fetch p.suppliers "
+ "where p.productName=:pname";
Query query = session.createQuery(productSelect);
query.setParameter("pname", productname);
Product result = (Product) query.uniqueResult();
The above query will fetch the Product along with the set of suppliers. So, wherever needed, you can override the default strategy by using above query to get suppliers along with Product.
Set the fetchtype to LAZY (instead of EAGER) for the supplier Set.
Related
I have a legacy hibernate application (using hibernate 3.2.6.ga) where we fetch information similar to the structure below. We have a person who contains several other attributes like address details or bank accounts or transactions.
Person
List of Address Details
List of bank accounts
List of transactions
and many more...
With a hibernate mapping file like this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="myPkg.Person" table="PERSON" schema="WhatEver" lazy="false">
<id name="personId" type="java.lang.Long">
<column name="PERSON_ID" precision="1" />
<generator class="sequence">
<param name="sequence">some_sequence_generator</param>
</generator>
</id>
<!-- lazy init is true as only some methods is interested in addressDetails-->
<bag name="addressDetails" inverse="true" cascade="all" lazy="true">
<meta attribute="propertyType">java.util.LinkedList</meta>
<key>
<column name="PERSON_ID" precision="10" not-null="true" />
</key>
<one-to-many class="myPkg.Address" />
</bag>
...
<!-- lazy init is true as a person can have billion transactions -->
<bag name="transactionDetails" inverse="true" cascade="all" lazy="true">
<meta attribute="propertyType">java.util.LinkedList</meta>
<key>
<column name="PERSON_ID" precision="10" not-null="true" />
</key>
<one-to-many class="myPkg.transaction" />
</bag>
</class>
</hibernate-mapping>
We have several methods to retrieve the persons. Some requires that the child related data be returned as well and some not. So all in the mapping file is set to lazy=”true”.
In cases where we are interested in the children right after we return the data we do a Hibernate.initialize(personObjectJustReturned) to go and fetch the children as well:
Person personInstance = (Person) getHibernateTemplate().get(Person.class, new Long(id));
Hibernate.initialize(personInstance);
But the problem comes in with the transactions list as this can become a very large set of data - where a person can have billions of transactions.
What I would like to do is never return transactions with the person object at all.
So I can do one of 2 things:
Simply make everyting eagerly loaded in my XML file by setting lazy=”false” (apart from my transactions) and don’t do a Hibernate.initialize(personObjectJustReturned). But that would mean that any other method returning data will eagerly fetch the child data as well as all methods in the app is using the same hibernate mapping. Which is not ideal as for example not all methods requires address details. So I will be fetching more data than required on a global scale which is bad practice.
Remove the transactions field relationship from my config file so hibernate will not even know about the transactions field when it retrieving a person. This will work perfectly but in cases where we save a person object and that person has newly generated transactions we will have to save the transactions separately. Aka we cannot simply tell hibernate go and save the person object which automatically saves the newly generated transactions as hibernate don't know about the relationship anymore.
Is there a better way to do this?
The first option you show can lead to memory consumption problems if you have a lot of children per object.
The best option then, is to load the children fetched by the parent object:
SELECT * FROM transaction_detail WHERE person = ?
This way you avoid the Hibernate.initialize(personObjectJustReturned) and fetch the details more precisely (sorted or more filtered).
Then, to save a new transactionDetail, the TransactionDetail object must have the Person object set (as the parent) and when saving (the save will be done at the TransactionDetail), no error should happen (the foreign keys will avoid orphans).
I'm using Hibernate mapping to config stages in my application. It's configured that the necessary class (here called Configurator) is injected with the stages.
I have one list containing Object Score:
private List<Score>dsScore = new ArrayList<Score>(0);
I have a database containing table Score and a list in class Student and student.hbm.xml as:
`<list name ="dsScore" table="SCORE">
<key>
<column name="SCORE_ID" not-null="true"></column>
</key>
<one-to-many class="model.Score"/>
</list>`
into a file config Student containing it, it not work.
Can anybody help me with this?
list hibernate type is collection with persisted ordering. It
requires explicit mapped column in underlying table for ordering, so
your mapping is missing <index>/<list-index> element. OR you
should use bag (maybe with order-by attribute) to get collection
mapped to java List without persisting order. See docs
Use package
attribute on <hibernate-mapping> instead of qualified class name.
I assume that both Student and Score classes are in model package. The mapping then should look like
<hibernate-mapping package="model">
<class name="Student" table="...">
...
<bag name="dsScore" table="Score" order-by="...">
<key column="SCORE_ID" />
<one-to-many class="Score" />
</bag>
</class>
</hibernate-mapping>
I have these 2 mappings:
<hibernate-mapping>
<class name="sample.Operator" table="OPERATOR">
<id name="id" >
<generator class="native" />
</id>
<property name="name" not-null="true">
<column name="NAME" />
</property>
<set name="pointCodes" inverse="false" lazy="true" cascade="save-update">
<key>
<column name="OperatorID" />
</key>
<one-to-many class="sample.PointCode" />
</set>
</class>
<hibernate-mapping>
<class name="sample.PointCode" table="POINTCODE">
<id name="id">
<generator class="native" />
</id>
<properties name="pointCodeKey" unique="true">
<property name="pointCode" not-null="true">
</property>
<property name="networkIndicator" not-null="true">
</property>
</properties>
<property name="name" not-null="true">
</property>
</class>
</hibernate-mapping>
Most of the time when featching an Operator I want the pointCodes to be fetched lazily, so I don't want to set lazy="false" in the mappings,
However I have one query, e.g. session.createQuery("from Operator").list() where I do want the pointCodes association to NOT be fetched lazily - how do I do that ?
The hibernate reference manual writes:
A "fetch" join allows associations or
collections of values to be
initialized along with their parent
objects using a single select. This is
particularly useful in the case of a
collection. It effectively overrides
the outer join and lazy declarations
of the mapping file for associations
and collections. See Section 20.1,
“Fetching strategies” for more
information.
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens
A fetch join does not usually need to
assign an alias, because the
associated objects should not be used
in the where clause (or any other
clause). The associated objects are
also not returned directly in the
query results. Instead, they may be
accessed via the parent object. The
only reason you might need an alias is
if you are recursively join fetching a
further collection:
from Cat as cat
inner join fetch cat.mate
left join fetch cat.kittens child
left join fetch child.kittens
The fetch construct cannot be used in
queries called using iterate() (though
scroll() can be used). Fetch should be
used together with setMaxResults() or
setFirstResult(), as these operations
are based on the result rows which
usually contain duplicates for eager
collection fetching, hence, the number
of rows is not what you would expect.
Fetch should also not be used together
with impromptu with condition. It is
possible to create a cartesian product
by join fetching more than one
collection in a query, so take care in
this case. Join fetching multiple
collection roles can produce
unexpected results for bag mappings,
so user discretion is advised when
formulating queries in this case.
Finally, note that full join fetch and
right join fetch are not meaningful.
Just for reference sake, I ran into something like this once, where hibernate was always eagerly loading a set for me.
Turns out I had this setter:
public void setStreams(Set<StreamRef> streams) {
for (StreamRef s : (Set<StreamRef>) streams) {
s.setTape(this); // boo on hibernate, which apparently needs this. whoa!
}
this.streams = streams;
}
which was being called by hibernate (passing it a pointer to a lazy set) but then my for loop was basically calling it to be loaded at that point. If that helps any followers :)
You can test if it's lazy by closing the session, then trying to iterate over the collection. If it's lazy, you'll get org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: XX.streams, no session or session was closed
I've created a UserObject and RoleObject to represent users in my application. I'm trying to use hibernate for CRUD instead of raw JDBC. I've successfully retrieved the information from the data base, but I can not create new users. I get the following error.
org.springframework.web.util.NestedServletException: Request processing failed; nested
exception is org.springframework.dao.DataIntegrityViolationException: could not insert:
[com.dc.data.UserRole]; nested exception is
org.hibernate.exception.ConstraintViolationException: could not insert:
[com.dc.data.UserRole]
My data base is defined as follows:
Users Table, Authority Table and Authorities table. Authorities table is a join of users and authority.
My hibernate mapping for UserObjec is as follows:
...
<set name="roles" table="authorities" cascade="save-update" lazy="false" >
<key column="userId" />
<one-to-many class="com.dc.data.UserRole"/>
<many-to-many class="com.dc.data.UserRole" column="authId" />
</set>
</class>
...
UserRole is mapped as follows:
<class name="com.dc.data.UserRole" table="authority">
<id name="id" column="authId">
<generator class="native" />
</id>
<property name="roleName">
<column name="authority" length="60" not-null="true" />
</property>
</class>
How do I need to change my mapping or Object structure to be able to persist new users?
You are defining two different relationships inside of your "set" element. What you probably want is just the many-to-many element.
If this still doesn't work, try saving the UserRole itself to see if you can persist it on its own. If you can, then the ConstraintViolationException is being thrown while trying to persist User.
Last tip, you probably don't want to cascade save/update on the "roles" Set. In all likelihood your UserRoles will already be in the DB and simply be attached to the Users as they get created.
The contraint violation on UserRole might be a cause of trying to insert a row with a duplicate key. Maybe experiment with using other types of generators, such as "sequence".
I have a user object that has a one-to-many relationship with String types. I believe they are simple mappings. The types table hold the associated user_id and variable type names, with a primary key 'id' that is basically a counter.
<class name="Users" table="users">
<id column="id" name="id" />
...
<set name="types" table="types" cascade="save-update">
<key column="id" />
<one-to-many class="Types" />
</set>
</class>
<class name="Types" table="types">
<id column="id" name="id" />
<property column="user_id" name="user_id" type="integer" />
<property column="type" name="type" type="string" />
</class>
This is the java I used for adding to the database:
User u = new User();
u.setId(user_id);
...
Collection<Types> t = new HashSet<Types>();
t.add(new Type(auto_incremented_id, user_id, type_name));
u.setTypes(t);
getHibernateTemplate().saveOrUpdate(u);
When I run it, it gives this error:
61468 [http-8080-3] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
61468 [http-8080-3] ERROR org.hibernate.util.JDBCExceptionReporter - Duplicate entry '6' for key 'PRIMARY'
61468 [http-8080-3] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
When I check the sql, it shows:
Hibernate: insert into users (name, id) values (?, ?)
Hibernate: insert into types (user_id, type, id) values (?, ?, ?)
Hibernate: update types set id=? where id=?
Why does Hibernate try to update the types' id?
The error says: Duplicate entry '6' for key 'PRIMARY', but there really isn't? I made sure the ids are incremented each time. And the users and types are added into the database correctly.
I logged the information going in, and the types added has an id of 7 and a user id of 6. Could it be that Hibernate takes the user_id of 6 and tried to update types and set id=6 where id=7? Therefore the duplicate primary key error?
But why would it do something so strange? Is there a way to stop it from updating?
Should I set the id manually? If not, then how should I add the types? It gives other errors when I add a type object that only has a type string in it and no ids.
Thanks guys. Been mulling over it for days...
Your biggest problem is incorrect column in the <key> mapping - it should be "user_id", not "id". That said, your whole mapping seems a bit strange to me.
First of all, if you want IDs auto generated you should really let Hibernate take care of that by specifying appropriate generator:
<id column="id" name="id">
<generator class="native"/>
</id>
Read Hibernate Documentation on generators for various available options.
Secondly, if all you need is a set of string types, consider re-mapping them into a collection of elements rather than one-to-many relationship:
<set name="types" table="types">
<key column="user_id"/>
<element column="type" type="string"/>
</set>
That way you won't need explicit "Types" class or mapping for it. Even if you do want to have additional attributes on "Types", you can still map it as component rather than entity.
Finally, if "Types" must be an entity due to some requirement you have not described, the relationship between "Users" and "Types" is bi-directional and needs to be mapped as such:
<set name="types" table="types" inverse="true">
<key column="user_id"/>
<one-to-many class="Types"/>
</set>
...
in Types mapping:
<many-to-one name="user" column="user_id" not-null="true"/>
In the latter case "Types" would have to have a "user" property of type "Users".
Here is a detailed example.
The solution that worked for me was to separately save the two parts (without adding type to user):
getHibernateTemplate().save(user);
getHibernateTemplate().save(new Type(user.id, type_name));
And with the < generator class="native" /> on only the types id.
Bad practice?
Had mapped it as a collection of elements, but it was somewhat wrongly adding the user_id to the types id column which causes duplicate error sometimes; since types id is the only primary key column. Weird! I sort of remember there was some other column error, but forgot about it, because I immediately reverted back to one-to-many. Couldn't grasp the strange workings of Hibernate...
I will try the bi-directional solution sometime... Thanks a lot for all the help :)