I have an Account entity and an AccountTransaction entity.
Account 1 <----> n AccountTransaction
In my AccountTransaction.hbm.xml I specify a many-to-one relationship:
<hibernate-mapping>
<class name="com.walshk.accountmanager.domain.AccountTransaction" table="AccountTransaction">
<id name="id" type="long" column="transaction_id">
<generator class="increment"/>
</id>
<property name="date" not-null="true" type="date" column="transaction_date"/>
<property name="description" not-null="true" column="transaction_description" length="500"/>
<property name="amount" column="transaction_amount" not-null="true"/>
<many-to-one name="account" column="account_id" not-null="true" cascade="all" lazy="false"/>
</class>
</hibernate-mapping>
This allows me to lookup AccountTransactions by account using
Criteria criteria = session.createCriteria(AccountTransaction.class)
.add(Restrictions.eq("account", account));
and also allows me to get the Account instance using AccountTransaction#getAccount();
What I want to do now is provide a way to get an account, e.g
Criteria criteria = session.createCriteria(Account.class).add(Restrictions.eq("id", id));
But I also want the Account entity to have a method
List<AccountTransaction> getTransactions();
And I want this to be lazy loaded, since I may not even need to list the transactions.
Since I am already specifying the relationship as many-to-one from the AccountTransaction how do I now specify a one-to-many relationship giving me access from the other direction.
Additionally, what's the best way to handle lazy-loading, do I have to assign a session to each entity and not close the session? I could potentially have too many sessions open though.
Thanks.
If you add a One-to-Many association in your Account class hibernate mapping, you will get:
List<AccountTransaction> getTransactions();
from any ORM creation tool.
One of the parameters of this association is the loading type - I am not familiar with the exact syntax in XML mapping, as we use annotations, but you could probably find it in any reference/documentation page of hibernate XML mapping.
in Order to work with Lazy Loading, You should have Open Session in view enabled.
If you are using Spring integration that you have OpenSesionInViewIntereptor/OpenSessionInViewFilter
If you are using native Hibernate without Spring integration, then you can implement it by yourself.
Please read the following:
http://community.jboss.org/wiki/OpenSessioninView
Hope it helps.
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 have a class Goods
<class name="Goods">
...
<map name="names" lazy="false" fetch="join">
<key not-null="true" />
<map-key column="LANGUAGE_CODE" type="language" length="2"/>
<composite-element class="Goods$Names">
<property name="name" not-null="true" type="text"/>
<property name="description" type="text"/>
</composite-element>
</map>
...
</class>
The problem arises when I try to search Goods by name like this:
session.createQuery("select g from Goods g where g.names[:lang].name = 'Some goods name'")
javax.servlet.ServletException: java.lang.IllegalArgumentException: Cannot create element join for a collection of non-entities!
Is it really impossible to do so with Hibernate?
Do I really need to make goods name an entity with it's own id?
Any solution?
If you want to create the entity class with composite key, then you have to go for inner class in java, If you are making the entities in hibernate than you have to maintain the relationship(like many to many, one to many, one to one, many to one) between your entities, and then Hibernate have the functionality to perform the join internally, you need not to perform that on you own, this is the whole hint from my side,please try it even this makes your design good understandable to others.
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 have an object:
public class Data {
private long id;
private long referenceCount;
private Blob dataCache;
/* getters and setters for the above */
}
Corresponding to this is a mapping:
<hibernate-mapping>
<class name=Data" table=DATA">
<id name=id" type="long">
<column name=DATA_ID" precision="20" scale="0" />
<generator class="assigned" />
</id>
<property name="referenceCount" type="long" generated="always" insert="false" update="false">
<column name="REFERENCE_COUNT" precision="10" scale="0" not-null="true" />
</property>
<property name=dataCache" type="blob">
<column name="DATA" />
</property>
</class>
</hibernate-mapping>
The contents of 'dataCache' may be quite large.
Here's where it gets tricky: The value of referenceCount is set by a trigger,
invoked by an object that has no mapping relation to the Data entity. As soon
as I save this object, I must refresh a data object -- which may or may not
already be loaded by the session -- in order to keep the reference count
correct. Leaving aside whether this is viewed as a good idea, I have to ensure
that the value of the referenceCount is up to date in my local session, but I
do NOT want to reload the dataCache blob. I could set the lazy attribute on
that property, but I'm not sure that it'll act the way I hope it will.
How should I do this? I guess that one way I could look at it is: "How can I remove an object from the session cache without loading that object if it isn't already there?"
According to this forum post, field of type java.sql.Blob/Clob, should support lazy loading. Therefore, one would expect that doing a Session.refresh() on a persistent instance of Data would, as documentation specifies, re-read the state of the given instance from the underlying database, but not load the dataCache.
Still, I would recommend that you move the dataCache to separate class and map it as mandatory one-to-one relationship. Make sure you don't have cascade refresh from Data to DataCache. That way you can exercise full control over what gets refreshed and what gets loaded.
Why not have the object update the referenceCount in the domain whenever it would fire the trigger on the DB to keep it synced with the persisted value.
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".