Hibernate lazy loading not work with many-to-one mapping - java

I have problem of performance in my many-to-one mapping. When I debug the SQL query in log file the principal query it's ok, but after i have other query representing many-to-one object mapping.
Entity.hbm.xml :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping>
<class name="com.omb.database.mapping.MyEntity" table="MY_ENTITY">
<id name="id" type="java.lang.Integer">
<column name="ENTITY_ID"/>
<generator class="sequence">
<param name="sequence">SEQ_MY_ENTITY</param>
</generator>
</id>
<property name="prop1" type="string" column="PROP1" />
<many-to-one name="object1" column="OBJECT1_ID" class="com.omb.database.mapping.Object1" />
<many-to-one name="object2" column="OBJECT2_ID" class="com.omb.database.mapping.Object2" />
</class>
</hibernate-mapping>
Object1.hbm.xml :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping default-lazy="true">
<class name="com.omb.database.mapping.Object1" table="TABLE_OBJECT_1">
<id name="id" type="java.lang.Integer" column="OBJECT1_ID" />
<property name="label" type="string" column="LABEL_OBJECT_1" length="15" />
</class>
</hibernate-mapping>
Object2.hbm.xml :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping default-lazy="true">
<class name="com.omb.database.mapping.Object2" table="TABLE_OBJECT_2">
<id name="id" type="java.lang.Integer" column="OBJECT2_ID" />
<property name="label" type="string" column="LABEL_OBJECT_2" length="15" />
</class>
</hibernate-mapping>
Query HBM :
public List<Entity> findByObject1Id(Integer object1Id) throws DataAccesException {
List<Entity> results = null;
try {
Query query = this.getSession().createQuery(
"from Entity ent where ent.object1.id = :object1Id");
query.setParameter("object1Id", object1Id);
results = query.list();
} catch (HibernateException hbe) {
throw new DataAccesException(hbe);
}
return results;
}
in pom.xml
<!-- Hibernate 3 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.6.ga</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
<exclusion>
<groupId>asm</groupId>
<artifactId>asm-attrs</artifactId>
</exclusion>
</exclusions>
</dependency>

Your mapping seems to be ok. As documented here 5.1.1. Entity
The <class> attribute lazy is by default true
lazy (optional): lazy fetching can be disabled by setting lazy="false".
The same for <many-to-one>: 5.1.7.1. Using a foreign key or an association table lazy attribute:
lazy (optional - defaults to proxy): by default, single point associations are proxied. lazy="no-proxy" specifies that the property should be fetched lazily when the instance variable is first accessed. This requires build-time bytecode instrumentation. lazy="false" specifies that the association will always be eagerly fetched.
So, where is the issue?
I would say in your debug window. Because you do have a reference to your list, and you are watching the result - in the moment it is executed - the reference is also loaded. Lazily - but loaded. That's in fact what we want. Proxy - when firstly tuched - is forcing the load.
Try to remove it from watch. Or close the session and then put it in the watch... You should see, taht the query used above - is not loading references... only when really accessed... even via debug window

Did you try with FetchMode.SELECT, like this;
<many-to-one name="object1" column="OBJECT1_ID" class="com.omb.database.mapping.Object1" fetch="select" />

Related

Version filed in many-to-one relation causes object references an unsaved transient instance

i have a class like this :
public class User(){
private String name;
private Integer version ;
//getter & setter
}
and the hibernate mapping file is :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="org.model.User" table="User">
<id name="id" column="Id" type="java.lang.Integer">
<generator class="sequence" >
<param name="sequence">SEQ_User</param>
</generator>
</id>
<version name="version" column="version" type="Integer" />
<property name="name" column="Name" type="string" not-null="true" />
</class>
</hibernate-mapping>
when i use as a many-to-one relation like this :
public class UserDetail(){
private String tel;
private User user;
// getter & setter
}
and this is UserDetail.hbm.xml file :
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="org.model.UserDetail" table="User_Detail">
<id name="id" column="Id" type="java.lang.Integer">
<generator class="sequence" >
<param name="sequence">SEQ_User_Detail</param>
</generator>
</id>
<property name="tel" column="tel" type="string" not-null="true" />
<many-to-one name="user" column="user_Id" entity-name="org.model.User" />
</class>
</hibernate-mapping>
when i want to save the UserDetail take this exception :
object references an unsaved transient instance - save the transient
instance before flushing
i save the UserDetail object in the way below :
#Override
public void add(UserDetail entity) {
Session session = getSession();
session.save(entity);
}
and i set User in the UserDetail like this :
entity.setUser(new User(1));
i just know the UserId and never load the user object.
after i debug if we put the version field Null in User i get the exception but when i set it with zero the problem is solved
Although you haven't added the code for UserDetails and the persistence logic, I suppose you have a #ManyToOne association to a User in UserDetails.
If you have a User and a UserDetails that you want to persist, you have to call persist() on both entities. Unless you have a parent-side association in User, you cannot use cascading since cascade makes sense only from parent to children but not vice versa.
This has nothing to do with concurrency or versioning as you initially suspected.
You said that:
I don't have any problem when i remove version field
That could be explained if you set an id to a transient User instance and you try to call persist():
User user = new User();
user.setId(1L);
user.setVersion(0);
session.persist(user);
Because you have a sequence identifier generator, it is Hibernate that should assign identifiers. Also, the version property should never be assigned manually. If you have a detached entity, you need to call merge instead of persist.
Update
According to your update and the new source code, I can't find any particular issue. However, you should know that the Session might have been opened before and maybe there are other entities that you added previously to creating the User entity. It's only during the flush that entity state transitions are propagated to the DB.

Hibernate one-to-one XML mapping

I found the following links:
Hibernate one to one mapping problem
Hibernate one-to-one mapping with a reference column (XML mapping)
hibernate one-to-one (on foreign key) vs one-to-one (on primary key)
But nothing seems to work.
I have 2 entities:
class User {
Integer userId;
Profile userProfile;
}
class Profile {
Integer profileId;
User user;
}
With XML mapping:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="model.User" table="User" catalog="Proj1" dynamic-update="true">
<id name="userId" type="java.lang.Integer">
<column name="userId" />
<generator class="identity" />
</id>
<one-to-one name="userProfile" class="model.Profile">
</one-to-one>
</class>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated Jun 12, 2013 7:51:22 PM by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping>
<class name="model.Profile" table="Profile" catalog="Proj1" dynamic-update="true">
<id name="profileId" type="java.lang.Integer">
<column name="profileId" />
<generator class="identity" />
</id>
<many-to-one name="user" class="model.Users" unique="true">
<column name="userId" />
</many-to-one>
</class>
</hibernate-mapping>
The thing here is, the User must have exactly one Profile but the Profile doesn't necessarily to have one User so a Profile may have null User.
Now the problem is every time I fetch a User with its associated Profile, the Profile retrieved is the Profile with the profileId the same as userId, that is if the User has userId 4 the Profile retrieved is the profile with profileId 4 as well even though it is supposed to retrieve Profile with userId 4 not profileId 4.
Update: add Dao code
public User findById( int id ) {
log.debug("getting User instance with id: " + id);
try {
Criteria userCriteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);
userCriteria.add(Restrictions.idEq(id));
userCriteria.setFetchMode("userProfile", FetchMode.JOIN);
userCriteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
Users instance = (Users) userCriteria.uniqueResult();
if(instance == null)
log.debug("get successful, no instance found");
else
log.debug("get successful, instance found");
return instance;
}
catch(RuntimeException re) {
log.error("get failed", re);
throw re;
}
}
Finally I found the solution. Originally I had to set the userProfile manually every time I need to fetch the associated userProfile of a User just for a temporary workaround. But I just found this link: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/associations.html#assoc-bidirectional-121
So basically I just have to add unique="true" not-null="false" to the many-to-one of the user in Profile xml and add property-ref="user" to the one-to-one userProfile in User. I think the key here is the property-ref="user"

how to avoid getting javassist lazy Entity proxy instances in Hibernate

What do I have to change to avoid Hibernate giving me lazy javassist instance proxies rather than the true entity?
UPDATE: I am using Spring 3.x and Hibernate 4.x
The API I am using to load the entity is org.hibernate.internal.SessionImpl#load(Person.class, Id) and the mapping simply:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.perfectjpattern.example.model">
<class name="Person" table="PERSON_" >
<id name="id">
<generator class="native"></generator>
</id>
<property name="name" update="false" />
<property name="age" update="true" />
</class>
<query name="Person.findByName">
<![CDATA[select p from Person p where p.name = ? ]]>
</query>
<query name="Person.findByAge">
<![CDATA[select p from Person p where p.age = :Age ]]>
</query>
</hibernate-mapping>
Use get() rather than load().
You can use Hibernate.initialize(obj) after session.load(id).
This method can instantly initialize your obj.
Actually solved it by simply changing the mapping to (see the default-lazy="false"):
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.perfectjpattern.example.model" default-lazy="false">

Hibernate Joins using Criteria

I am trying to use Hibernate to access persisted data for our rights management, but I am very new to it and struggling to get the data I need.
I have Users table, with an ID and name, a Groups table with an ID and name, and a User/Groups mapping which just consists of the group ids and user ids that are linked.
What I want to do is get all the names of the members of a given group, so the standard SQL query I want to execute is this:
SELECT
u.NAME
FROM
USERS u
JOIN
GROUP_USERS gu
ON
u.ID = gu.USER_ID
JOIN
GROUPS g
ON
gu.GROUP_ID = g.ID
WHERE
g.NAME = 'test'
But despite hours of looking and playing I cannot seem to get anywhere.
I want to use Criteria as they seem clearer, so my code is as follows:
#Override
public final List<String> getGroupMembers(final String groupName) {
#SuppressWarnings("unchecked")
List<User> groupUsers = getHibernateTemplate().execute(new HibernateCallback<List<User>>() {
#Override
public List<User> doInHibernate(Session session) throws HibernateException, SQLException {
Criteria criteria = session.createCriteria(User.class)
.setFetchMode("GroupUsers", FetchMode.JOIN)
.setFetchMode("Group", FetchMode.JOIN)
.add(Restrictions.eq("name", groupName));
return criteria.list();
}
});
List<String> groupUsernames = new ArrayList<String>();
for (User groupUser : groupUsers) {
groupUsernames.add(groupUser.getName());
}
return groupUsernames;
}
But when I test it the result set is empty, and according to the logs the executed query is this:
select this_.ID as M1_4_0_, this_.NAME as M2_4_0_ from USERS this_ where this_.NAME=?
I let Hibernate create the tables using hibernate.hbm2ddl.auto, but then removed it so that the tables are definitely as hibernate expects, but the data is not being cleaned.
Any help with the Criteria would be greatly appreciated, so thanks in advance!
Edit: I am using xml mapping files:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.company">
<class name="com.company.Group" table="GROUPS">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME" type="java.lang.String" unique="true" not-null="true"/>
</class>
</hibernate-mapping>
and
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.company">
<class name="com.company.GroupUsers" table="GROUP_USERS">
<composite-id>
<key-many-to-one name="groupId" class = "Group" column="GROUP_ID" />
<key-many-to-one name="userId" class = "User" column="USER_ID" />
</composite-id>
</class>
</hibernate-mapping>
and
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.company">
<class name="com.company.User" table="USERS">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME" type="java.lang.String" unique="true" not-null="true"/>
</class>
</hibernate-mapping>
You don't need to map the user-group table, just define your relation between User and Group as a many-to-many relation, take a look at this and this about how to map many-to-many relations.
In your case, it will look like:
<hibernate-mapping package="com.company">
<class name="com.company.User" table="USERS">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME" type="java.lang.String" unique="true" not-null="true"/>
<many-to-many name="userGroups"
target-entity="com.company.Group">
<join-table name="YOUR_USER_GROUP_TABLE">
<join-column name="USER_ID" />
<inverse-join-column name="GROUP_ID" />
</join-table>
</many-to-many>
</class>
And to filter your users using the name field from the Group entity for example:
Criteria criteria = session.createCriteria(User.class);
criteria.createAlias("userGroups", "usrGrp",CriteriaSpecification.LEFT_JOIN);
criteria.add( Restrictions.eqProperty("usrGrp.name", "test") )
The way you map your object to tables does not benefit from the usage of Hibernate as ORM. Consider a more object-oriented model, eg:
class Group {
private Set<User> users;
// ...
}
class User {
private Set<Group> groups;
//..
}
So, you've got a classical many-to-many association. Here you can find an example of such mapping with Hibernate.

Hibernate: How to model a double parent/child relationship

I got a pretty simple parent/child relationship here, which looks like this:
Email servers have n folders.
Folders can have n (sub-)folders.
Folders have a reference to their parent folder as well as to the email server they belong to.
My mapping files look like this:
MailServer.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 02.05.2011 12:32:52 by Hibernate Tools 3.3.0.GA -->
<hibernate-mapping>
<class name="test.MailServer" table="MAILSERVER">
<id name="id" type="long" access="field">
<column name="MAIL_SERVER_ID" />
<generator class="native" />
</id>
<bag name="folders" table="FOLDERS" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="MAIL_SERVER_ID"></key>
<one-to-many class="test.Folder" />
</bag>
</class>
</hibernate-mapping>
Folder.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 04.05.2011 15:02:31 by Hibernate Tools 3.3.0.GA -->
<hibernate-mapping>
<class name="test.Folder" table="FOLDERS">
<id name="id" type="long" access="field">
<column name="FOLDER_ID" />
<generator class="native" />
</id>
<many-to-one name="mailServer" column="MAIL_SERVER_ID" not-null="true" />
<bag name="folders" table="FOLDERS" lazy="false" inverse="true" cascade="all">
<key column="PARENT_FOLDER_ID" not-null="false"></key>
<one-to-many class="test.Folder" />
</bag>
<many-to-one name="parentFolder" column="PARENT_FOLDER_ID" />
</class>
</hibernate-mapping>
The problem I got, is the following.
Let's say, I have the following hierachy:
- MyMailServer
Folder1
- Folder2
Subfolder
Folder3
When I call hibernateSession.save(mailServerInstance); or hibernateSession.update(mailServerInstance);, Hibernate correctly stores everything to the database. The parent column id's are filled correctly. Same for all other references.
BUT when I load the data, Hibernate reloads the folder hierachy like this:
- MyMailServer
Folder1
Folder2
Subfolder
Folder3
I understand the reason: Subfolder has a reference to its MailServer and thus, Hibernate ads it there instead of the folder where it belongs to.
But, how do I solve this problem?
Thanks in advance!
As you already understood what Hibernate does here is logical since you're fetching all folders for given mailServer. I don't think there is a way to achieve what you want in 1 single query (not a hibernate restriction, also with plain old SQL this is not possible).
I have a very similar case where I do the following:
get root folders for mail server (-> where parent folder is null)
for each folder get the child folders
Another solution is using Transfer Objects and you map the entities to the transfer objects so that the required structure is obtained.
It all depends on your use case which (if any) solution is appropriate. E.g. if you can execute an AJAX call each time a folder is expanded (tree structure) then the first solution is ideal.
I worked around this by replacing
<bag name="folders" table="FOLDERS" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="MAIL_SERVER_ID"></key>
<one-to-many class="test.Folder" />
</bag>
in MailServer.hbm.xml with
<one-to-one name="rootFolder" class="test.Folder" cascade="all" />
That did the trick.

Categories