Hello!
I'm working on an application which are supposed to be a database back end for a web and mobile application.
I have three main classes which are saved to the database; User, Session and Location.
A User can have an arbitrary number of sessions, and each session can have an arbitrary number of locations.
Beyond this, none of them are connected in any way.
So a User can have many Sessions, and each Session can have many Locations.
I have created the mappings and all works well. I can add sessions to a user and locations to the session, and they all get saved to the database.
No, the problem is that I don't want to fetch everything associated with a User when I get a User from the database. For example, a Session can have thousands of locations, which I only want to fetch when I actually need them, and that's why I use the lazy initialization method.
Say I have fetched a user, and closed the session associated with the transaction, effectively blocking any attempts of fetching the Sessions belonging to the User. How can I fetch these sessions on demand?
Here is my fetch user function:
Code:
public User getUser( Long id ) {
if ( id == null ) {
throw new IllegalArgumentException("userId is null");
}
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
User u = null;
try {
transaction = session.beginTransaction();
u = (User) session.get(User.class, id);
transaction.commit();
} catch( RuntimeException re ) {
if ( transaction != null && transaction.isActive() ) {
try {
transaction.rollback();
} catch( HibernateException he ) {
logger.debug("Could not rollback transaction.");
}
}
throw re;
} finally {
session.close();
}
return u;
}
I have come up with a few solutions, but I don't like either of them.
1. create another function which uses
Hibernate.initialize( u.getSessions() ); to get the session set before returning the User
2. send the user object to a function (initializeSessions) which fetches the User again with the function described i 1, and use the setSessions() on the object passed to the function with the getSessions() from the newly fetched user - this removes the need of the caller having to change it's user pointer
What I want is 2. but without the need of fetching the user, but only fetch the Sessions and add them to the already existing User object.
I tried to open a new hibernate session and transaction and call
Hibernate.initialize( u.getSessions() ); but that gave me the "collection is not associated with any session" exception, see below:
Code:
public void initializeSessions( User u ) {
org.hibernate.Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Hibernate.initialize(u.getSessions());
tx.commit();
} catch( RuntimeException e ) {
if ( tx != null && tx.isActive() ) {
try {
tx.rollback();
} catch( HibernateException he ) {
logger.error("Could not rollback transaction.");
}
}
throw e;
} finally {
session.close();
}
}
Is there any good way of doing this? I would like to have this on the Session to Locations as well with a function called
initializeLocations(Sesssion s);Here's the hbm.xml files for the three classes User, Session and Location:
Code:
<?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="no.sygard.tracking.manager.User" table="users">
<meta attribute="class-description">
This class contains the user details.
</meta>
<id name="userId" type="long" column="userId">
<generator class="native"/>
</id>
<property name="username" type="string" column="username" not-null="true" unique="true" />
<property name="password" type="string" column="password" not-null="true" />
<property name="email" type="string" column="email" not-null="true" unique="true" />
<property name="created" type="timestamp" column="created" not-null="true" />
<property name="imageUrl" type="string" column="imageUrl" not-null="true" />
<property name="apiKey" type="string" column="apiKey" not-null="true" unique="true" />
<property name="lastLogin" type="timestamp" column="lastLogin" not-null="true" />
<property name="lastLoginIp" type="string" column="lastLoginIp" not-null="true" />
<property name="lastApiLogin" type="timestamp" column="lastApiLogin" not-null="true" />
<property name="firstname" type="string" column="firstname" not-null="true" />
<property name="lastname" type="string" column="lastname" not-null="true" />
<set name="sessions" table="user_session" cascade="all">
<key column="userId" />
<many-to-many column="sessionId" unique="true"
class="no.sygard.tracking.manager.Session" />
</set>
</class>
</hibernate-mapping>
Code:
<?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="no.sygard.tracking.manager.Session" table="sessions">
<meta attribute="class-description">
This class contains the session details.
</meta>
<id name="sessionId" type="long" column="sessionId">
<generator class="native"/>
</id>
<property name="name" type="string" column="name" not-null="true" />
<property name="description" type="string" column="description" not-null="true" />
<property name="created" type="timestamp" column="created" not-null="true" />
<property name="active" type="boolean" column="active" not-null="true" />
<set name="locations" table="session_location" cascade="all">
<key column="sessionId" />
<many-to-many column="locationId" unique="true"
class="no.sygard.tracking.manager.Location" />
</set>
</class>
</hibernate-mapping>
Code:
<?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="no.sygard.tracking.manager.Location" table="locations">
<meta attribute="class-description">
This class contains the location details.
</meta>
<id name="locationId" type="long" column="locationId">
<generator class="native"/>
</id>
<property name="latitude" type="double" column="latitude" not-null="true" />
<property name="longitude" type="double" column="longitude" not-null="true" />
<property name="speed" type="double" column="speed" not-null="true" />
<property name="altitude" type="double" column="altitude" not-null="true" />
<property name="accuracy" type="double" column="accuracy" not-null="true" />
<property name="bearing" type="double" column="bearing" not-null="true" />
<property name="provider" type="string" column="provider" not-null="true" />
<property name="time" type="timestamp" column="time" not-null="true" />
</class>
</hibernate-mapping>
If anyone have any good solutions or just say that it cannot be done, I would really appreciate it !