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.