3

I have an issue with JavaMailSender which sends double or triple messages.

In Controller:

while(size > 0) //# of emails I want to send, for example 5
{
    Item item= itemRepository.findFirstBySentFalseAndValidTrue(); //getting only emails that are not sent!
    item.getData(); // API call that adds some info to the item, takes between 5-15 seconds per item    
    if(item.isValid())
    {

            item.setHeaderName(dataService.setHeader(item.getStore(), LocaleContextHolder.getLocale()));
            item.setAddress(dataService.getAddress(item.getStore(), LocaleContextHolder.getLocale()) + item.getId());

            String email = dataService.getEmail(item.getStore());
            String footer = dataService.getFooter(item.getStore(), LocaleContextHolder.getLocale());

            if(!item.isSent()) //I check if sent here but still get double messages!
            {
                if(mailSenderService.sendEmail(item, "emails/templates/item", email, footer))
                {
                    item.setSent(true);
                }
            }

    itemRepository.save(item);
    size--;
    }
}

In my email send service class:

    final MimeMessage mimeMessage = this.mailSender.createMimeMessage();
    final MimeMessageHelper message = new MimeMessageHelper(mimeMessage,true, "UTF-8");

    Context ctx =  new Context(LocaleContextHolder.getLocale());
    message.setFrom(email);
    message.setTo("test@gmail.com");

    ResourceBundle labels = ResourceBundle.getBundle("messages", LocaleContextHolder.getLocale());
    mimeMessage.setHeader("Content-Type", encodingOptions);
    mimeMessage.setSubject(labels.getString("email.itme.title"), "UTF-8");


    ctx.setVariable("data", item);
    ctx.setVariable("footer", footer);

    try{        
        String processedTemplate =  templateEngine.process(template, ctx);
        mimeMessage.setContent(processedTemplate, encodingOptions);
        this.mailSender.send(mimeMessage);

    } catch(Exception e){
      e.printStackTrace();
    }

    return true;
}

I just assume it's an issue in my email sending class, but maybe the api call is too slow that the method execution takes too long and sends duplicate messages? Sometimes, when I send only few emails (e.g., 3) it's ok and it doesn't send duplicates. When I want to send like 20 or 30, everything takes around 15 minutes and I get duplicates (I cannot control API's response time, sometimes it's faster sometime its super slow). Is there any way I can debug it and see which method is called when and why I would get these duplicates?

Edit: Thats my itemRepository implementation:

 @Repository
 @Qualifier(value = "itemRepository")
 @Transactional
 public interface itemRepository extends CrudRepository<Item, Long> {

 public Item save(Item item);
 public Item findFirstBySentFalseAndValidTrue();
 }
Damian
  • 461
  • 1
  • 7
  • 18
  • your controller code run multi times at same time? maybe the request send to backend multi times at same time? add synchronized – andy Jul 20 '15 at 13:03
  • I was not familiar with synchronized, exploring it now. so chaging my controller method to: public synchronized ModelAndView sendItems(@PathVariable("size") int size) throws Exception { should be it? – Damian Jul 20 '15 at 13:15
  • have a try, and how you call your controller, from UI? – andy Jul 20 '15 at 13:17
  • nope, controller method is called from crontab or just in the browser by URL for testing – Damian Jul 20 '15 at 13:21
  • How is `itemRepository.save(item)` implemented? Maybe some sort of a race condition between the `save` not persisted and the `findFirstBySentFalseAndValidTrue()` fetching stale data? – Dirk Lachowski Jul 20 '15 at 13:38
  • editted my question with the CrudRepo interface im using for querying DB – Damian Jul 20 '15 at 13:43
  • Dirk Lachowski, are you familiar with Lock annotations? Would it make sense to put @Lock(LockModeType.PESSIMISTIC_READ) on my save method to block dirty-read on the repo in the next loop iteration? – Damian Jul 20 '15 at 14:44

1 Answers1

0

This part:

while(size > 0) //# of emails I want to send, for example 5
{
    Item item= itemRepository.findFirstBySentFalseAndValidTrue(); //getting only emails that are not sent!

Doesn't make any sense. You're specifying a static number of emails you want to send, then never decrementing this value, then calling one email from the repository that hasn't been sent, and doing something with it. This is bad design. You are likely getting duplicates because there is nothing preventing your program from retrieving the same record repeatedly with this line of code: Item item= itemRepository.findFirstBySentFalseAndValidTrue(); before it has had a chance to update the record.

What you should do instead is query your repository for a list of unsent emails matching some criteria and then performing a loop on this returned list to send the email and then update the record in the repository.

Daniel Cottone
  • 4,257
  • 24
  • 39
  • Sorry, my mistake, I forgot to copy that decrement statement (it was always in my code). Updated my question. I used to query the DB for a list of the items I need to send and then foop on the resoultset. I was getting duplicates once in a while too – Damian Jul 20 '15 at 13:31