Sizing Up Open Source Java Persistence : Page 4

Confused and puzzled by the plethora of persistence options in Java? You are not alone. Examine how some popular open source persistence frameworks stack up against one another and JDBC.

by Jim White

Feb 15, 2007

Page 4 of 6

Performance
Performance and scalability are like the good life. Everyone wants a lot of it, but what are you willing to give to get it? Therefore, when looking at performance figures for each of the frameworks, this is "out of the box" performance for the standard CRUD types of operations defined in the DAO interface defined above (see the first code example).

Many things can impact performance. The object property types, database indexes, database driver, etc. can all have huge impacts. Furthermore, features in each of the frameworks are provided to assist with certain performance issues. For example, many of the frameworks offer cache that can greatly improve performance for objects that are requested over and over again. So, a huge footnote to the numbers given here is that these performance results should be seen as a rough gage and basis for general performance comparison. There is little doubt that by fine-tuning and using special features of the framework, the results would vary. However, to improve the results would also require more time and expertise. They may also lock one into a unique feature of the framework that would not be supported or supported differently in another framework.

Editor's Note: The benchmarks cited in this article were created by the author, who has made every effort to ensure that his methodology is fair and accurate. You should consider the findings here to be representative only. Other independent benchmarks will inevitably have slightly different results.

When examining performance to save and read back simple Java objects with no relationships to other objects, the frameworks all compared well to JDBC. Table 3 shows the averages from conducting 10 tests of each operation.

Framework ▼

Operation ►

Create 100 Simple
objects and persist them

Read 100 Simple
objects

Hibernate

360

295

JDBC

410

45

iBatis

460

210

JDO

560

325

Castor

620

480

Table 3. Persistence Framework Performance on Simple Objects: The time, in milliseconds, for each of the frameworks to perform the operations listed.

Things get a little more interesting when you look at operations on complex object graphs. Table 4 shows the time, in seconds, for each of the frameworks to perform the operations listed. These are averages from conducting 10 tests of each operation. The asterisk (*) next to the Castor retrieval of 100 employees result is for retrieving 10 employees and not 100! Castor also presented some problems when saving/updating/removing which will be addressed later in this article.

Framework

▼

Read 5K Customers
from the DB into objects

Read 5K Employees
from the DB into objects

retrieve 100
Employees from the DB into objects by id

Create and Insert 10
Employee object graphs & persist to the DB

Update 10 Employee
objects and persist to the DB

Update and persist
10 Address objects associated to employees

Remove 10 employees
and assoc objects (cascade delete)

operation►

findAllCustomers

findAllEmployees

findOneEmployee

saveEmployee

saveEmployee

saveEmployee

deleteEmployee

JDBC

0.37

0.51

0.59

0.73

0.58

0.28

2.25

iBatis

0.62

0.77

0.33

0.9

0.55

0.2

0.71

Hibernate

3.14

8.09

0.56

0.67

0.7

0.41

0.79

JDO

3.5

5.64

1.34

1.23

1.17

0.71

1.39

Castor

6.72

11.07

52.21*

Table 4. Persistence Framework Performance: The time, in seconds, for each of the frameworks to perform the operations listed.

When retrieving objects, all the associated objects were eagerly retrieved (versus lazy loading, which is covered later in this article). So the read times are actually for an object graph (customer and address, employee and addresses, user and organization). The update and delete times represent the time it takes for each framework to carry out cascade updates or deletes through the same graph.

As expected, straight JDBC generally performs best. How often do you add a layer to your application and find it performs faster? Frameworks add a layer of code. What the frameworks provide over straight JDBC code is maintainability, flexibility, etc. It should be noted, that while JDBC PreparedStatements were used in the JDBC code, the code did not take advantage of the parsing/planning cache that PreparedStatements can offer. So, the JDBC results could be made better still with some changes.

As you can see, JDBC actually does not perform best in all cases. When updating or removing an object, JDBC requires many database calls to handle all the cascade effects across object relationships (to address, user, and organization for example). iBatis performs closest to the JDBC code because iBatis is a lightweight framework. iBatis is essentially JDBC code with the SQL moved to XML files. Therefore, if you're using JDBC or iBatis, your performance is going to be as good (or bad) as your SQL. Hibernate, JPOX JDO, and the like do not require you to know SQL in order to get optimal performance. And some of the SQL can get quite complex, as you'll see in a bit.

Castor's performance was especially disappointingso poor, in fact, that retrieving an object (and its associated objects) by its identifier took too long to chart. According to the Castor documentation, a lock is obtained for each object loaded. I suspect, but am not certain, that this contributes to the longer execution times. Castor experts are encouraged to reply to explain the issue and how performance can be improved.

Each of the frameworks and JDBC offer improved performance over repeated iterations after an initial execution. Taking a look at Figure 5, you can see that in an operation such as reading five thousand customers and associated addresses, each of the frameworks takes longer to execute on the first execution. Most significantly improve after the first execution. JDO is the only framework that has oscillating performance over multiple iterations.

Figure 5. Performance over Multiple Iterations: This graph shows the time it takes to read 5K customers over 10 successive iterations.

Code Impact
When looking at the performance of code options, it is absolutely necessary to also look at the code impact (size, complexity, maintainability, etc.) of any two program's solutions. They are the ying and yang of the development world. Give in one and take in the other. Where straight JDBC code may perform well in most situations, the amount of programmer code required to perform the same task in a persistence framework like Hibernate is stark.

As a small example, take a look at one of the CRUD operations that each of the frameworks must perform; notably that of saving/updating an employee and all associated objects (Listing 1).

This highlights the Java code differences. The amount of JDBC Java code required to save an employee and associated objects is typically more than double and almost triple than using a persistence framework! The complexity of the persistence code when a framework is used is pretty straightforward. In the general the algorithm is:

Open a connection/transaction
Save the new or modified object
Close the connection/transaction

No SQL. No loading of parameters. No extracting of data from result sets and appropriately setting object properties.

Not shown here is the amount of configuration in XML that is also required. Table 5 gives you the total number of lines of code and configuration needed to get the basic functionality of the PersonDAO interface implemented in each technology. The interface includes the DAO code and necessary utility class in each of the persistence frameworks. Most of the configuration code is in XML but some is also in simple key/value pair configuration files.

Lines of Java code

Ratio to JDBC
solution

Lines of
configuration code

Total lines of code

Total ratio to JDBC

Hibernate

249

0.37

128

377

0.55

JDO

263

0.39

82

345

0.50

Castor

296

0.43

156

452

0.66

iBatis

331

0.49

290

621

0.91

JDBC

681

1.00

4

685

1.00

Table 5. Persistence Framework Code Size: This table represents the total number of lines of Java code and configuration code for implementing the PersonDAO interface.

Even with configuration code, JDBC is almost double the size of some of the code that takes advantage of the persistence framework. You might note that the iBatis code is not significantly smaller than that of JDBC. In fact, as mentioned earlier, iBatis is more of a lightweight persistence framework. It helps remove SQL from the Java code, but still requires the programmer to write SQL code that handles most of the labor associated with persisting objects and their association.

The SQL code you have to manage in iBatis or JDBC solutions can become quite nasty; especially when dealing with relationships. Take the relatively simple relationships between Person, Employee, Address, User, and Organization. As seen in the code below, the SQL code necessary to retrieve the data for these objects starts to get pretty ugly. How many of the developers around you can write SQL that includes left joins and left outer joins in the same statement? Ick!

SELECT e.employee_id,lastName,firstName,gender,
dateOfBirth,email,phoneNumber,startDate,salary,i.user_id,username,password,
passwordHint,active,o.organization_id,name,a.address_id,street,city,state,
zip FROM persons p, employees e left outer join person_address pa on
e.employee_id=pa.person_id left join intertech_users i on
e.user_id=i.user_id left join organizations o on e.organization_id=o.organization_id
left join addresses a on pa.address_id=a.address_id
WHERE p.person_id=e.employee_id ORDER BY e.employee_id

This is one of the areas that frameworks, like Hibernate, implementations of JDO and Castor, shine. They allow you to concentrate on Java programming and avoid SQL coding. While these frameworks do not free you from needing to understand relational databases (tables, columns, types, etc.), they do eliminate, or at least reduce, the need to write a lot of complex SQL.

The statistics in Table 5 for the Castor code are probably inflated. I had issues getting Castor to automatically handle the persistence of associated objects like addresses (again, something that will be discussed later in this article). This required extra code to force the persistence of associated objects.

Another caveat with regard to the data shown in Table 5 is that the JDBC and iBatis code does not have some of the association intelligence/awareness built into it that some of the other persistence frameworks automatically offer. No allowance is given to adding or removing addresses in an employee update scenario in the JDBC/iBatis code. Small property changes to an employee, for example, trigger SQL calls for the updating the employee row and all the associated object rows. To handle these situations, the JDBC and iBatis code would bloat further but probably perform even faster. This again highlights another of the strengths offered by many of the persistence frameworks covered in the next section.

Lines of code do not always tell the whole story with regard to application size. In some cases, especially on more limited hardware, the size of the executed code (byte code, libraries, etc.) can be an issue. Certainly the number and size of all the persistence framework libraries impact the footprint of an application. Additionally, JDO's extra byte code enhancement adds significant persistence code to each of the domain objects. Table 6 gives an indication of the size of the persistence framework libraries, persistence code and configuration files as well as the impact on the size of the domain files.

Size in bytes of
data access management classes (DAO and utils)

Size in bytes of
domain classes

Size in bytes of
configuration files

Size of required
libraries in KB

Total application
footprint impact in KB

JDO

6,768

90,470

4,443

5,405

5506.7

Hibernate

5,900

28,983

5,422

3,416

3456.3

Castor

7,947

28,983

5,430

2,976

3018.4

iBatis

8,489

28,983

11,865

399

448.3

JDBC

18,945

28,983

94

0

48.0

Table 6. Persistence Footprint Impact: The libraries add significant size to the application when using a framework.

As you can see, JDBC does not require any additional libraries, but the code that performs the object persistence is significant. Also, surprisingly, JDO's extra byte code enhancement more than triples the size of domain class byte codes.

While libraries and byte code files are not code that you maintain directly, they can impact how your application may run when resources are in short supply and should be a consideration in limited environments.