Dev Learning Lab

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

Running with the default settings

  1. 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.
  2. order.getMember().getName(): getMember() returns null. getName() was called on null, so a NullPointerException is thrown.

Result of this run

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

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these