Tuesday, February 27, 2007

Hibernate: Annotation many-to-one(foreign-key)

Hibernate Doc (Chap 8.2.1)


::Relationship::
person(many) -> address(one)


::DB Schema::
person( personId, addressId )
address( addressId )


::Java Operation::
person.getAddress();


::Annotation::
@Entity
@Table(name="PERSON")
public class Person {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;

@ManyToOne
@JoinColumn(name = "ADDRESS_ID")
private Address address;

public Address getAddress() {
return address;
}
}

@Entity
@Table(name="ADDRESS")
public class Address {   

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
}


[Association Mapping List]

Thursday, February 15, 2007

Hibernate: When is "inverse=true" and when it's not?

When I was a hibernate beginner, I was confused about deciding "inverse=true" or "inverse=false".

Here is an easy way to understanding it:

example scenario:
Person(one) <-> Address(many)
* bi-directional one-to-many relationship. (A person has multiple addresses.)


public class Person {
private Integer id;
private Set<Address> addresses;

// setter, getter
Set<Address> getAddresses() { return addresses; }
....
}


public class Address {
private Integer id;
private Person person;

// setter, getter
Person getPerson() { return person; }
.....
}
  • Person class has "Set<Address> getAddresses()" method
  • Address class has "Person getPerson()" method

If you think about the relation between two classes, it may gives an idea that person has addresses. (Person -> Addresses)
So, it feels like a person is an owner, and an address is a child. Then, you want to think that address is "inverse=true" because address is owned by person.
However, it's not correct.


Here, I'd like to suggest a way to think about it.

Let's look at table structure instead of classes.
  • PERSON[ id, name, ...]
  • ADDRESS[ id, person_id, city, street,...]

The person_id column in Address table is the relational information between thease two tables.
So, Address is an owner of the relationship, and Person is the inverse side.
"inverse=true" means "this side is the inverse side", and "inverse=false" means "this is not the inverse side. this side is the owner of the relationship".


Answer is:

<class name="Person">
<id name="id">...</id>
<set name="addresses" inverse="true">
<key column="person_id"/>
<one-to-many class="Address"/>
</set>
</class>


<class name="Address">
<id name="id">...</id>
<many-to-one name="person" class="Person" />
</class>



In sum, look at the table structure to distinguish "inverse=true" or "inverse=false".
If the table has relational information, then it is the owner side. So, it's not inverse side.(inverse=false)
If the table doesn't have relational information, then it is the inverse side. So, it needs "inverse=true".

Sunday, February 11, 2007

Hibernate: Single Shot Query to get Count and Data by ScrollableResults

Pagination is one issue when you display list-style data.
Usually it requires two kinds of data: "certain range of objects"(offset, limit) and "total number of data"(count).

There is a couple of ways to implement this.

(1) Use two queries. one for total number(count) and one for ranged data(offset,limit).
(2) Load all data into java layer, then check the size of list.

(2) is not practical if you have large number of data and displaying only a few of them, it has huge overhead.
So, I have seen couple of projects using (1) style to display list of data and total number of them.

For retrieving ranged data, hibernate provides "setFetchSize(int)" and "setFirstResult(int)" to specify offset and limit.
This way, application only instantiate ranged return objects.
Also, these two methods separate pagination information from hql and absorbes vendor specific offset and limit SQL.

For example, HQL that retrieves all active users is :
"from User where active = true"
you may easily retrieve ranged users from offset 10 to 30, 30 to 50...

Now you need to retrieve total number of active users in order to display how many pages it has.
For total number of active users, you may use this hql.
"select (*) from User where active = true"

Then, how do you manage those almost similar two queries?

From my experience, some of the applications use string manipulation that dynamically replace or add select clause to HQL statement.
(Adding "select count(*)" is easy to do because hql statement can start with from-clause. )
But replacing may not easy if the hql already has select clause. "select id, name from User where active = true".
You can write some methods to replace it but the source code may not seem neat.

Some applications prepare two hql statements, one for total number and the other for data.


If your JDBC happily supports scrollable result set, here is one way to retrieve count and data by single shot query.


public ResultList scrollCount(final String hql, final int scrollOffset,
final int scrollSize) {
return (ResultList) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {

Query q = session.createQuery(hql);
ScrollableResults rs = q.scroll(ScrollMode.FORWARD_ONLY);

rs.beforeFirst();
rs.scroll(scrollOffset);

ResultList resultList = new ResultList();
for (int i = 0; i < scrollSize && rs.next(); i++) {
Object[] results = rs.get();
// flatten only if one object per row
resultList.add(results.length == 1 ? results[0]
: results);
}

rs.last();
final int total = rs.getRowNumber() + 1; // start with -1
resultList.setTotalSize(total);

return resultList;
}
});
}



public class ResultList extends ArrayList {
private int totalSize;

public int getTotalSize() {
return totalSize;
}

public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
}


You can put it into your dao class, probably in generic dao, base dao, abstract dao...

So, now you don't need to do any string manipulation.
I'm not sure about the cost of moving result set.
But single query can retrieve required two data.
From maintaining source code view, it also reduces duplication.

Friday, February 9, 2007

Hibernate: tune up Named Query initialization time

Our application uses Hibernate with many named queries.
Whenever I start up jboss, parsing the named queries is one of the time consuming initialization process.

I read a post on hibernate JIRA regarding preparing named query.
It says, instead of writing named queries in mapping files, creating named-query-only-files gives faster startup.

So, I tried it.

Hiberante3 & Jboss4
Num of NamedQuery: 405

Named queries exist within entity mapping(our current status):


start up time
1.
2m:33s:301ms
2.
2m:15s:737ms
3.
2m:25s:113ms
4.
2m:15s:753ms
5.
2m: 1s:706ms
6.
2m: 2s:941ms
7.
2m:19s:722ms
8.
2m: 8s:769ms
9.
2m:18s:473ms
10.
2m:14s: 82ms
AVG
2m:15s:633ms


Moved approximately half of named query to a query-only-file.
(moving all queries takes time, so I just slacked…)



start up time
1.
1m:52s:300ms
2.
2m: 7s:191ms
3.
2m:11s:566ms
4.
2m: 7s:581ms
5.
1m:51s:799ms
6.
2m:19s:613ms
7.
1m:49s:612ms
8.
2m: :675ms
9.
1m:58s: 50ms
10.
2m:10s:675ms
AVG
2m: 2s:951ms


::Result::
Average start up time : 2m:15s:633ms => 2m: 2s:951ms


Well, start-up time varies. But surely it makes startup faster.
If I move all named-queries to a couple of query-only-files, we will gain more performance.

Monday, February 5, 2007

Hello World!!

Well, here is my first step for my blog.
Who am I?
I'm a software developer who loves to create application very nice and clean.
What makes application nice and clean? My opinion is make its architecture simple and standard.
I have experienced many applications that some guys created with wrong use of technologies.
Accessing database very aggressively in taglib, putting many objects into HttpSession, giviing HttpRequest object to DAO layers, creating own wicked web action handling framework using opensource web frameworks, and unfortunately more and more...
Well, I'm not always correct but at least let's create maintainable and understandable application, and let's make other developers happy to develop it!!
For this blog, I don't know what kind of topic I'll post yet, but I'm hoping I can share my knowledge to other developers.
Lastly, English is my second language. So, bear with me when my writing sounds awkward!!
Thank you for reading!!