Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › JPA

Entities

What is an entity, and how does it differ from an ordinary class?

An entity is a Java class that is paired with a database table, and one object of it is paired with one row of the table. In code, it is an ordinary Java class declared with class, and it differs from an ordinary class in two places. @Entity above the class is a marker that objects of this class are stored in a table, and @Id above a field is a marker that this field is the primary key (the column that tells rows apart). With these two, JPA (Jakarta Persistence, here Hibernate) looks at the fields of the class and pairs them with the columns of the table, and when you pass an object to em.persist, it builds the INSERT statement for you and runs it at commit. We do not write SQL. Here, em is the EntityManager object that makes JPA do the saving. In this lab, you remove the two markers one at a time and run the same saving code, to see whether the table is created and whether an INSERT statement is sent.

Checked against real execution · Compiled the source as it is for all 8 combinations on Hibernate 7.4.5 and H2 2.4.240, really started JPA, saved, and checked against the results

Concepts in this lab

Entity
A Java object that corresponds to a row of a database table. It is defined as a class annotated with @Entity and has an identifier (@Id). JPA tracks the state changes of these objects and runs the matching SQL.
@Entity
@Entity is an annotation that you put above a class to mark that "objects of this class are stored as one row of a database table". The JPA specification says that an entity class must have this annotation or be declared as an entity in an XML configuration file, and that the class must have a constructor with no arguments (public or protected) and must not be final. If you do not write a name, the class name becomes the entity name, and the table name also follows this name by default. A class without @Entity is not accepted as an entity even if you register it with JPA, so if you pass an object of that class to em.persist, an error occurs.
@Id
@Id is an annotation that you put above a field (or getter) of an entity to mark that "this value is the primary key". The JPA specification says that every entity must have a primary key, and that a simple primary key made of one field is declared by putting @Id on that field. The primary key is the value that tells one object of the entity apart from the others, so it should not be changed after the object is saved. If you put @Id on a field, JPA reads and writes the value through the field (field access), and if you put it on a getter, it reads and writes through the getter (property access). If a class with @Entity has no @Id, JPA throws an error when it starts and stops.
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.

Try these

Running with the default settings

  1. When the program starts, it tells JPA (here, Hibernate) about the Member class, and JPA reads this class. The line that registers it and the settings that connect to the database are left out of the screen. None of the code we wrote has a line that pairs fields with columns. JPA does the pairing by looking at the markers on the class.
  2. JPA looks for @Entity above the Member class. It is there, so JPA accepts it as a class to store. Next it reads the fields, takes id, which has @Id, as the primary key, and takes the other fields (name) as columns that are not the primary key.
  3. JPA creates a table in the H2 database following this pairing. The table name follows the class name, Member, and the columns are one per field: id, name. This lab sets JPA to create the table when it starts (hibernate.hbm2ddl.auto=create-drop), and we did not write CREATE TABLE either. There are no rows yet.
  4. em.getTransaction().begin(): starts the saving work. A transaction is a unit that handles work as one group, and it is covered separately. No INSERT statement has been sent yet.
  5. new Member(1L, "Alice"): makes an object from the Member class. This object is still an ordinary Java object, and JPA does not know it.
  6. em.persist(...): JPA receives this object. At the moment this line ends, the number of INSERT statements sent is still 0. Passing the object does not write it to the database right away. When the INSERT is sent is covered in the "Persistence context" lab.
  7. em.getTransaction().commit(): JPA builds an INSERT statement and sends it to the database. The statement that Hibernate 7.4.5 built in this run is "insert into Member (name,id) values (?,?)". Into each ?, the values of the fields of the object go in the order of the column names before it. The code we wrote has no word INSERT. The text of the statement may differ by Hibernate version.
  8. A row was created in the Member table. id is 1 and name is Alice. One object became one row of the table.

Result of this run

Code

// Marker: objects of this class are stored as one row of a table
@Entity
public class Member {
    // Marker: this field is the primary key
    @Id
    private Long id;
    private String name;

    protected Member() { }

    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

// The side that saves
em.getTransaction().begin();
em.persist(new Member(1L, "Alice"));
em.getTransaction().commit();

Assumptions of this model

This was checked against real execution on Hibernate 7.4.5 and H2 2.4.240 (an in-memory database). Spring was not used, and Hibernate was started directly in code (the class was registered in Configuration, without persistence.xml). For all 8 combinations, the same text as the Member source on the screen (with only the package and import lines added) was compiled, and a new Java process was started each time. In the combinations without @Entity, Member was registered in the same way. The three "side that saves" lines on the screen are actually code inside main, and the line that gets the EntityManager (em), the line that catches and records the exception, and the lines that measure were left out of the screen. What was checked is whether JPA fails while starting and the kind of exception, the tables and columns in H2 right after starting and after the run (INFORMATION_SCHEMA.COLUMNS), the number and text of the INSERT statements captured with a StatementInspector (when the persist line ended and when the commit line ended), and the number of rows and the content of the table after the run. H2 stores the table name as MEMBER and the column names as ID, NAME, and EMAIL, and the screen shows the table name with only the first letter capitalized and the column names in lowercase to make them easier to read. The columns were not created in the order in which the fields are written (in this run the order was id, email, name). The rule that decides the order was not checked. What the documentation defines is section 2.1 of the Jakarta Persistence 3.2 specification (an entity class must have @Entity, must have a public or protected constructor with no arguments, and must not be final) and section 2.4 (every entity must have a primary key, and a simple primary key is declared by putting @Id on a field), and items 3.4.1, 3.4.3, 3.4.5, and 3.4.6 of section 3.4 (Entity types) of the Hibernate ORM 7.0 User Guide (the table name follows the entity name by default, and putting @Id on a field makes it field access). These documents were opened with WebFetch, and what came back was a summary, not the original text, so I could not check each sentence against the original. For the specification, only the first part was read (the first 100,000 characters, which contain sections 2.1 and 2.4). The following are not what the documentation defines but behaviors observed on this version. When a class without @Entity is registered, JPA finishes starting without an error (there is no entity that JPA knows), and em.persist throws an IllegalArgumentException, and the result is the same even if @Id is there. With @Entity but without @Id, an AnnotationException is thrown when starting and no table is made (item 3.4.5 of the Hibernate 7.0 User Guide says that omitting the identifier used to be allowed but is deprecated, but on 7.4.5 it was an error). The table is created not at persist or commit but when JPA starts (with the setting hibernate.hbm2ddl.auto=create-drop), and the INSERT statement was sent not on the persist line but on the commit line. The statement has the shape insert into Member (name,id) values (?,?), its text may differ by Hibernate version, and details such as whether that INSERT is sent at persist or at commit are covered in the "Persistence context" lab. What the code on the screen leaves out is the package and import lines, and only the fields id, name, and email were covered. @Table and @Column, which change the pairing of names and columns, @GeneratedValue, which makes primary key values automatically, accessor methods, and the rest of the conditions on an entity class (it must not be final, and so on) were not covered. It was not checked against execution on MySQL.

Previous lab: Test code · Next lab: Persistence context

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these