JPA and Hibernate Tutorial using Spring Boot Data JPA

blog details
author Ranga Karanam September 13, 2025 19 minutes

Image

This guide introduces JPA and walks you through setting up a simple example in Spring Boot.

Image

You Will Learn

  • What is JPA?
  • The problem JPA solves – Object-Relational Impedance Mismatch
  • Alternatives to JPA
  • What is Hibernate, and how does it relate to JPA?
  • What is Spring Data JPA?
  • How to create a simple JPA project using the Spring Boot Data JPA Starter

You Will Need the Following Tools

  • Maven 3.0+ – Build and dependency management tool
  • An IDE of your choice – for example, Eclipse or IntelliJ IDEA
  • JDK 17+ – Java Development Kit
  • H2 Database – In-memory database for quick setup and testing

What is Object Relational Impedance Mismatch?

Image

Java is an object-oriented programming language, where all data is represented and managed through objects.

In most applications, data is stored in relational databases (although NoSQL databases are also gaining popularity, we will not focus on them here). Relational databases store data in tables.

However, the way we design objects in Java is very different from how relational databases are structured, leading to what is known as an impedance mismatch.

  • Object-oriented programming uses concepts like encapsulation, inheritance, interfaces, and polymorphism.
  • Relational databases organize data into tables and follow principles such as normalization.

Examples of Object-Relational Impedance Mismatch

Let’s take a simple example involving Employees and Tasks.

  • Each Employee can be assigned multiple tasks.
  • Each Task can be shared by multiple employees.

This creates a many-to-many relationship between Employee and Task.
Now, let’s look at some examples of impedance mismatch.

Example 1: Column Naming Differences

Suppose the Task class in Java has fields like taskId and taskDescription.
In the database, the corresponding table might use column names such as id and description.

Even though they represent the same data, the mismatch in naming conventions between objects and database tables creates friction in mapping.

public class Task {
    private int id;

    private String desc;

    private Date targetDate;

    private boolean isDone;

    private List<Employee> employees;
}
 CREATE TABLE task
 (
     id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
     description VARCHAR(255),
     is_done     BOOLEAN,
     target_date TIMESTAMP,
     PRIMARY KEY (id)
 ) 

Example 2: Differences in Representing Relationships

In Java, relationships between objects are expressed differently than relationships between tables in a database.

For example:

  • Each Employee object can have multiple Task objects.
  • Each Task object can be associated with multiple Employee objects.

This creates a many-to-many relationship.
In a relational database, representing this relationship typically requires a join table, which adds an extra layer of complexity when mapping objects to tables.

public class Employee {

    //Some other code

    private List<Task> tasks;
}

public class Task {

    //Some other code

    private List<Employee> employees;
}
CREATE TABLE employee
(
    id BIGINT NOT NULL,
    OTHER_COLUMNS
)


CREATE TABLE employee_tasks
(
    employees_id BIGINT  NOT NULL,
    tasks_id     INTEGER NOT NULL
)

CREATE TABLE task
(
    id INTEGER GENERATED BY DEFAULT AS IDENTITY,
    OTHER_COLUMNS
)

Example 3: Sometimes multiple classes are mapped to a single table and vice versa.

Objects

public class Employee {
    //Other Employee Attributes
}

public class FullTimeEmployee extends Employee {
    protected Integer salary;
}

public class PartTimeEmployee extends Employee {
    protected Float hourlyWage;
}

Tables

CREATE TABLE employee
(
    employee_type VARCHAR(31) NOT NULL,
    id            BIGINT      NOT NULL,
    city          VARCHAR(255),
    state         VARCHAR(255),
    street        VARCHAR(255),
    zip           VARCHAR(255),

    hourly_wage   FLOAT,   --PartTimeEmployee

    salary        INTEGER, --FullTimeEmployee

    PRIMARY KEY (id)
)

Other Approaches Before JPA — JDBC, Spring JDBC & MyBatis

Before JPA, most approaches focused on writing queries and mapping query results to objects.

Typically, any query-based approach involves two main tasks:

  • Setting query parameters – reading values from objects and assigning them as parameters to the query.
  • Mapping query results – converting the results returned by the query into Java objects (beans).

JDBC

  • JDBC stands for Java Database Connectivity.
  • It used concepts like Statement, PreparedStatement, and ResultSet.
  • In the example below, the query used is Update todo set user=?, desc=?, target_date=?, is_done=? where id=?
  • The values required to execute the query are set using various set methods on the PreparedStatement.
  • The results returned by the query are stored in a ResultSet, and we must manually map these results into Java objects.

Update Todo

Connection connection = datasource.getConnection();

PreparedStatement st = connection.prepareStatement("Update todo set user=?, desc=?, target_date=?, is_done=? where id=?");

st.setString(1,todo.getUser());
st.setString(2,todo.getDesc());
st.setTimestamp(3,new Timestamp(todo.getTargetDate().getTime()));
st.setBoolean(4,todo.isDone());
st.setInt(5,todo.getId());
st.execute();
st.close();
connection.close();

Retrieved by Todo

Connection connection = datasource.getConnection();

PreparedStatement st = connection.prepareStatement(
        "SELECT * FROM TODO where id=?");

st.setInt(1,id);

ResultSet resultSet = st.executeQuery();

if(resultSet.next()) {

    Todo todo = new Todo();
    todo.setId(resultSet.getInt("id"));
    todo.setUser(resultSet.getString("user"));
    todo.setDesc(resultSet.getString("desc"));
    todo.setTargetDate(resultSet.getTimestamp("target_date"));

    return todo;
}

st.close();

connection.close();

return null;

Spring JDBC

  • Spring JDBC provides a higher-level abstraction over standard JDBC.
  • It introduces concepts like JdbcTemplate.
  • Typically, requires fewer lines of code compared to plain JDBC, because it simplifies:
    • Mapping parameters to queries
    • Converting (mapping) result sets into Java objects (beans)

Update Todo

jdbcTemplate.update("Update todo set user=?, desc=?, target_date=?, is_done=? where id=?", todo.getUser(), 
todo.getDesc(), new Timestamp(todo.getTargetDate().getTime()),
todo.isDone(), 
todo.getId());

Retrieve a Todo


@Override
public Todo retrieveTodo(int id) {

    return jdbcTemplate.queryForObject(
            "SELECT * FROM TODO where id=?",
            new Object[]{id}, new TodoMapper());

}

Reusable Row Mapper

// new BeanPropertyRowMapper(TodoMapper.class)
class TodoMapper implements RowMapper<Todo> {
    @Override
    public Todo mapRow(ResultSet rs, int rowNum)
            throws SQLException {
        Todo todo = new Todo();

        todo.setId(rs.getInt("id"));
        todo.setUser(rs.getString("user"));
        todo.setDesc(rs.getString("desc"));
        todo.setTargetDate(rs.getTimestamp("target_date"));
        todo.setDone(rs.getBoolean("is_done"));
        return todo;
    }
}    

MyBatis

MyBatis eliminates the need to manually write code for setting query parameters and mapping results.
It provides a simple XML- or annotation-based configuration to map Java POJOs to database tables.

Below, we compare the approaches used to write queries in different frameworks.

  • JDBC or Spring JDBC
    UPDATE todo 
    SET user=?, desc=?, target_date=?, is_done=? 
    WHERE id=?
    
  • MyBatis
    UPDATE todo 
    SET user=#{user}, desc=#{desc}, target_date=#{targetDate}, is_done=#{isDone}
    WHERE id=#{id}
    

Todo Update and Todo Retrieve


@Mapper
public interface TodoMybatisService
        extends TodoDataService {

    @Override
    @Update("Update todo set user=#{user}, desc=#{desc}, target_date=#{targetDate}, is_done=#{isDone} where id=#{id}")
    void updateTodo(Todo todo) throws SQLException;

    @Override
    @Select("SELECT * FROM TODO WHERE id = #{id}")
    Todo retrieveTodo(int id) throws SQLException;
}

public class Todo {

    private int id;

    private String user;

    private String desc;

    private Date targetDate;

    private boolean isDone;
}

Common Features of JDBC, Spring JDBC, and MyBatis

  • All three approaches—JDBC, Spring JDBC, and MyBatis—require writing SQL queries.
  • In large applications, queries can become complex, especially when retrieving data from multiple tables.
  • This complexity creates problems whenever the database structure changes, requiring updates to all affected queries.

How Does JPA Work?

JPA was designed with a different approach: instead of focusing on queries, it maps Java objects directly to database tables.

Key concepts include:

  • Entities – Java classes that represent database tables
  • Attributes – Fields in the class that correspond to table columns
  • Relationships – Associations between entities, such as one-to-many or many-to-many

This process is known as Object-Relational Mapping (ORM).
Before JPA, the term ORM was commonly used to describe frameworks like Hibernate, which is why Hibernate is often referred to as an ORM framework.

Important Concepts in JPA

Image

JPA allows you to map application classes directly to database tables.

  • Entity Manager – Once mappings are defined, the Entity Manager manages your entities and handles all interactions with the database.
  • JPQL (Java Persistence Query Language) – Provides a way to write queries against entities. Unlike SQL, JPQL understands the mappings between entities and tables, allowing you to add conditions as needed.
  • Criteria API – Offers a Java-based API to build and execute queries programmatically against the database.

JPA vs Hibernate

Hibernate is one of the most popular ORM frameworks.

JPA defines the specification and provides an API, answering key questions like:

  • How do you define entities?
  • How do you map attributes?
  • How do you map relationships between entities?
  • Who manages the entities?

Hibernate is a widely used implementation of JPA:

  • It understands the mappings defined between objects and database tables, ensuring that data is stored and retrieved correctly.
  • Hibernate also offers additional features beyond JPA. However, relying on these features creates a vendor lock-in, making it difficult to switch to other JPA implementations such as TopLink.

Examples of JPA Mappings

Let’s look at a few examples to understand how JPA can be used to map objects to database tables.

Example 1

Suppose we have a Task class that we want to map to a Task table. There may be differences in column names between the class and the table. JPA provides annotations to handle this mapping:

  • @Table(name = "Task") – Maps the class to the database table.
  • @Id – Marks the primary key of the entity.
  • @GeneratedValue – Specifies that the primary key value will be generated automatically.
  • @Column(name = "description") – Maps the class attribute to a specific column in the table.
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;

@Entity
@Table(name = "Task")
public class Task {
    @Id
    @GeneratedValue
    private int id;

    @Column(name = "description")
    private String desc;

    @Column(name = "target_date")
    private Date targetDate;

    @Column(name = "is_done")
    private boolean isDone;

}
 CREATE TABLE task
 (
     id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
     description VARCHAR(255),
     is_done     BOOLEAN,
     target_date TIMESTAMP,
     PRIMARY KEY (id)
 ) 

Example 2

Relationships between objects are expressed differently than relationships between database tables.

For example:

  • Each Employee can have multiple Tasks.
  • Each Task can be associated with multiple Employees.

This creates a many-to-many relationship, which can be mapped in JPA using the @ManyToMany annotation.

public class Employee {

    //Some other code

    @ManyToMany
    private List<Task> tasks;
}

public class Task {

    //Some other code

    @ManyToMany(mappedBy = "tasks")
    private List<Employee> employees;
}
CREATE TABLE employee
(
    id BIGINT NOT NULL,
    OTHER_COLUMNS
)


CREATE TABLE employee_tasks
(
    employees_id BIGINT  NOT NULL,
    tasks_id     INTEGER NOT NULL
)

CREATE TABLE task
(
    id INTEGER GENERATED BY DEFAULT AS IDENTITY,
    OTHER_COLUMNS
)

Example 3

Sometimes, multiple classes need to be mapped to a single table, or a single class needs to be mapped across multiple tables.
In such cases, we define an inheritance strategy.

For example, the strategy InheritanceType.SINGLE_TABLE maps an entire class hierarchy to a single database table.


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "EMPLOYEE_TYPE")
public class Employee {
    //Other Employee Attributes
}

public class FullTimeEmployee extends Employee {
    protected Integer salary;
}

public class PartTimeEmployee extends Employee {
    protected Float hourlyWage;
}

Tables

CREATE TABLE employee
(
    employee_type VARCHAR(31) NOT NULL,
    id            BIGINT      NOT NULL,
    city          VARCHAR(255),
    state         VARCHAR(255),
    street        VARCHAR(255),
    zip           VARCHAR(255),

    hourly_wage   FLOAT,   --PartTimeEmployee

    salary        INTEGER, --FullTimeEmployee

    PRIMARY KEY (id)
)

Step-by-Step Code Example

Bootstrapping a Web application with Spring Initializr

Creating a JPA application with Spring Initializr is very simple.

Image

As shown in the image above, follow these steps to create your Spring Boot project:

  • Launch Spring Initializr: http://start.spring.io/
    • Set Group to com.in28minutes.springboot
    • Set Artifact to H2InMemoryDbDemo
    • Add the following dependencies:
      • Web
      • JPA
      • H2 – an in-memory database used for this example
  • Click the Generate button at the bottom of the page.
  • Import the generated project into Eclipse.

Project Structure

  • H2InMemoryDbDemoApplication.java – Spring Boot launcher that initializes Auto Configuration and the Application Context.
  • application.properties – Application configuration file.
  • H2InMemoryDbDemoApplicationTests.java – Simple launcher for unit testing.
  • pom.xml – Includes dependencies for Spring Boot Starter Web and Data JPA, and uses Spring Boot Starter Parent as the parent POM.

Important dependencies are listed below.


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-h2console -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-h2console</artifactId>
    <version>4.0.0-M1</version> // you can it in if you're creating a Spring Boot application
</dependency>

User Entity

Let’s define a bean user and add the appropriate JPA annotations.

package com.example.h2.user;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;

@Entity
@NamedQuery(query = "select u from User u", name = "query_find_all_users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;// Not perfect!! Should be a proper object!

    private String role;// Not perfect!! An enum should be a better choice!

    protected User() {
    }

    public User(String name, String role) {
        super();
        this.name = name;
        this.role = role;
    }

}

Important Annotations and Concepts

  • @Entity – Specifies that the class is a JPA entity. Apply this annotation to the entity class.
  • @NamedQuery – Defines a static, named query in Java Persistence Query Language (JPQL).
  • @Id – Marks the primary key of an entity.
  • @GeneratedValue – Specifies how the primary key value should be automatically generated.
  • protected User() – Default constructor required by JPA to instantiate the entity.

User Service to Interact with the Entity Manager

With JPA, it is common to create a service layer to interact with the Entity Manager.
In this example, we create a UserService to manage the persistence operations for the User entity.


@Repository
@Transactional
public class UserService {

    @PersistenceContext
    private EntityManager entityManager;

    public long insert(User user) {
        entityManager.persist(user);
        return user.getId();
    }

    public User find(long id) {
        return entityManager.find(User.class, id);
    }

    public List<User> findAll() {
        Query query = entityManager.createNamedQuery(
                "query_find_all_users", User.class);
        return query.getResultList();
    }
}

Important Annotations and Concepts

  • @Repository – A Spring annotation indicating that this component is responsible for data access and interacting with a data store.
  • @Transactional – A Spring annotation that simplifies transaction management.
  • @PersistenceContext – Injects a persistence context, which manages a set of entities and tracks their states (e.g., managed, detached) in relation to the underlying database.
  • EntityManager – The interface used to interact with the persistence context.
  • entityManager.persist(user) – Makes the user entity managed and persistent, i.e., saves it to the database.
  • entityManager.createNamedQuery – Creates a TypedQuery to execute a JPQL named query. The second parameter specifies the expected result type.

An EntityManager instance is associated with a persistence context.
A persistence context is a set of entity instances in which each persistent entity identity corresponds to a unique entity instance.
Within this context, the lifecycle of entities is managed. The EntityManager API is used to create and remove persistent entities, find entities by their primary key, and execute queries over entities.

The set of entities managed by an EntityManager is defined by a persistence unit.
A persistence unit specifies all classes related or grouped by the application that must be mapped to a single database.


CommandLineRunner for User Entity Manager

The CommandLineRunner interface is used to indicate that a Spring bean should run as soon as the application context is initialized.

In this example, we execute a few simple methods on the UserService to demonstrate entity persistence and queries.


@Component
public class UserEntityManagerCommandLineRunner implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(UserEntityManagerCommandLineRunner.class);

    @Autowired
    private UserService userService;

    @Override
    public void run(String... args) {

        log.info("-------------------------------");
        log.info("Adding Tom as Admin");
        log.info("-------------------------------");
        User tom = new User("Tom", "Admin");
        userService.insert(tom);
        log.info("Inserted Tom" + tom);

        log.info("-------------------------------");
        log.info("Finding user with id 1");
        log.info("-------------------------------");
        User user = userService.find(1L);
        log.info(user.toString());

        log.info("-------------------------------");
        log.info("Finding all users");
        log.info("-------------------------------");
        log.info(userService.findAll().toString());
    }
}

Important Things to Note

  • @Autowired private UserService userService – The UserService is automatically injected by Spring.
  • The remaining setup and configuration are straightforward.

Spring Data JPA

Spring Data JPA provides a consistent and simplified model for accessing data from different types of data stores.

The UserService we created earlier contains some redundant code that can be generalized. Spring Data JPA helps reduce boilerplate code and simplifies data access.


@Repository
@Transactional
public class UserService {

    @PersistenceContext
    private EntityManager entityManager;

    public long insert(User user) {
        entityManager.persist(user);
        return user.getId();
    }

    public User find(long id) {
        return entityManager.find(User.class, id);
    }

    public List<User> findAll() {
        Query query = entityManager.createNamedQuery(
                "query_find_all_users", User.class);
        return query.getResultList();
    }
}

As far as JPA is concerned, there are two key Spring Data modules to know:

  • Spring Data Commons – Defines concepts that are shared across all Spring Data modules.
  • Spring Data JPA – Provides easy integration with JPA repositories.

CrudRepository

CrudRepository is a core repository interface provided by Spring Data Commons.
It enables basic CRUD operations on a repository. Some important methods include:

public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

    <S extends T> S save(S entity);

    T findOne(ID primaryKey);

    Iterable<T> findAll();

    Long count();

    void delete(T entity);

    boolean exists(ID primaryKey);

    // … more functionality omitted.
}

JpaRepository

JpaRepository, provided by Spring Data JPA, is a JPA-specific repository interface that extends CrudRepository.
It offers additional JPA-related methods, such as flushing changes to the database and batch operations, while retaining all CRUD functionalities.

public interface JpaRepository<T, ID extends Serializable>
        extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

We will now use JpaRepository to manage the User entity.

The key idea is to create a UserRepository that manages User entities, where the primary key is of type Long.
The snippet below highlights the important details for defining this repository.

package com.example.h2.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

User Repository: CommandLineRunner

The following code is straightforward. The CommandLineRunner interface indicates that this bean should run as soon as the Spring application context is initialized.

In this example, we execute a few simple methods on the UserRepository to demonstrate basic CRUD operations.

package com.example.h2;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import com.example.h2.user.User;
import com.example.h2.user.UserRepository;

@Component
public class UserRepositoryCommandLineRunner implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    public void run(String... args) {
        User harry = new User("Harry", "Admin");
        userRepository.save(harry);
        log.info("-------------------------------");
        log.info("Finding all users");
        log.info("-------------------------------");
        for (User user : userRepository.findAll()) {
            log.info(user.toString());
        }
    }

}

Important Things to Note

  • @Autowired private UserRepository userRepository – The UserRepository is automatically injected by Spring.
  • The rest of the setup is straightforward.

H2 Console

Enable the H2 console in /src/main/resources/application.properties:

spring.h2.console.enabled=true

Start the application by running H2InMemoryDbDemoApplication as a Java application.

The H2-Console can also be accessed via the web browser.

  • http://localhost:8080/h2-console
  • Use db url jdbc:h2:mem:testdb

Questions

  • Where was the database created?
    • In memory, using H2.
  • What schema is used to create the tables?
    • Tables are created automatically based on the entity definitions.
  • Where are the tables created?
    • In memory, using H2, based on the entity definitions.
  • Can I see the data in the database?
    • http://localhost:8080/h2-console
    • Use db url jdbc:h2:mem:testdb
  • Where is Hibernate coming from?
    • Provided by the Spring Data JPA Starter.
  • How is a data source created?
    • Automatically configured through Spring Boot Auto Configuration.

Spring Boot’s Magic with In-Memory Databases

  • No project setup or additional infrastructure required.
  • Zero configuration.
  • Minimal maintenance.
  • Easy to use for learning and unit testing.
  • Simple to switch to a real database with minimal configuration.

Restrictions of Using In-Memory Databases

  • Data is not persisted between application restarts.

Complete Code Example

/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>jpa-in-10-steps</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>jpa-with-in-memory-db-in-10-steps</name>
    <description>Demo project for in memory database H2</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.0-M2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-h2console -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-h2console</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
</project>

/src/main/java/com/example/h2/H2InMemoryDbDemoApplication.java

package com.example.h2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class H2InMemoryDbDemoApplication {

    static void main(String[] args) {
        SpringApplication.run(H2InMemoryDbDemoApplication.class, args);
    }
}

/src/main/java/com/example/h2/user/User.java

package com.example.h2.user;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQuery;

@Entity
@NamedQuery(query = "select u from User u", name = "query_find_all_users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;// Not perfect!! Should be a proper object!
    private String role;// Not perfect!! An enum should be a better choice!

    protected User() {
    }

    public User(String name, String role) {
        super();
        this.name = name;
        this.role = role;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getRole() {
        return role;
    }

    @Override
    public String toString() {
        return String.format("User [id=%s, name=%s, role=%s]", id, name, role);
    }

}

/src/main/java/com/example/h2/user/UserRepository.java

package com.example.h2.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

/src/main/java/com/example/h2/user/UserService.java

package com.example.h2.user;

import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import jakarta.transaction.Transactional;

import org.springframework.stereotype.Repository;

@Repository
@Transactional
public class UserService {

    @PersistenceContext
    private EntityManager entityManager;

    public long insert(User user) {
        entityManager.persist(user);
        return user.getId();
    }

    public User find(long id) {
        return entityManager.find(User.class, id);
    }

    public List<User> findAll() {
        Query query = entityManager.createNamedQuery(
                "query_find_all_users", User.class);
        return query.getResultList();
    }
}

/src/main/java/com/example/h2/UserEntityManagerCommandLineRunner.java

package com.example.h2;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import com.example.h2.user.User;
import com.example.h2.user.UserService;

@Component
public class UserEntityManagerCommandLineRunner implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(UserEntityManagerCommandLineRunner.class);

    @Autowired
    private UserService userService;

    @Override
    public void run(String... args) {

        log.info("-------------------------------");
        log.info("Adding Tom as Admin");
        log.info("-------------------------------");
        User tom = new User("Tom", "Admin");
        userService.insert(tom);
        log.info("Inserted Tom" + tom);

        log.info("-------------------------------");
        log.info("Finding user with id 1");
        log.info("-------------------------------");
        User user = userService.find(1L);
        log.info(user.toString());

        log.info("-------------------------------");
        log.info("Finding all users");
        log.info("-------------------------------");
        log.info(userService.findAll().toString());
    }
}

/src/main/java/com/example/h2/UserRepositoryCommandLineRunner.java

package com.example.h2;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import com.example.h2.user.User;
import com.example.h2.user.UserRepository;

@Component
public class UserRepositoryCommandLineRunner implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    public void run(String... args) {
        User harry = new User("Harry", "Admin");
        userRepository.save(harry);
        log.info("-------------------------------");
        log.info("Finding all users");
        log.info("-------------------------------");
        for (User user : userRepository.findAll()) {
            log.info(user.toString());
        }
    }

}

/src/main/resources/application.properties

spring.h2.console.enabled=true
#logging.level.org.hibernate=debug
spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:mem:testdb;NON_KEYWORDS=USER
spring.data.jpa.repositories.bootstrap-mode=default
spring.jpa.defer-datasource-initialization=true

/src/main/resources/data.sql

insert into user (id, name, role) values (101, 'Ranga', 'Admin');
insert into user (id, name, role) values (102, 'Ravi', 'User');
insert into user (id, name, role) values (103, 'Satish', 'Admin');

/src/test/java/com/example/h2/H2InMemoryDbDemoApplicationTests.java

package com.example.h2;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class H2InMemoryDbDemoApplicationTests {

    @Test
    public void contextLoads() {
    }

}

/src/test/java/com/example/h2/user/UserRepositoryTest.java

package com.example.h2.user;

import static org.junit.Assert.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.example.h2.user.UserRepository;

@DataJpaTest
@ExtendWith(SpringExtension.class)
public class UserRepositoryTest {

    @Autowired
    UserRepository userRepository;

    @Autowired
    TestEntityManager entityManager;

    @Test
    public void check_todo_count() {
        assertEquals(3, userRepository.count());
    }
}

Just Released