简体   繁体   中英

Transaction propagation in spring integration test with JPA and JMS

Please, help me understand the following.

I have an spring integration test, which I'm trying to test the method of ProcessCommentsDao class :

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:testContext.xml"})
@Transactional()
public class ParseCommentsTest {

  @Resource
  private ProcessCommentsDao processCommentsDao;

  @Test
  public void testJMS() throws Exception {

     // Test data creation
     .........................

     processCommentsDao.parseComments();
   }
 }

In the method parseComments(), I get a list of entities, then each entity are processed through the Spring's JMS MessageListener implementation:

@Service
public class ProcessCommentsDaoImpl extends BaseDao implements IProcessCommentsDao {

    private static final int PARSE_COMMENTS_COUNT_LIMIT = 100;
    @Autowired
    private JmsTemplate jmsTemplate;
    @Autowired
    private Queue parseCommentsDestination;

    @Override
    public void parseComments() {

      List<Comment> comments = attributeDao.findTopUnparsedComments(PARSE_COMMENTS_COUNT_LIMIT);

      for (Comment comment : comments) {
        jmsTemplate.convertAndSend(parseCommentsDestination, comment);
      }
    }
}

Implementation of the MessageListener as follows:

@Component
public class QueueListener implements MessageListener {

  @PersistenceContext
  private  EntityManager em;

  @Transactional()
  public void onMessage(final Message message) {
      try {
         if (message instanceof ObjectMessage) {
            final ObjectMessage objMessage = (ObjectMessage) message;
            Comment comment = (Comment) objMessage.getObject();

            //...Some logic ...

             comment = em.find(Comment.class, comment.getId());
             comment.setPosStatus(ParsingType.PROCESSED);
             em.merge(comment);

              //...Some logic ...

    } catch (final JMSException e) {
        e.printStackTrace();
    }
}

}

As a result, the method em.find (Comment.class, comment.getId ()) returns null, because the data were created in another thread and the current thread does not know anything about this transaction. Is there a way to set up a transaction propagation so that MessageListener method seen entyties who were created in the main thread, in which the test method run?

I found a next solution of my problem. Test data are generated in a separate transaction, which created manualy:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:testContext.xml"})
@Transactional()
public class ParseCommentsTest {

  @Resource
  private ProcessCommentsDao processCommentsDao;
  @Autowired
  private PlatformTransactionManager transactionManager;

  @Before
  public void tearUp() {
    createTestData();
  }

  @Test
  @Rollback(false)
  public void testJMS() throws Exception {
   processCommentsDao.parseComments();
  }

  @After
  public void tearDown() {
   removeTestData();
 }

 private void createTestData() {
    TransactionTemplate txTemplate = new TransactionTemplate(transactionManager);
    txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    txTemplate.execute(new TransactionCallback<Object>() {

        @Override
        public Object doInTransaction(TransactionStatus status) {
            try {
            // Test data creation
            ...........................
        }
    });
  }
}

In the method ProcessCommentsDaoImpl.parseComments() is implemented waiting for the completion of all the asynchronous JMS request. The main thread has finished its work until all entities are processed:

@Service
public class ProcessCommentsDaoImpl extends BaseDao implements IProcessCommentsDao {

  private static final int PARSE_COMMENTS_COUNT_LIMIT = 100;
  @Autowired
  private JmsTemplate jmsTemplate;
  @Autowired
  private Queue parseCommentsDestination;

  @Override
  public void parseComments() {

    List<Comment> comments =    attributeDao.findTopUnparsedComments(PARSE_COMMENTS_COUNT_LIMIT);

    for (Comment comment : comments) {
     jmsTemplate.convertAndSend(parseCommentsDestination, comment);
    }
    // Wait all request procesed
    waitParseCommentsProcessed(comments);
   }

  @Override
  @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
  public void parseComment(Long commentId) {
     ......................
     //Some logic
    ....................
    }
}

And refactoring of the MessageListener as follows:

public class ParseCommentQueueListener {

  private static Logger log = Logger.getLogger(ParseCommentQueueListener.class);

  @Resource(name = SpringContext.DAO_PROCESS_COMMENTS)
  private IProcessCommentsDao processCommentsDao;

  public Object receive(ObjectMessage message) {
    try {
        Long id = (Long) message.getObject();
        processCommentsDao.parseComment(id);
    } catch (JMSException ex) {
        log.error(ex.toString());
    }
    return message;
  }
} 

Xml configuration of ParseCommentQueueListener is follows:

<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="com.provoxlabs.wordminer.parsing.ParseCommentQueueListener"/>
    </constructor-arg>
    <property name="defaultListenerMethod" value="receive"/>
    <property name="defaultResponseDestination" ref="parseCommentsStatusDestination"/>
    <!-- we don't want automatic message context extraction -->
    <property name="messageConverter">
        <null/>
    </property>
</bean>

As a result, the method em.find (Comment.class, comment.getId ()) returns null, because the data were created in another thread and the current thread does not know anything about this transaction.

To be more exact, the message listener runs in a separate transaction, and it doesn't see the data created by ParseCommentsTest.testJMS because that method did not commit.

What's more important, your test is not written correctly. It has a race condition: the calls to jmsTemplate.convertAndSend() are asynchronous, so the logic in QueueListener.messageListener() may be invoked after the test method completes (and rolls back the changes it has made). This test may yield different results each time it's run.

Your code is not easily testable, either. Consider extracting processing logic from the onMessage() method to a POJO and test it separately.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM