Using EntityManager

EntityManager is the standard JPA interface for loading and saving entities. It can also run native SQL queries.

We recommend using DataManager for working with entities and fall back to EntityManager only if you really need it.

For information on the EntityManager methods refer to the jakarta.persistence.EntityManager API documentation.

You can use EntityManager in Jmix applications for working with JPA entities mostly in the same way as in any Spring application. However, Jmix provides its own implementation of the standard interface, which has some specific features.

Obtaining EntityManager

You can inject an instance of EntityManager in any Spring bean with the help of the @PersistenceContext annotation. To use the EntityManager instance, you should have an open transaction. For example:

@PersistenceContext
private EntityManager entityManager;

@Transactional
public Customer createCustomer() {
    Customer customer = metadata.create(Customer.class);
    customer.setName("Bob");
    entityManager.persist(customer);
    return customer;
}

If you need to work with entities from an additional data store, obtain the EntityManager instance for this data store by specifying the data store name in the unitName parameter of the @PersistenceContext annotation, for example:

@PersistenceContext(unitName = "db1")
private EntityManager entityManagerForDb1;

@Transactional("db1TransactionManager")
public Foo createFoo() {
    Foo foo = metadata.create(Foo.class);
    foo.setName("foo1");
    entityManagerForDb1.persist(foo);
    return foo;
}
EntityManager cannot be injected in UI controllers.

Using Fetch Plans

By default, when you use EntityManager for loading entities by id or by JPQL query, it returns all local (immediate) attributes and eagerly fetched references by the rules of the JPA specification. Other references are loaded lazily while in the same transaction.

You can use fetch plans to optimize loading of the needed object graph regardless of FetchType.LAZY fetch instructions in your entity attribute annotations. The fetch plan should be specified in the properties map. For example:

@PersistenceContext
private EntityManager entityManager;

@Autowired
private FetchPlans fetchPlans;

@Transactional
public Order findOrder(UUID orderId) {
    FetchPlan fetchPlan = fetchPlans.builder(Order.class)
            .add("customer")
            .build();

    Map<String, Object> properties = PersistenceHints.builder()
            .withFetchPlan(fetchPlan)
            .build();

    return entityManager.find(Order.class, orderId, properties);
}

In the above example, all local attributes of the Order and the related Customer entity will be loaded, even though they are not included in the fetch plan explicitly. If you want to load only a subset of local attributes, build a "partial" fetch plan. For example:

@Transactional
public Order loadGraphOfPartialEntities(UUID orderId) {
    FetchPlan fetchPlan = fetchPlans.builder(Order.class)
            .addAll("number", "date", "customer.name")
            .partial()
            .build();

    Map<String, Object> properties = PersistenceHints.builder()
            .withFetchPlan(fetchPlan)
            .build();

    return entityManager.find(Order.class, orderId, properties);
}

Soft Deletion

EntityManager supports soft deletion. When you invoke the remove() method for an entity with the Soft Delete trait, it updates the @DeletedDate and @DeletedBy attributes instead of removing the record from the database.

You can turn soft deletion off for a particular transaction to hard delete entities with Soft Delete trait and be able to load instances marked as deleted, by specifying the PersistenceHints.SOFT_DELETION property. For example:

@Transactional
public void hardDelete(Product product) {
    entityManager.setProperty(PersistenceHints.SOFT_DELETION, false);
    entityManager.remove(product);
}

Limitations of EntityManager

  1. EntitySavingEvent and EntityLoadingEvent are not sent when saving and loading entities with EntityManager.

  2. Lazy loading of references outside of the transaction loaded the root entity doesn’t work. You will get a "java.lang.IllegalStateException: Cannot get unfetched attribute …​'’ exception if you access such a reference.

  3. Cross-datastore references are not maintained.

  4. Data access checks are bypassed.

  5. The following JPA features are not supported: named queries, CriteriaBuilder, EntityGraph, EntityTransaction.