请帮助我理解以下内容。
我有一个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,测试方法在哪个运行?
答案 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并单独测试。