Performing CRUD Operations with JPA

Illustration for Performing CRUD Operations with JPA
By Last updated:

One of the most common tasks in database-driven applications is CRUD (Create, Read, Update, Delete). In traditional JDBC, developers wrote boilerplate SQL and managed connections manually. With JPA (Java Persistence API), CRUD operations become intuitive and object-oriented, allowing you to interact with relational databases using Java entities instead of raw SQL.

Think of it this way: JPA is like a translator between your Java objects and the database rows. Just as you might use Google Translate to bridge languages, JPA bridges the object-oriented world and relational tables seamlessly.

In this tutorial, we’ll explore CRUD operations step by step using JPA’s EntityManager, covering setup, annotations, queries, best practices, and real-world considerations.


Setup and Configuration

persistence.xml Configuration

Before using JPA, you need to configure a persistence unit:

<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
    <persistence-unit name="my-pu" transaction-type="RESOURCE_LOCAL">
        <class>com.example.User</class>
        <properties>
            <property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
            <property name="jakarta.persistence.jdbc.user" value="sa"/>
            <property name="jakarta.persistence.jdbc.password" value=""/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

This configures an H2 in-memory database for quick testing.


Entity Definition

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

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

    private String username;
    private String email;

    // Constructors, Getters, Setters
}

CRUD Operations with EntityManager

1. Create (Insert)

EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu");
EntityManager em = emf.createEntityManager();

em.getTransaction().begin();
User user = new User();
user.setUsername("ashwani");
user.setEmail("ashwani@example.com");
em.persist(user); // INSERT operation
em.getTransaction().commit();

SQL Generated:

insert into users (username, email) values ('ashwani', 'ashwani@example.com');

2. Read (Find)

User foundUser = em.find(User.class, user.getId()); // SELECT by primary key

SQL Generated:

select u.id, u.username, u.email from users u where u.id = ?;

3. Update (Merge)

em.getTransaction().begin();
foundUser.setEmail("newmail@example.com");
em.merge(foundUser); // UPDATE operation
em.getTransaction().commit();

SQL Generated:

update users set email='newmail@example.com' where id=?;

4. Delete (Remove)

em.getTransaction().begin();
em.remove(foundUser); // DELETE operation
em.getTransaction().commit();

SQL Generated:

delete from users where id=?;

Querying with JPA

JPQL (Object-Oriented Queries)

List<User> users = em.createQuery("SELECT u FROM User u WHERE u.username = :uname", User.class)
                     .setParameter("uname", "ashwani")
                     .getResultList();

Criteria API (Type-Safe)

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.select(root).where(cb.equal(root.get("username"), "ashwani"));
List<User> results = em.createQuery(cq).getResultList();

Native SQL

List<User> results = em.createNativeQuery("SELECT * FROM users", User.class).getResultList();

Persistence Context and Performance

  • The Persistence Context acts like a classroom attendance register: it keeps track of managed entities.
  • If an entity is already loaded, JPA returns it from cache instead of re-querying the DB.
  • Beware of Lazy vs Eager fetching:
    • Lazy loading = like ordering food only when hungry (efficient).
    • Eager loading = like ordering all food in advance (wasteful if unused).

Anti-Patterns and Pitfalls

  • N+1 Problem: When fetching collections lazily in a loop, leading to excessive queries. Fix with JOIN FETCH.
  • Improper Cascade Usage: Avoid CascadeType.ALL blindly; use specific cascades.
  • Eager Fetching Abuse: Fetch only what you need.

Best Practices

  • Use SEQUENCE strategy with allocationSize for performance in inserts.
  • Always close EntityManager and EntityManagerFactory.
  • Prefer JPQL or Criteria API over native queries for portability.
  • Monitor SQL with hibernate.show_sql for debugging.

📌 JPA Version Notes

  • JPA 2.0: Introduced Criteria API, Metamodel.
  • JPA 2.1: Added stored procedures, Entity graphs.
  • Jakarta Persistence (EE 9/10/11): Package renamed from javax.persistencejakarta.persistence.

Conclusion and Key Takeaways

  • JPA simplifies CRUD by abstracting raw SQL.
  • EntityManager provides methods like persist, find, merge, remove.
  • Queries can be done with JPQL, Criteria API, or native SQL.
  • Mind the persistence context, lazy vs eager fetching, and performance tuning.

By mastering CRUD with JPA, you gain cleaner, portable, and production-ready persistence logic.


FAQ

1. What’s the difference between JPA and Hibernate?
JPA is a specification; Hibernate is an implementation.

2. How does JPA handle the persistence context?
It manages entities in memory, avoiding unnecessary DB hits.

3. What are the drawbacks of eager fetching in JPA?
Can lead to memory bloat and unnecessary joins.

4. How can I solve the N+1 select problem with JPA?
Use JOIN FETCH, batch fetching, or entity graphs.

5. Can I use JPA without Hibernate?
Yes, alternatives include EclipseLink and OpenJPA.

6. What’s the best strategy for inheritance mapping in JPA?
Use JOINED for normalized schema, SINGLE_TABLE for performance.

7. How does JPA handle composite keys?
Via @IdClass or @EmbeddedId.

8. What changes with Jakarta Persistence?
Namespace moved from javax.persistence to jakarta.persistence.

9. Is JPA suitable for microservices?
Yes, but lightweight projections and DTOs are recommended.

10. When should I avoid using JPA?
In high-performance batch systems or NoSQL-heavy apps.