Lesson 4 / 25

EntityManager Operations

Use persist, find, getReference, merge and remove correctly.

The core API

The EntityManager is the main JPA interface for working with entities (Hibernate's Session implements it and adds more). persist(entity) makes a new entity managed; it is inserted at the next flush. find(Class, id) loads an entity by primary key, returning null if it does not exist, and returns the same instance if it is already in the persistence context, without another query. getReference(Class, id) returns a lazy proxy without querying, useful for setting a foreign key (order.setCustomer(em.getReference(Customer.class, 42L))); accessing its fields later triggers the load, or EntityNotFoundException if it does not exist. merge(entity) copies the state of a detached object onto a managed instance (loading it if necessary) and returns the managed copy; the argument itself stays detached, a classic source of bugs. remove(entity) schedules deletion of a managed entity. refresh reloads state from the database, and detach or clear remove entities from management. In Spring applications you usually inject the EntityManager with @PersistenceContext or use Spring Data repositories, which call these methods for you.

Operations and their effects

persist and find bring entities into the persistence context; flush turns changes into SQL.

A central rounded area holding several entity icons, with arrows entering from the left for persist and find, and an arrow from the area down to a database cylinder.
Figure 2.1 — EntityManager operations around the persistence context.

Using the EntityManager directly

Note that merge returns the managed instance you should keep using.

@Service
public class ProductService {

    @PersistenceContext
    private EntityManager em;

    @Transactional
    public Long create(String sku, String title, BigDecimal price) {
        Product p = new Product(sku, title, price);   // transient
        em.persist(p);                                 // managed; INSERT at flush/commit
        return p.getId();                              // id assigned (sequence) during persist
    }

    @Transactional
    public Product updateFromDto(ProductDto dto) {
        Product detached = dto.toEntity();
        Product managed = em.merge(detached);          // use the RETURNED instance
        managed.changePrice(dto.price());              // tracked; 'detached' is not
        return managed;
    }

    @Transactional
    public void delete(Long id) {
        Product p = em.find(Product.class, id);
        if (p != null) em.remove(p);                   // remove requires a managed entity
    }
}

Do not call save on managed entities

Inside a transaction, a loaded entity is already managed: changing its fields is enough, and Hibernate writes the UPDATE at flush. Calling merge or repository.save again is unnecessary noise.

Quick check: What does `em.merge(detachedProduct)` return?

  • Nothing; it updates the argument in place
  • A new transient object
  • A managed instance with the detached object's state copied onto it
  • The database row count
Answer

A managed instance with the detached object's state copied onto it — merge returns the managed copy; the argument itself remains detached.