Setting Up JPA and Your First Entity

JPA Beginner 5 min read Published
In this lesson

Let’s run JPA for real. You’ll use Hibernate 6 as the JPA provider and H2, an in-memory database that needs no installation. Later you can switch to MySQL or PostgreSQL by changing two lines.

Step 1: Maven dependencies#

<dependencies>
  <dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.5.2.Final</version>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
  </dependency>
</dependencies>

hibernate-core brings in the jakarta.persistence API too.

Step 2: persistence.xml#

Create src/main/resources/META-INF/persistence.xml:

<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
  <persistence-unit name="school">
    <class>com.vitalstack.Course</class>
    <properties>
      <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:school;DB_CLOSE_DELAY=-1"/>
      <property name="jakarta.persistence.jdbc.user" value="sa"/>
      <property name="jakarta.persistence.jdbc.password" value=""/>
      <!-- create tables from entities (development only) -->
      <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
      <!-- print the SQL so you can learn from it -->
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

A persistence unit is a named configuration: which database, which entity classes, which settings.

Step 3: The entity#

package com.vitalstack;

import jakarta.persistence.*;

@Entity                                   // this class maps to a table
public class Course {

    @Id                                   // primary key
    @GeneratedValue(strategy = GenerationType.IDENTITY)   // auto-increment column
    private Long id;

    private String title;
    private int price;

    protected Course() { }                // JPA needs a no-argument constructor

    public Course(String title, int price) {
        this.title = title;
        this.price = price;
    }

    public Long getId() { return id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public int getPrice() { return price; }
    public void setPrice(int price) { this.price = price; }

    @Override public String toString() { return "Course{id=" + id + ", title='" + title + "', price=" + price + "}"; }
}

Entity rules:

  • Annotated with @Entity and has an @Id.
  • Has a public or protected no-argument constructor (JPA creates objects through it).
  • Not final, and persistent fields not final (Hibernate may create proxy subclasses).
  • Use wrapper types like Long for ids, so “not saved yet” is null.

Step 4: Save and load#

package com.vitalstack;

import jakarta.persistence.*;

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("school");   // heavy: create once per app

        EntityManager em = emf.createEntityManager();     // light: one per unit of work
        em.getTransaction().begin();
        Course java = new Course("Java", 499);
        em.persist(java);                                  // schedule INSERT
        em.persist(new Course("Spring Boot", 799));
        em.getTransaction().commit();                      // SQL is sent here
        System.out.println("Saved with id " + java.getId());
        em.close();

        EntityManager em2 = emf.createEntityManager();
        Course found = em2.find(Course.class, java.getId());   // SELECT by primary key
        System.out.println(found);
        em2.close();
        emf.close();
    }
}

Console output (formatted SQL shortened):

create table Course (price integer not null, id bigint generated by default as identity, title varchar(255), primary key (id))
insert into Course (price,title,id) values (?,?,default)
insert into Course (price,title,id) values (?,?,default)
Saved with id 1
select c1_0.id,c1_0.price,c1_0.title from Course c1_0 where c1_0.id=?
Course{id=1, title='Java', price=499}

Hibernate created the table, inserted the rows and mapped the result back into an object. You wrote zero SQL.

The main JPA objects#

ObjectWhat it isLifetime
EntityManagerFactorybuilt from the persistence unit; expensiveone per application
EntityManageryour workspace to save/find/query entitiesone per request / unit of work
EntityTransactionbegin / commit / rollbackaround each set of changes

Changes (persist, update, remove) must happen inside a transaction. In Spring you’ll simply annotate a method with @Transactional instead of calling begin/commit yourself.

Switching to MySQL#

Add the driver dependency and change the properties:

<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/school"/>
<property name="jakarta.persistence.jdbc.user" value="school"/>
<property name="jakarta.persistence.jdbc.password" value="change-me"/>

Hibernate detects the database and generates MySQL-flavoured SQL. Your Java code doesn’t change.

Use drop-and-create (or update) only while learning. In real projects, the schema is managed with migration tools like Flyway or Liquibase, and Hibernate is set to validate or none. Otherwise a deployment could drop your production tables!

Common mistakes#

  • No Persistence provider for EntityManager named school: persistence.xml isn’t in META-INF on the classpath, or the unit name doesn’t match.
  • No default constructor for entity: add a protected no-arg constructor.
  • Persisting outside a transaction: nothing is saved, or TransactionRequiredException.
  • Creating a new EntityManagerFactory for every request (slow and leaks resources).

Exercise#

  1. Run the example and find the generated CREATE TABLE and INSERT in the console.
  2. Add a level field (String) and a published boolean. Check the new columns.
  3. Save five courses, then load one by id and print it.
  4. Change a course’s price inside a transaction without calling any save method. Is an UPDATE printed?
Hint for 4

Yes. A managed entity is tracked; at commit, Hibernate compares it with its snapshot and sends an UPDATE for changed fields. This is called dirty checking (lesson 4).

Interview corner#

  • What is persistence.xml? The JPA configuration file defining persistence units (data source, entities, provider properties).
  • EntityManagerFactory vs EntityManager? EMF is heavyweight, thread-safe, one per app; EM is lightweight, not thread-safe, one per unit of work.
  • Why does an entity need a no-arg constructor? The provider instantiates entities by reflection when loading rows.
  • **What does hibernate.hbm2ddl.auto / schema-generation do?** Generates or updates the schema from entities; use only in development.

Summary#

  • Add hibernate-core + a JDBC driver, configure a persistence unit in META-INF/persistence.xml.
  • @Entity + @Id + a no-arg constructor makes a class persistent.
  • EntityManagerFactory once; EntityManager per unit of work; changes inside transactions.

Next: control exactly how fields map to columns.

Quick quiz

3 questions. Check what stuck and earn 2 XP for each right answer.

1Where does persistence.xml live?
2Why does an entity need a no-argument constructor?
3How many EntityManagerFactories should an app create?

Progress is saved in this browser. Create a free account to keep it on every device.