I did some performance comparison on JPA queries and JDBC queries using
EclipseLink 2.1.
My one test result on an Intel processor shows that JPA queries are 15% faster
than functionally equivalent, but slightly simplified JDBC queries , while
another test result on an AMD processor says JPA queries are the same as JDBC
queries.
In both tests, JPA named queries, JPA criteria queries, and JPA Native queries
are equally fast.
My question is: Has any one done similar tests and observed different results
from my mine? How to explain EclipseLink is faster on one machine architecture
over the other?
See below for more testing details..
Thanks,
Daoqi
************ more details **************************************
Since a JPA implementation is a framework built on
the top of JDBC, a valid question to ask is: how much overhead does a JPA
implementation introduce over using JDBC directly. In the following, I
use EclipseLink JPA 2.1 to do some performance testing and compare it to JDBC.
A typical insert, update, and delete operation is
performed on one row at a time and to one database table only. Thus I am not
worried about possible performance overhead from JPA. My test is focused on
query performance between JPA and JDBC. My test scenario is constructed as
follows:
Use
some well-known entities: Order, LineItem, and Customer with some obvious
relationships and mapping.
Perform
JPA JPQL queries, JPA criteria queries, and JPA native queries, and JDBC
queries on separate JVM runs. These queries are functionally equivalent with
JPA native queries and JDBC queries perform almost exactly the same SQL
queries. The JDBC queries are slightly simplified versions of the SQL queries
generated from JPA JPQL queries by EclipseLink.
They
simulate a typical web application, in which a max number of records is
retrieved from all queries, and the values in the JDBC result set are
programmatically converted into a list of Java objects. JPA queries
return a list of Java objects automatically. These objects are potentially used
in a framework such as Struts or JSF for display in the browser.
A test run using Derby
10.4.2 database on a Windows XP computer with Intel Core Duo
CPU, 2.66 GHz, and 3.5 GB RAM gives the following
results:
Query
Types
|
Time
in seconds
|
JPA
JPQL Queries
|
53
|
JPA
Criteria Queries
|
53
|
JPA
Native Queries
|
53
|
JDBC
Queries
|
62
|
This test run has 11984 records in the Order table,
23966 records in the LineItem table, and 47935 records in the Customer table.
It shows that JPA queries are 15% faster than their equivalent JDBC queries.
Also, JPA criteria queries are as fast as JPQL queries, and JPA native queries
have the same efficiency as JPQL queries.
Another test run on the same queries using Derby 10.4.2 database on a Windows XP computer with 2.20
GHz AMD Athlon 64 processor 3500+ and 2.5GB RAM gives the following results:
Query
Types
|
Time
in seconds
|
JPA
JPQL Queries
|
42
|
JPA
Criteria Queries
|
42
|
JPA
Native Queries
|
42
|
JDBC
Queries
|
42
|
This test run has 11008 records in the Order table,
22008 records in the LineItem table, and 44000 records in the Customer table.
It shows that JPA queries are as fast as their equivalent JDBC queries.
Next, I show the three queries that are run multiple
times during each test.
Query 1
The first JPQL named query involves a two table
inner join and reads:
SELECT
o FROM Order o JOIN o.lineItems i WHERE i.price > 5
EclipseLink
JPA generates the following SQL query during execution:
SELECT
t1.ORDER_ID_PK, t1.CREATE_TIME, t1.CREATE_USER, t1.PRICE,
t1.STATUS, t1.UPDATE_TIME, t1.ORDER_TIME, t1.VERSION,
t1.UPDATE_USER, t1.CUSTOMER_ID_FK
FROM
jpatest.LINE_ITEM t0, jpatest.ORDERS t1
WHERE
((t0.PRICE > ?) AND (t0.ORDER_ID_FK = t1.ORDER_ID_PK))
The
corresponding first JDBC query is based on the generated SQL above but with
fewer data fields in the SELECT clause:
SELECT
t1.ORDER_ID_PK, t1.PRICE, t1.STATUS, t1.ORDER_TIME
FROM
jpatest.LINE_ITEM t0, jpatest.ORDERS t1
WHERE
((t0.PRICE > ?) AND (t0.ORDER_ID_FK = t1.ORDER_ID_PK))
The first JPA native query executes the same SQL
query as the JDBC query above, and the first JPA criteria query corresponds
exactly to the JPQL query above.
Query 2
The second JPQL query involves a two table outer
join and reads:
SELECT
o FROM Order o LEFT OUTER JOIN o.lineItems i
WHERE
o.orderTime < :today
EclipseLink
JPA generates the following SQL query during execution:
SELECT
t1.ORDER_ID_PK, t1.CREATE_TIME, t1.CREATE_USER, t1.PRICE,
t1.STATUS, t1.UPDATE_TIME, t1.ORDER_TIME, t1.VERSION,
t1..UPDATE_USER, t1.CUSTOMER_ID_FK
FROM
jpatest.ORDERS t1 LEFT OUTER JOIN jpatest.LINE_ITEM t0
ON (t0.ORDER_ID_FK = t1.ORDER_ID_PK)
WHERE
(t1.ORDER_TIME < ?)
The
corresponding second JDBC query is based on the generated SQL above but with
fewer data fields in the SELECT clause:
SELECT
t1.ORDER_ID_PK, t1.PRICE, t1.STATUS, t1.ORDER_TIME
FROM
jpatest.ORDERS t1 LEFT OUTER JOIN jpatest.LINE_ITEM t0
ON (t0.ORDER_ID_FK = t1.ORDER_ID_PK)
WHERE
(t1.ORDER_TIME < ?)
The second JPA native query executes the same SQL
query as the JDBC query above, and the second JPA criteria query corresponds
exactly to the JPQL query above.
Query 3
The third JPQL query involves third tables with a
subquery and reads:
SELECT
c FROM Customer c
WHERE
(
SELECT COUNT(o) FROM c.orders o WHERE SIZE(o.lineItems) >= 1
) > :count
EclipseLink
JPA generates the following SQL query during execution:
SELECT
t0.CUSTOMER_ID_PK, t0.CUSTOMER_TYPE, t0.CREATE_TIME,
t0.UPDATE_TIME, t0.VERSION, t0.PICTURE, t0.CREATE_USER,
t0.INCOME, t0.NAME, t0.UPDATE_USER, t0.ADDRESS_ID_FK,
t0.ACCOUNT_NUMBER, t0.BANK_NAME, t0.ROUTING_NUMBER,
t1.CUSTOMER_ID_FPK, t1.CREDIT_LIMIT, t1.CARD_NUMBER,
t2.CUSTOMER_ID_FPK, t2.EXPIRATION_DATE, t2.DISCOUNT_RATE
FROM
jpatest.CUSTOMER t0 LEFT OUTER JOIN jpatest.GOLD_CUSTOMER t1
ON (t1.CUSTOMER_ID_FPK = t0.CUSTOMER_ID_PK)
LEFT OUTER JOIN jpatest.PREFERRED_CUSTOMER t2
ON (t2.CUSTOMER_ID_FPK = t0.CUSTOMER_ID_PK)
WHERE
((SELECT COUNT(t3.ORDER_ID_PK) FROM jpatest.CUSTOMER t4,
jpatest.ORDERS t3 WHERE ((((SELECT COUNT(t5.LINE_ITEM_ID_PK)
FROM jpatest.LINE_ITEM t5
WHERE (t5..ORDER_ID_FK = t3.ORDER_ID_PK)) >= ?)
AND (t0.CUSTOMER_ID_PK = t4.CUSTOMER_ID_PK))
AND (t3.CUSTOMER_ID_FK = t4.CUSTOMER_ID_PK))) > ?)
EclipseLink
JPA binds the first parameter with the value of 1.
The
corresponding third JDBC query is based on the generated SQL above but with
fewer data fields in the SELECT clause:
SELECT
t0.CUSTOMER_ID_PK, t0.CUSTOMER_TYPE, t0.PICTURE, t0.INCOME,
t0.NAME, t0.UPDATE_USER, t0.ADDRESS_ID_FK, t0.ACCOUNT_NUMBER,
t0.BANK_NAME, t0.ROUTING_NUMBER,
t1.CUSTOMER_ID_FPK, t1.CREDIT_LIMIT, t1.CARD_NUMBER,
t2.CUSTOMER_ID_FPK, t2.EXPIRATION_DATE, t2.DISCOUNT_RATE
FROM
jpatest.CUSTOMER t0 LEFT OUTER JOIN jpatest.GOLD_CUSTOMER t1
ON (t1.CUSTOMER_ID_FPK = t0.CUSTOMER_ID_PK)
LEFT OUTER JOIN jpatest.PREFERRED_CUSTOMER t2
ON (t2.CUSTOMER_ID_FPK = t0.CUSTOMER_ID_PK)
WHERE
((SELECT COUNT(t3.ORDER_ID_PK) FROM jpatest.CUSTOMER t4,
jpatest.ORDERS t3
WHERE ((((SELECT COUNT(t5.LINE_ITEM_ID_PK)
FROM jpatest.LINE_ITEM t5
WHERE (t5.ORDER_ID_FK = t3.ORDER_ID_PK)) >= ?)
AND (t0.CUSTOMER_ID_PK = t4.CUSTOMER_ID_PK))
AND (t3.CUSTOMER_ID_FK = t4.CUSTOMER_ID_PK))) > ?)
The
first parameter in this JDBC query is also bound with value 1.
The third JPA native query executes the same SQL
query as the JDBC query above, and the third JPA criteria query corresponds
exactly to the JPQL query above.
During the test run for JPQL queries, the following
Java code is executed multiple times and running time is measured:
protected void
queryUsingJPQL() {
EntityManager em = emf.createEntityManager();
try {
//
JPQL query 1: join two tables
{
Query q = em.createQuery(jpqlQuery1);
q.setMaxResults(maxRows);
q.setFirstResult(0);
List resultList = q.getResultList();
for (Object o :
resultList) {
double price = ((Order) o).getPrice();
if (debug) {
System.out.println("jpql inner join o: " + price);
}
}
}
//
JPQL query 2: join two tables with outer join
{
. . .
}
// JPQL query 3: join two tables with a subquery
{
. .
}
} catch (Throwable t) {
t.printStackTrace();
}
em.close();
}
Note that a fixed number of records is retrieved for
each query to simulate a typical web application in which a page of data is
displayed on the screen each time.
During the test run for JDBC queries, the following
Java code is executed multiple times and running time is measured:
protected void queryUsingJDBC()
{
try {
Connection conn = DriverManager.getConnection(databaseURL,
databaseUsername, databasePassword);
//
JDBC query 1: join two tables
{
PreparedStatement ps = conn.prepareStatement(jdbcQuery1);
ps.setDouble(1, 5.0);
ps.setMaxRows(maxRows);
ResultSet rs = ps.executeQuery();
List<Order> orderList = new ArrayList<Order>();
while (rs.next())
{
Order order = new Order();
rs.getInt(1);
order.setPrice(rs.getDouble(2));
int status = rs.getInt(3);
order.setStatus(OrderStatus.fromInt(status));
order.setOrderTime(rs.getTimestamp(4));
orderList.add(order);
if (debug) {
System.out.println("jdbc inner join o: " +
order.getPrice());
}
}
}
// JDBC query 2: join two tables with outer join
{
. . .
}
// JDBC query 3: join two tables with a subquery
{
. . .
}
conn.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
To
make the JDBC and JPQL queries functionally equivalent, the values in the JDBC
result set are converted into Java objects. These objects are typically used by
frameworks such as Struts or JSF in the presentation layer. This will make it
an apple for apple comparison..