使用JPA和JMS进行Spring集成测试中的事务传播

时间:2011-12-26 19:33:17

标签: spring jpa jms

请帮助我理解以下内容。

我有一个spring集成测试,我正在尝试测试ProcessCommentsDao类的方法:

@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();
   }
 }

在方法parseComments()中,我得到一个实体列表,然后通过Spring的JMS MessageListener实现处理每个实体:

@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);
      }
    }
}

MessageListener的实现如下:

@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();
    }
}

}

因此,方法em.find(Comment.class,comment.getId())返回null,因为数据是在另一个线程中创建的,当前线程对此事务一无所知。有没有办法设置事务传播,以便MessageListener方法看到在主线程中创建的entyty,测试方法在哪个运行?

2 个答案:

答案 0 :(得分:1)

我找到了问题的下一个解决方案。测试数据是在一个单独的事务中生成的,该事务是手动创建的:

@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
            ...........................
        }
    });
  }
}

在方法ProcessCommentsDaoImpl.parseComments()中实现等待所有异步JMS请求的完成。主线程已完成其工作,直到处理完所有实体:

@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
    ....................
    }
}

重构MessageListener,如下所示:

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;
  }
} 

ParseCommentQueueListener的Xml配置如下:

<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>

答案 1 :(得分:0)

  

因此,方法em.find(Comment.class,comment.getId())返回null,因为数据是在另一个线程中创建的,当前线程对此事务一无所知。

更确切地说,消息侦听器在单独的事务中运行,并且它看不到ParseCommentsTest.testJMS创建的数据,因为该方法没有提交。

更重要的是,您的测试编写不正确。它有一个竞争条件:对jmsTemplate.convertAndSend()的调用是异步的,因此在测试方法完成后可以调用QueueListener.messageListener()中的逻辑(并回滚它所做的更改)。每次运行时,此测试可能会产生不同的结果。

您的代码也不容易测试。考虑将处理逻辑从onMessage()方法提取到POJO并单独测试。