A Completed Example for Hibernate

A
Completed Example for Hibernate

In the
following example, I am trying to create a new trial user and create a default
subscription for it based on Hibernate lazy initialisation. In the first part, I
would like to HibernateTransactionManager to look after the transaction, so one
of the Hibernate actions fails will trigger the rollback for the entire
transaction.

    @Transactional

    public UserMain
createTrialUser(TrialForm form) throws Exception {

        //create new trial
user account

        UserMain userMain
= new UserMain();

       
userMain.setFirstname(form.getFirstName());

       
userMain.setLastname(form.getLastName());

       
userMain.setUpdatedAt(new Date());

        if
(userMain.getCreatedAt() == null) {

           
userMain.setCreatedAt(new Date());

        }

        //save the new
user and make it persistent

       
getSession().saveOrUpdate(userMain);

 

        //save the email
information for the user    

        String
emailaddress = form.getEmail();

        if
(StringUtils.isNotEmpty(emailaddress)) {

           
createAssociatedContact(userMain, emailaddress);

        }

        //update the
associate

       
getSession().saveOrUpdate(userMain);

        return userMain;

}

Code 1

I
am using the annotation declaration for using the HibernateTransactionManager
in the preceding code. In order to make it work, I also need to declare it in
the context file.

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot;

            xmlns:tx=http://www.springframework.org/schema/tx

           

…>

<!– use declarative transactions
@Transactional etc –>

<tx:annotation-driven
transaction-manager="transactionManager" />  

 

 <!– Transaction manager for a single
Hibernate SessionFactory (alternative to JTA) –>

 <bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">

      
<property name="sessionFactory"
ref="sessionFactory"/>

 </bean>

Code 2

In
Code 1, You can see that getSession().saveOrUpdate(userMain) method has been
called twice. The first time, it was called to make the userMain object
persistent, which means the object will have a projection in the database and
an id will be assigned to the object. You have to do this at least before calling
to saveUserTypeDomain(userMain).  The
reason is before some query executions, the Session will execute the SQL
statements needed to synchronize the JDBC connection’s state with the state of
objects held in memory.  This process
called flush. As you can see in code 3, the method is calling a SQL query via
criteria. Any transient object should be saved before the query is called.
Otherwise you would get an “org.hibernate.TransientObjectException:
object references an unsaved transient instance”.

private void saveUserTypeDomain(UserMain userMain) {

    UserType userType =
getUserType("Single");

    UserTypeDomain
userTypeDomain = new UserTypeDomain();

   
userTypeDomain.setDomain("Test");

   
userTypeDomain.setUserMain(userMain);

   
userTypeDomain.setUserType(userType);

    getSession().save(userTypeDomain);

}           

 

public UserType getUserType(String userType){

return (UserType)
getSession().createCriteria(UserType.class)

.add(Restrictions.eq("userTypeDesc",
userType)).uniqueResult();

}

Code 3

What
transient object that should be saved? Let’s look at the method
createAssociatedContact(), which is called just before saveUserTypeDomain().
You probably could identify that method userContact.setUserMain(userMain) is
actually take userMain as an transient object (not attached with session yet).  So, that is clear. The userMain object is the
one you need to save and that has to be done before the call to
saveUserTypeDomain()

private void createAssociatedContact(UserMain userMain, String
emailaddress, UserMain creatorUserMain) {

            //create a new contact    

        Contact contact =
new Contact();

       
contact.setData(emailaddress);

       
contact.setCreatedAt(new Date());

        //save the
contact. Be aware of lazy initilisation

       
getSession().save(contact);

 

            //create a new
UserContact

        UserContact
userContact = new UserContact();

//make sure you userMain object is
persistent and has an identifier

       
userContact.setUserMain(userMain);

       
userContact.setContact(contact);

        userContact.setCreatedAt(new
Date());

       
userContact.setUpdatedAt(new Date());

        //save it

       
getSession().save(userContact);

 

            //assign the
value of UserContact to the user

       
Set<UserContact> contactSet = new HashSet<UserContact>();

        contactSet.add(userContact);

       
userMain.setUserContacts(contactSet);

}

   

Code 4

Make sure you
have saved the object individually as they are all lazy initialized and you
could not modify them in cascaded style until they are attached (via save or
update) to the session. You also need to setUserContacts(), if you need to use
this userMain object/instance to get the UserContacts property in the future,
but it wouldn’t cause problem in the database side, as the tables for
UserContacts  and UserMain (the second
call of getSession().saveOrUpdate(userMain)) will be saved or updated
separately.  

Now we come
to the second part, create a default subscription for the new trial user. I
guess there are not much hassles about how to run procedure.

public void subscribeUserToOffer(UserMain userMain) {

 

        List<Long>
selectedOffers = new ArrayList<Long>();

        SubscriptionOffer
offerIdGold = getSubscriptionOffer("Gold_Memember");

       
if(offerIdGold!=null){

           
selectedOffers.add(offerIdGold.getSubscriptionOfferId());

        }

 

        Connection
connection = null;

        try {

            connection =
getHibernateTemplate().getSessionFactory()

.openStatelessSession().connection();

            for (Long
selectedOfferId : selectedOffers) {

               
CallableStatement callableStatement

                        =
connection.prepareCall("{ call
proc_create_user_subscription(?,?,?,?)}");

               
callableStatement.setObject("in_user_main_username",
userMain.getUsername());

               
callableStatement.setObject("in_subscription_offer_id",
selectedOfferId);

               
callableStatement.setObject("in_subscription_type",
"Trial");

               
callableStatement.setObject("in_subscription_end_accessdate",
getEndAccessDateFromTheDurationEntered(new Long("14")));

               
callableStatement.registerOutParameter("result_code",
java.sql.Types.INTEGER);

                ResultSet
resultSet = callableStatement.executeQuery();

                Long resultCode = new
Long(callableStatement.getLong("result_code"));

               
resultSet.close();

                //whether
should run the following code also depends on

               //the resource configuration in the server,
ie defaultAutoCommit="true"

                //connection.commit();

            }

        } catch
(SQLException e) {

            //copied from
usertools. not sure why doing this.

            //it will be
caught later anyway as a failure of creating a new trial user

           
e.printStackTrace();

            org.hibernate.HibernateException hE =
new HibernateException(e);

            throw
SessionFactoryUtils.convertHibernateAccessException(hE);

        } finally {

            if (connection
!= null) {

                try {

                   
connection.commit();

                   
connection.close();

                } catch
(SQLException ignore) {

                }

            }

        }

}

Code 5

What
I would like to stress here is where should this method be called. Inside createTrialUser()?
You might think if you called this subscribeUserToOffer()  method inside createTrialUser(), if the
procedure fail, the transaction manager can just roll it back. Well, I actually
did think like that. However, this could be a mission impossible. The procedure
is totally separate thing from the transaction. In the situation when the
procedure is called, and it needs to call some of the table that has been
update by the earlier Hibernate action (such as, adding new user contact), it would
be no wonder if it just throws you an exception. The procedure would not be
able to call the synchronised tables until the transaction is committed, while
the procedure itself is called within the transaction.
There
is an alternation if you put getSession().flush() before you call the procedure
within the transaction. (No recommended, as it is more like a separate
function).
 Flush() will synchronize
the JDBC connection’s state with the state of objects held in memory.

One
last thing to mention is whether
connection.commit() should be
called in the procedure. If the resource value in the context file of your application
server has configured to
defaultAutoCommit="true", you should
never call the manually call the commit again, as commented in the code 5.

 

Reference:

http://docs.jboss.org/hibernate/core/3.3/reference/en/html/objectstate.html#objectstate-flushing

 

Advertisements
This entry was posted in Hibernate. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s