Hibernate Formula Annotated Field Behaviors

When the @Formula annotation is used to create a virtual column (in object model but not in database), developers should be aware of some behaviors to avoid of any pitfalls. This post uses the source code here to demonstrate these behaviors.

Background
As we know, Hibernate works on sessions. When an entity object is attached to a session, it becomes a persistent instance cached by the L1 cache. L1 cache is a build-in cache that can’t be disabled or configured. Whenever we access a persistent instance, we are in fact retrieving it from the cache. Therefore, unless being cleared or evicted, we are always seeing the objects from the cache, which is not guaranteed to be same as the data persisted in the database.

Developers can instruct Hibernate to persist the object data into database. You can call session.flush() or transaction.commit() to write data into tables. After flush() is called, you can still call session.rollBack() to revert the change. In contrast, commit() will terminate the transaction and is only supposed to be invoked at the end of the unit work. Therefore, if your code is part of a bigger transaction, you’d better not to commit() the transaction to avoid data integrity problem. However, you may want to call flush() to temporarily store the data, which can be reverted or made permanent at the decision of the bigger scope owners. One may argue that flush() could make the temporary data visible to other sessions to leak data prematurely. But this in not a concern if your database server can lock uncommitted data from being seeing by others.

In most situations, developers may not care about this because Hibernate does all the hardwork to make our life easier, especially when within the same session, where the L1 cache is scoped. However, if the instance has derived properties, for example, calculated by @formula, it can become very complicated. Let’s examine a few cases.

Case#1: Behavior after saving a new instance

    Test cases afterSave() and afterSaveFlush() create a persistent Employee instance e1. Then when the derived property fullName is accessed, it returns null. Notice that the non-derived property firstName and lastName are accessible at this point.
    Test case afterSaveRefresh() calls refresh() after save and it updates the cached fullName.

This case can be visualized by the following diagram. Save() only updated the L1 cache with the non-derived properties. When refresh() is called at this point, Hibernate will calculate the derived property fullName based on the given formula. Please refer to the left side for right side for the cases without and with refreshing.

saveRefresh

Case#2: Behavior after flushing/commiting

    Test cases afterSaveFlush() and afterSaveCommit() show the non-derived data has been synchronized into the database from the L1 cache. However, the derived property is still null.
    Test case afterSaveFlushRefresh() shows the derived property fullName is populated.

This case can be visualized by the following diagram

saveFlushRefresh

Case#3: Behavior after updating

    Test case afterSaveRefreshUpdate() shows the non-derived lastname cache is updated by the second save() but not the derived fullName.
    Test case afterSaveRefreshUpdateRefresh shows the cached value of lastName (“Black”) is overwritten by the calling refresh() and the new value is changed back to “Smith” from the database.

This case can be visualized by the following diagram

saveFlushUpdate

Advertisements
This entry was posted in Database, Java and tagged , , , . 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