EntityManagerStoreImpl.java

package org.flasby.transactions;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import java.util.Stack;
import lombok.extern.slf4j.Slf4j;
import org.jboss.weld.environment.se.events.ContainerInitialized;

@Slf4j
@ApplicationScoped
public class EntityManagerStoreImpl implements EntityManagerStore {

  private EntityManagerFactory emf;

  private ThreadLocal<Stack<EntityManager>> emStackThreadLocal =
      new ThreadLocal<Stack<EntityManager>>();

  public void init(@Observes ContainerInitialized containerInitialized) {
    emf = Persistence.createEntityManagerFactory("H2");
    log.info("Creating EMF {}", emf);
  }

  @Override
  public EntityManager get() {
    log.info("Getting the current entity manager");
    final Stack<EntityManager> entityManagerStack = emStackThreadLocal.get();
    if (entityManagerStack == null || entityManagerStack.isEmpty()) {
      /* if nothing is found, we return null to cause a NullPointer exception in the business code.
      This leeds to a nicer stack trace starting with client code.
       */

      log.warn(
          "No entity manager was found. Did you forget to mark your method " + "as transactional?");

      return null;
    } else return entityManagerStack.peek();
  }

  /**
   * Creates an entity manager and stores it in a stack. The use of a stack allows to implement
   * transaction with a 'requires new' behaviour.
   *
   * @return the created entity manager
   */
  @Override
  public EntityManager createAndRegister() {
    log.info("Creating and registering an entity manager");
    Stack<EntityManager> entityManagerStack = emStackThreadLocal.get();
    if (entityManagerStack == null) {
      entityManagerStack = new Stack<EntityManager>();
      emStackThreadLocal.set(entityManagerStack);
    }

    final EntityManager entityManager = emf.createEntityManager();
    entityManagerStack.push(entityManager);
    return entityManager;
  }

  /**
   * Removes an entity manager from the thread local stack. It needs to be created using the {@link
   * #createAndRegister()} method.
   *
   * @param entityManager - the entity manager to remove
   * @throws IllegalStateException in case the entity manager was not found on the stack
   */
  @Override
  public void unregister(EntityManager entityManager) {
    log.info("Unregistering an entity manager");
    final Stack<EntityManager> entityManagerStack = emStackThreadLocal.get();
    if (entityManagerStack == null || entityManagerStack.isEmpty())
      throw new IllegalStateException(
          "Removing of entity manager failed. Your entity manager was not found.");

    if (entityManagerStack.peek() != entityManager)
      throw new IllegalStateException(
          "Removing of entity manager failed. Your entity manager was not found.");
    entityManagerStack.pop();
  }
}