The labs on this site need JavaScript to run. The same content is written out below.
Learn › JPA › Getting started
What JPA solves
When you write the SQL yourself, what do you have to fill in by hand?
Java objects are linked to each other by references, while the rows of a table are linked by numbers (foreign keys). Because the shapes differ, someone has to write the code that moves the rows of a table into objects.
Checked against real execution · JDBC code and Hibernate 7.4.5 were run on H2 in 12 combinations and checked against the results
Concepts in this lab
- ORM
- A technique that connects (maps) objects to the tables of a relational database. The developer works with objects, and the matching SQL is generated and executed automatically. You write less repetitive SQL and can write object-centered code, but performance problems appear if you do not understand which SQL is executed.
- JDBC
- The most basic standard API that a Java program uses to send SQL to a database and receive the results. You get a connection, pass the SQL statement to it as a string, fill in the values at the question marks and run it, and then take the column values one by one from the returned result (a ResultSet). Both writing the SQL and moving the values you took into the fields of an object are the developer's job. JPA and MyBatis also send SQL through JDBC internally. So even if you do not use JDBC directly, it helps to know what happens underneath.
- Object/relational impedance mismatch
- The gap that arises because Java objects and the tables of a relational database represent data in different ways. An object points to other objects with references and moves along those references, but a table row points to other rows with numbers called foreign keys and links them with joins. Each time you use new, you get a different object, but in a table, rows with the same primary key are the same row. Objects have inheritance, but tables do not. If you write the SQL yourself, the developer writes the conversion code that bridges this difference, and an ORM takes over that conversion. It is often also called the paradigm mismatch.
- JPA, Hibernate, Spring Data JPA
- JPA (Jakarta Persistence) is the standard ORM specification for Java: a set of rules (interfaces) that defines which features exist and how to use them. Hibernate is a library that actually implements that specification. Spring Data JPA is a Spring module that provides repository interfaces to make JPA easier to use, and it uses JPA (Hibernate) internally.
- First-level cache
- The store inside the persistence context that keeps entities by identifier. If you load the same identifier again within the same transaction, it returns the stored entity without sending a query to the database. As a result, entities with the same identifier are always the same object within the same transaction. By default it disappears when the transaction ends (in a web request with Open Session in View (OSIV) turned on, it lasts until the request ends), so it is different from an application-wide cache.
Try these
- Default settings — The order was read with JDBC, but order.getMember() is null.
- Write the SQL that reads the member yourself — It works, but the amount of SQL and of code that moves values doubles.
- Go to the member with JPA — You wrote no SQL, yet the member name appears.
- Load twice: JDBC — It is the same row, but two different objects are created.
- Load twice: JPA — One SELECT, and one object.
- Add a field: SQL unchanged — It was saved as VIP, but the value disappears without an error.
- Add a field: JPA — You only add one line to the entity.
Running with the default settings
- orderDao.findById(1L): The SELECT we wrote reads row 1 of orders and copies id and amount into the object. The row has only the number of the member (member_id = 1), not the name of the member. There is no code that creates a member object and sets it, so the member field of order stays null.
- order.getMember().getName(): getMember() returns null. getName() was called on null, so a NullPointerException is thrown.
Result of this run
- Objects are navigated by references such as order.getMember(), while tables are linked by numbers such as member_id. When you write the SQL yourself, an object is filled in only as far as the SQL you read. To use the member too, you have to write more SQL that reads the member and more code that moves the values.
Code
class Order {
Long id;
int amount;
Member member; // in an object, we want to point to the member object directly
}
class OrderDao {
Connection conn;
Order findById(long id) throws SQLException {
PreparedStatement ps = conn.prepareStatement(
"select id, amount, member_id from orders where id = ?");
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
rs.next();
Order o = new Order();
o.setId(rs.getLong("id"));
o.setAmount(rs.getInt("amount"));
long memberId = rs.getLong("member_id"); // the table has only the member number
// There is no code that creates a member object and sets it. o.member stays null
return o;
}
}
Order order = orderDao.findById(1L);
System.out.println(order.getMember().getName());
Assumptions of this model
All 12 combinations were actually run and checked on the PC used to write this lab (the JDBC side ran DAO code with the same structure as on the screen against an H2 database, and the JPA side ran on Hibernate 7.4.5 and H2). What was checked: the number of SQL statements written in the code, the kind and the table of the SQL sent on each line, the value of a == b, the member name or the exception, and the grade read back and the grade left in the DB. The "filled in by hand" setting has meaning only for JDBC, and for JPA it does not affect the result. The SQL statements on the screen are shortened to be easy to read, and the statements that Hibernate actually builds differ in column order and aliases. The @ManyToOne of JPA was set to lazy fetching (LAZY). With the default, eager fetching (EAGER), a single em.find reads the order and the member together. The amount of JDBC code is based on the DAO in this example. If you use Spring's JdbcTemplate or MyBatis, the repeated code shrinks, but it stays the same that you write the SQL yourself and have to change the SQL together when a field changes. "Four places to change" is counted in the code on the screen.
Next lab: Persistence context