diff --git a/extensions/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java b/extensions/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java index 1f92ae4ae558d..9b749633414d1 100644 --- a/extensions/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java +++ b/extensions/jpa/deployment/src/main/java/org/jboss/shamrock/jpa/HibernateResourceProcessor.java @@ -77,6 +77,7 @@ import org.jboss.shamrock.jpa.runtime.DefaultEntityManagerProducer; import org.jboss.shamrock.jpa.runtime.JPAConfig; import org.jboss.shamrock.jpa.runtime.JPADeploymentTemplate; +import org.jboss.shamrock.jpa.runtime.RequestScopedEntityManagerHolder; import org.jboss.shamrock.jpa.runtime.TransactionEntityManagers; import org.jboss.shamrock.jpa.runtime.boot.scan.ShamrockScanner; @@ -134,7 +135,7 @@ void handleNativeImageImportSql(BuildProducer resour @BuildStep void registerBeans(BuildProducer additionalBeans, CombinedIndexBuildItem combinedIndex, List descriptors) { - additionalBeans.produce(new AdditionalBeanBuildItem(false, JPAConfig.class, TransactionEntityManagers.class)); + additionalBeans.produce(new AdditionalBeanBuildItem(false, JPAConfig.class, TransactionEntityManagers.class, RequestScopedEntityManagerHolder.class)); if (descriptors.size() == 1) { // There is only one persistence unit - register CDI beans for EM and EMF if no diff --git a/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/RequestScopedEntityManagerHolder.java b/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/RequestScopedEntityManagerHolder.java new file mode 100644 index 0000000000000..492d7007d3c42 --- /dev/null +++ b/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/RequestScopedEntityManagerHolder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.shamrock.jpa.runtime; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PreDestroy; +import javax.enterprise.context.RequestScoped; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + + +/** + * Bean that is used to manage request scoped entity managers + */ +@RequestScoped +public class RequestScopedEntityManagerHolder { + + private final Map entityManagers = new HashMap<>(); + + public EntityManager getOrCreateEntityManager(String name, EntityManagerFactory factory) { + return entityManagers.computeIfAbsent(name, (n) -> factory.createEntityManager()); + } + + @PreDestroy + public void destroy() { + for (Map.Entry entry : entityManagers.entrySet()) { + entry.getValue().close(); + } + } + +} diff --git a/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java b/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java index 1ab4547c5070a..8db70bed180d5 100644 --- a/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java +++ b/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/TransactionEntityManagers.java @@ -19,8 +19,7 @@ import java.util.HashMap; import java.util.Map; -import javax.annotation.PreDestroy; -import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.transaction.TransactionManager; @@ -28,7 +27,6 @@ import org.jboss.shamrock.jpa.runtime.entitymanager.TransactionScopedEntityManager; -@RequestScoped public class TransactionEntityManagers { @Inject @@ -40,6 +38,9 @@ public class TransactionEntityManagers { @Inject JPAConfig jpaConfig; + @Inject + Instance requestScopedEntityManagers; + private final Map managers; public TransactionEntityManagers() { @@ -48,14 +49,7 @@ public TransactionEntityManagers() { public EntityManager getEntityManager(String unitName) { return managers.computeIfAbsent(unitName, - un -> new TransactionScopedEntityManager(tm, tsr, jpaConfig.getEntityManagerFactory(un))); - } - - @PreDestroy - public void destroy() { - for (TransactionScopedEntityManager manager : managers.values()) { - manager.requestDone(); - } + un -> new TransactionScopedEntityManager(tm, tsr, jpaConfig.getEntityManagerFactory(un), unitName, requestScopedEntityManagers)); } } diff --git a/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/entitymanager/TransactionScopedEntityManager.java b/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/entitymanager/TransactionScopedEntityManager.java index f9446b42934cd..76221d8e295f5 100644 --- a/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/entitymanager/TransactionScopedEntityManager.java +++ b/extensions/jpa/runtime/src/main/java/org/jboss/shamrock/jpa/runtime/entitymanager/TransactionScopedEntityManager.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; +import javax.enterprise.inject.Instance; import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; @@ -38,24 +39,26 @@ import javax.transaction.TransactionManager; import javax.transaction.TransactionSynchronizationRegistry; +import org.jboss.shamrock.jpa.runtime.RequestScopedEntityManagerHolder; + public class TransactionScopedEntityManager implements EntityManager { private final TransactionManager transactionManager; private final TransactionSynchronizationRegistry tsr; private final EntityManagerFactory emf; + private final String unitName; private static final Object transactionKey = new Object(); - private EntityManager fallbackEntityManager; + private final Instance requestScopedEms; - public TransactionScopedEntityManager(TransactionManager transactionManager, TransactionSynchronizationRegistry tsr, EntityManagerFactory emf) { + public TransactionScopedEntityManager(TransactionManager transactionManager, + TransactionSynchronizationRegistry tsr, + EntityManagerFactory emf, + String unitName, Instance requestScopedEms) { this.transactionManager = transactionManager; this.tsr = tsr; this.emf = emf; - } - - public void requestDone() { - if(fallbackEntityManager != null) { - fallbackEntityManager.close(); - } + this.unitName = unitName; + this.requestScopedEms = requestScopedEms; } EntityManagerResult getEntityManager() { @@ -81,10 +84,11 @@ public void afterCompletion(int i) { }); return new EntityManagerResult(newEm, false); } else { - if(fallbackEntityManager == null) { - fallbackEntityManager = emf.createEntityManager(); - } - return new EntityManagerResult(emf.createEntityManager(), false); + //this will throw an exception if the request scope is not active + //this is expected as either the request scope or an active transaction + //is required to properly managed the EM lifecycle + RequestScopedEntityManagerHolder requestScopedEms = this.requestScopedEms.get(); + return new EntityManagerResult(requestScopedEms.getOrCreateEntityManager(unitName, emf), false); } } diff --git a/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/CRUDResource.java b/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/CRUDResource.java index f6a79ad52ea49..728236394057e 100644 --- a/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/CRUDResource.java +++ b/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/CRUDResource.java @@ -1,5 +1,8 @@ package org.shamrock.jpa.tests.configurationless; +import java.util.HashMap; +import java.util.Map; + import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.persistence.EntityManager; @@ -9,8 +12,6 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; -import java.util.HashMap; -import java.util.Map; /** * @author Emmanuel Bernard emmanuel@hibernate.org @@ -38,8 +39,7 @@ public Map create() { em.clear(); gift = em.find(Gift.class, gift.getId()); map.put("jpa", "Roller coaster".equals(gift.getName()) ? "OK" : "Boooo"); - } - catch (Exception e) { + } catch (Exception e) { map.put("exception message", e.getMessage()); } return map; @@ -60,13 +60,11 @@ public Map createUserTransaction() { gift = em.find(Gift.class, gift.getId()); transaction.commit(); map.put("jpa", "Roller coaster".equals(gift.getName()) ? "OK" : "Boooo"); - } - catch (Exception e) { + } catch (Exception e) { map.put("exception message", e.getMessage()); try { transaction.rollback(); - } - catch (Exception ne) { + } catch (Exception ne) { //swallow the bastard } } @@ -85,4 +83,12 @@ public Map get() { return map; } + @GET + @Produces(MediaType.TEXT_PLAIN) + @Transactional + @Path("/cake") + public String getCake() { + Cake c = (Cake) em.createQuery("from Cake").getSingleResult(); + return c.getType(); + } } diff --git a/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/Cake.java b/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/Cake.java new file mode 100644 index 0000000000000..4355dfdf3431e --- /dev/null +++ b/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/Cake.java @@ -0,0 +1,34 @@ +package org.shamrock.jpa.tests.configurationless; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +@Entity +@Table(uniqueConstraints = @UniqueConstraint(name = "typeConstraint", columnNames = "type")) + +public class Cake { + private Long id; + private String type; + + @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="cakeSeq") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/StartupCakeManager.java b/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/StartupCakeManager.java new file mode 100644 index 0000000000000..265c705c4834d --- /dev/null +++ b/integration-tests/jpa/src/main/java/org/shamrock/jpa/tests/configurationless/StartupCakeManager.java @@ -0,0 +1,26 @@ +package org.shamrock.jpa.tests.configurationless; + +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; + +import org.jboss.shamrock.runtime.StartupEvent; + +/** + * creates a chocolate cake on startup to make sure JPA works in the startup event + */ +@Dependent +public class StartupCakeManager { + + @Inject + EntityManager entityManager; + + @Transactional + public void startup(@Observes StartupEvent startupEvent) { + Cake c = new Cake(); + c.setType("Chocolate"); + entityManager.persist(c); + } +} diff --git a/integration-tests/jpa/src/test/java/org/shamrock/jpa/tests/configurationless/JPAOnStartupTest.java b/integration-tests/jpa/src/test/java/org/shamrock/jpa/tests/configurationless/JPAOnStartupTest.java new file mode 100644 index 0000000000000..2ca6ae1b0c48f --- /dev/null +++ b/integration-tests/jpa/src/test/java/org/shamrock/jpa/tests/configurationless/JPAOnStartupTest.java @@ -0,0 +1,21 @@ +package org.shamrock.jpa.tests.configurationless; + +import static org.hamcrest.core.StringContains.containsString; + +import org.jboss.shamrock.test.junit.ShamrockTest; +import org.junit.jupiter.api.Test; + +import io.restassured.RestAssured; + +/** + * @author Emmanuel Bernard emmanuel@hibernate.org + */ +@ShamrockTest +public class JPAOnStartupTest { + + @Test + public void testStartupJpa() { + RestAssured.when().get("/jpa-test/cake").then() + .body(containsString("Chocolate")); + } +}