2017-03-16 3 views
0

Ich habe eine Spring Boot-App, die JMS verwendet, um eine Verbindung zu einer Warteschlange herzustellen und auf eingehende Nachrichten zu warten. In der App habe ich einen Integrationstest, der einige Nachrichten an eine Warteschlange sendet und dann sicherstellt, dass die Dinge, die passieren sollen, wenn der Hörer eine neue Nachricht empfängt, tatsächlich passieren.EntityManagerFactory geschlossen, nachdem der Kontext mit @DirtiesContext neu geladen wurde

Ich habe meine Testklasse mit @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) annotiert, um sicherzustellen, dass meine Datenbank nach jedem Test sauber ist. Jeder Test besteht, wenn er isoliert ausgeführt wird. Allerdings, wenn sie alle zusammen nach dem ersten Test bestanden erfolgreich die nächste Test nicht besteht, mit Ausnahme unten ausgeführt wird, wenn der im Test befindlichen Code versucht, eine Einheit in der Datenbank zu speichern:

org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: EntityManagerFactory is closed 
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:431) ~[spring-orm-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:447) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:277) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at com.sun.proxy.$Proxy95.handleWorkflowEvent(Unknown Source) ~[na:na] 
    at com.mottmac.processflow.infra.jms.EventListener.onWorkflowEvent(EventListener.java:51) ~[classes/:na] 
    at com.mottmac.processflow.infra.jms.EventListener.onMessage(EventListener.java:61) ~[classes/:na] 
    at org.apache.activemq.ActiveMQMessageConsumer.dispatch(ActiveMQMessageConsumer.java:1401) [activemq-client-5.14.3.jar:5.14.3] 
    at org.apache.activemq.ActiveMQSessionExecutor.dispatch(ActiveMQSessionExecutor.java:131) [activemq-client-5.14.3.jar:5.14.3] 
    at org.apache.activemq.ActiveMQSessionExecutor.iterate(ActiveMQSessionExecutor.java:202) [activemq-client-5.14.3.jar:5.14.3] 
    at org.apache.activemq.thread.PooledTaskRunner.runTask(PooledTaskRunner.java:133) [activemq-client-5.14.3.jar:5.14.3] 
    at org.apache.activemq.thread.PooledTaskRunner$1.run(PooledTaskRunner.java:48) [activemq-client-5.14.3.jar:5.14.3] 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [na:1.8.0_77] 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [na:1.8.0_77] 
    at java.lang.Thread.run(Unknown Source) [na:1.8.0_77] 
Caused by: java.lang.IllegalStateException: EntityManagerFactory is closed 
    at org.hibernate.jpa.internal.EntityManagerFactoryImpl.validateNotClosed(EntityManagerFactoryImpl.java:367) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.jpa.internal.EntityManagerFactoryImpl.internalCreateEntityManager(EntityManagerFactoryImpl.java:316) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final] 
    at org.hibernate.jpa.internal.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:286) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final] 
    at org.springframework.orm.jpa.JpaTransactionManager.createEntityManagerForTransaction(JpaTransactionManager.java:449) ~[spring-orm-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:369) ~[spring-orm-4.3.6.RELEASE.jar:4.3.6.RELEASE] 
    ... 17 common frames omitted 

Meine Testklasse:

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = { TestGovernance.class }) 
@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) 
public class ActivitiIntegrationTest 
{ 
    private static final String TEST_PROCESS_KEY = "oneTaskProcess"; 
    private static final String FIRST_TASK_KEY = "theTask"; 
    private static final String NEXT_TASK_KEY = "nextTask"; 

    @Autowired 
    private JmsTemplate jms; 

    @Autowired 
    private WorkflowEventRepository eventRepository; 

    @Autowired 
    private TaskService taskService; 

    @Test 
    public void workFlowEventForRunningTaskMovesItToTheNextStage() throws InterruptedException 
    { 
     sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY); 

     Task activeTask = getActiveTask();   
     assertThat(activeTask.getTaskDefinitionKey(), is(FIRST_TASK_KEY)); 

     sendMessageToUpdateExistingTask(activeTask.getProcessInstanceId(), FIRST_TASK_KEY); 

     Task nextTask = getActiveTask();   
     assertThat(nextTask.getTaskDefinitionKey(), is(NEXT_TASK_KEY)); 
    } 

    @Test 
    public void newWorkflowEventIsSavedToDatabaseAndKicksOffTask() throws InterruptedException 
    { 
     sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY); 

     assertThat(eventRepository.findAll(), hasSize(1)); 
    } 

    @Test 
    public void newWorkflowEventKicksOffTask() throws InterruptedException 
    { 
     sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY); 

     Task activeTask = getActiveTask();   
     assertThat(activeTask.getTaskDefinitionKey(), is(FIRST_TASK_KEY)); 
    } 


    private void sendMessageToUpdateExistingTask(String processId, String event) throws InterruptedException 
    { 
     WorkflowEvent message = new WorkflowEvent(); 
     message.setRaisedDt(ZonedDateTime.now()); 
     message.setEvent(event); 
     // Existing 
     message.setIdWorkflowInstance(processId); 
     jms.convertAndSend("workflow", message); 
     Thread.sleep(5000); 
    } 

    private void sendMessageToCreateNewInstanceOfProcess(String event) throws InterruptedException 
    { 
     WorkflowEvent message = new WorkflowEvent(); 
     message.setRaisedDt(ZonedDateTime.now()); 
     message.setEvent(event); 
     jms.convertAndSend("workflow", message); 
     Thread.sleep(5000); 
    } 

    private Task getActiveTask() 
    { 
     // For some reason the tasks in the task service are hanging around even 
     // though the context is being reloaded. This means we have to get the 
     // ID of the only task in the database (since it has been cleaned 
     // properly) and use it to look up the task. 
     WorkflowEvent workflowEvent = eventRepository.findAll().get(0); 
     Task activeTask = taskService.createTaskQuery().processInstanceId(workflowEvent.getIdWorkflowInstance().toString()).singleResult(); 
     return activeTask; 
    } 

} 

Das Verfahren, das die Ausnahme in der Anwendung (repository ist nur ein Standard-Spring Data CrudRepository) wirft:

@Override 
    @Transactional 
    public void handleWorkflowEvent(WorkflowEvent event) 
    { 
     try 
     { 
      logger.info("Handling workflow event[{}]", event); 

      // Exception is thrown here: 
      repository.save(event); 

      logger.info("Saved event to the database [{}]", event); 
      if(event.getIdWorkflowInstance() == null) 
      { 
       String newWorkflow = engine.newWorkflow(event.getEvent(), event.getVariables()); 
       event.setIdWorkflowInstance(newWorkflow); 
      } 
      else 
      { 
       engine.moveToNextStage(event.getIdWorkflowInstance(), event.getEvent(), event.getVariables()); 
      } 
     } 
     catch (Exception e) 
     { 
      logger.error("Error while handling workflow event:" , e); 
     } 
    } 

Meine Testkonfiguration Klasse:

@SpringBootApplication 
@EnableJms 
@TestConfiguration 
public class TestGovernance 
{ 
    private static final String WORKFLOW_QUEUE_NAME = "workflow"; 

    @Bean 
    public ConnectionFactory connectionFactory() 
    { 
     ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false"); 
     return connectionFactory; 
    } 

    @Bean 
    public EventListenerJmsConnection connection(ConnectionFactory connectionFactory) throws NamingException, JMSException 
    { 
     // Look up ConnectionFactory and Queue 
     Destination destination = new ActiveMQQueue(WORKFLOW_QUEUE_NAME); 

     // Create Connection 
     Connection connection = connectionFactory.createConnection(); 

     Session listenerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); 
     MessageConsumer receiver = listenerSession.createConsumer(destination); 

     EventListenerJmsConnection eventListenerConfig = new EventListenerJmsConnection(receiver, connection); 
     return eventListenerConfig; 
    } 
} 

Die JMS-Nachricht Hörer (nicht sicher, ob das hilft):

/** 
* Provides an endpoint which will listen for new JMS messages carrying 
* {@link WorkflowEvent} objects. 
*/ 
@Service 
public class EventListener implements MessageListener 
{ 
    Logger logger = LoggerFactory.getLogger(EventListener.class); 

    private WorkflowEventHandler eventHandler; 

    private MessageConverter messageConverter; 

    private EventListenerJmsConnection listenerConnection; 

    @Autowired 
    public EventListener(EventListenerJmsConnection listenerConnection, WorkflowEventHandler eventHandler, MessageConverter messageConverter) 
    { 
     this.eventHandler = eventHandler; 
     this.messageConverter = messageConverter; 
     this.listenerConnection = listenerConnection; 
    } 

    @PostConstruct 
    public void setUpConnection() throws NamingException, JMSException 
    { 
     listenerConnection.setMessageListener(this); 
     listenerConnection.start(); 
    } 

    private void onWorkflowEvent(WorkflowEvent event) 
    { 
     logger.info("Recieved new workflow event [{}]", event); 
     eventHandler.handleWorkflowEvent(event); 
    } 

    @Override 
    public void onMessage(Message message) 
    { 
     try 
     { 
      message.acknowledge(); 
      WorkflowEvent fromMessage = (WorkflowEvent) messageConverter.fromMessage(message); 
      onWorkflowEvent((WorkflowEvent) fromMessage); 
     } 
     catch (Exception e) 
     { 
      logger.error("Error: ", e); 
     } 
    } 
} 

Ich habe versucht, das Hinzufügen @Transactional' to the test methods and removing it from the code under test and various combinations with no success. I've also tried adding various test execution listeners and I still can't get it to work. If I remove the @ DirtiesContext` dann geht die Ausnahme entfernt und alle Tests laufen ohne Ausnahme (sie scheitern jedoch mit Assertionsfehlern, wie ich es erwarten würde).

Jede Hilfe würde sehr geschätzt werden. Meine Suchen haben bisher nichts ergeben, alles deutet darauf hin, dass @DirtiesContext funktionieren sollte.

+0

Welches ist ein sehr schlechter Grund, Schmutzige Kontext zu verwenden. Tun Sie das nicht, es ist langsam und wenn Ihre Testsuite wächst und die Anzahl der Beans wird es noch langsamer. Also nicht. Setzen Sie Ihren Test auf "@ Transactional" und der Standardwert ist, dass die Daten nach dem Test zurückgesetzt werden. Sie können fehlschlagen, da nichts festgeschrieben ist, so dass Sie möglicherweise den EntityManager injizieren müssen und einen EntityManager.flush() zwischen Methodenaufrufen einfügen müssen, um einen Commit zu simulieren. Sie verwenden sogar einen SpringBootTest (gerade bemerkt), was es wahrscheinlich noch schrecklicher macht, Ihre gesamte Anwendung für den Test neu zu starten. –

+0

Zusätzlich würde ich sagen, dass Ihr JMS-Setup fehlerhaft ist und Sie es viel einfacher machen können. Indem Sie einfach Ihre 'onMessage' mit' @ JmsLIstener' und einigen Queue-Namen implementieren, erledigt Spring den Rest. –

+0

Ich hatte ursprünglich '@ JmsListener', aber etwas über die Art, wie die automatische Konfiguration es einrichtet, bedeutete, dass es in der Produktion nicht funktionieren würde, wenn es mit einem Microsoft Service Bus kombiniert würde. –

Antwort

0

Mit @DirtiesContext ist dies eine schreckliche Idee (imho), was Sie tun sollten, ist Ihre Tests @Transactional. Ich würde auch vorschlagen, die Thread.sleep zu entfernen und stattdessen etwas wie awaitility zu verwenden.

In der Theorie, wenn Sie eine Abfrage ausführen, sollten alle ausstehenden Änderungen festgeschrieben werden, so dass Sie die Wartezeit bis höchstens 6 Sekunden verwenden können, um zu sehen, ob etwas in der Datenbank persistent ist. Wenn das nicht funktioniert, können Sie versuchen, vor der Abfrage einen Flush hinzuzufügen.

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = { TestGovernance.class }) 
@Transactional 
public class ActivitiIntegrationTest { 

    private static final String TEST_PROCESS_KEY = "oneTaskProcess"; 
    private static final String FIRST_TASK_KEY = "theTask"; 
    private static final String NEXT_TASK_KEY = "nextTask"; 

    @Autowired 
    private JmsTemplate jms; 

    @Autowired 
    private WorkflowEventRepository eventRepository; 

    @Autowired 
    private TaskService taskService; 

    @Autowired 
    private EntityManager em; 

    @Test 
    public void workFlowEventForRunningTaskMovesItToTheNextStage() throws InterruptedException 
    { 
     sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY); 

     await().atMost(6, SECONDS).until(getActiveTask() != null); 

     Task activeTask = getActiveTask()); 
     assertThat(activeTask.getTaskDefinitionKey(), is(FIRST_TASK_KEY)); 

     sendMessageToUpdateExistingTask(activeTask.getProcessInstanceId(), FIRST_TASK_KEY); 

     Task nextTask = getActiveTask();   
     assertThat(nextTask.getTaskDefinitionKey(), is(NEXT_TASK_KEY)); 
    } 

    private Task getActiveTask() 
    { 
     em.flush(); // simulate a commit 
     // For some reason the tasks in the task service are hanging around even 
     // though the context is being reloaded. This means we have to get the 
     // ID of the only task in the database (since it has been cleaned 
     // properly) and use it to look up the task. 
     WorkflowEvent workflowEvent = eventRepository.findAll().get(0); 
     Task activeTask = taskService.createTaskQuery().processInstanceId(workflowEvent.getIdWorkflowInstance().toString()).singleResult(); 
     return activeTask; 
    } 

} 

Sie benötigen könnten/möchten Ihre getActiveTask ein wenig polieren zu können return null oder vielleicht diese Änderung macht es noch verhalten, wie Sie es erwartet zu tun.

Ich habe nur eine einzige Methode die anderen können Sie wahrscheinlich selbst herausfinden. Ihr Gewinn mit diesem Ansatz ist wahrscheinlich 2-fach, 1 wird nicht mehr 5 Sekunden warten, aber weniger und Sie müssen nicht Ihre gesamte Anwendung zwischen den Tests neu laden. Beides sollte Ihre Tests beschleunigen.

+0

Ich habe die Tests aktualisiert, um '@ Transactional' zu verwenden, aber es scheint nicht die Datenbank zu bereinigen. Ich kann in dem Debugprotokoll sehen, dass es etwas tut: 2017-03-16 19: 24: 11.216 DEBUG 12348 --- [Haupt] osorm.jpa.JpaTransactionManager: Rollback JPA-Transaktion auf EntityManager [org.hibernate.jpa .internal.EntityManagerImpl @ 18c820d2] 'aber wenn ich alle Tests zusammen durchführe, kommt es zur letzten Testmethode und es gibt 4 Elemente in der Datenbank. Der Test läuft isoliert ab und ich weiß, dass es definitiv nicht der Test ist, der alle in die Datenbank bringt. –

+0

Ich schaue mir die Logs an und frage mich, ob die Tatsache, dass der getestete Code in einem anderen Thread läuft, irgendetwas damit zu tun hat. Die JMS-Nachrichtenverarbeitung läuft im Thread '[Session Task-1]' und die Tests laufen im Thread '[main]'. Alles scheint jedoch in der richtigen Reihenfolge zu geschehen. –

+0

Das ist in der Tat, was passiert, da Transaktionen (und der zugehörige 'EntityManager') Thread-basiert sind, die nicht funktionieren werden. Was ist natürlich die ganze Idee von JMS :). Ich werde die Antwort entfernen, da es in dieser Hinsicht keinen Sinn ergibt (ich habe vergessen, dass Sie tatsächlich JMS in Ihren Tests verwenden). Nichtsdestotrotz würde ich immer noch die Verwendung von ** Abwarten ** vorschlagen. –

Verwandte Themen