简体   繁体   English

如何从Java EE批处理作业发送电子邮件

[英]How to send emails from a Java EE Batch Job

I have a requirement to process a list of large number of users daily to send them email and SMS notifications based on some scenario. 我需要每天处理大量用户列表,以便根据某些情况向他们发送电子邮件和SMS通知。 I am using Java EE batch processing model for this. 我为此使用Java EE批处理模型。 My Job xml is as follows: 我的Job xml如下:

<step id="sendNotification">
    <chunk item-count="10" retry-limit="3">
        <reader ref="myItemReader"></reader>
        <processor ref="myItemProcessor"></processor>
        <writer ref="myItemWriter"></writer>
        <retryable-exception-classes>
            <include class="java.lang.IllegalArgumentException"/>
        </retryable-exception-classes>
    </chunk>
</step>

MyItemReader's onOpen method reads all users from database, and readItem() reads one user at a time using list iterator. MyItemReader的onOpen方法从数据库中读取所有用户,而readItem()使用列表迭代器一次读取一个用户。 In myItemProcessor, the actual email notification is sent to user, and then the users are persisted in database in myItemWriter class for that chunk. 在myItemProcessor中,实际的电子邮件通知发送给用户,然后将用户保留在该块的myItemWriter类中的数据库中。

@Named
public class MyItemReader extends AbstractItemReader {

    private Iterator<User> iterator = null;
    private User lastUser;

    @Inject
    private MyService service;

    @Override
    public void open(Serializable checkpoint) throws Exception {
        super.open(checkpoint);

        List<User> users = service.getUsers();
        iterator = users.iterator();

        if(checkpoint != null) {
            User checkpointUser = (User) checkpoint;
            System.out.println("Checkpoint Found: " + checkpointUser.getUserId());
            while(iterator.hasNext() && !iterator.next().getUserId().equals(checkpointUser.getUserId())) {
                System.out.println("skipping already read users ... ");
            }
        }
    }

    @Override
    public Object readItem() throws Exception {

        User user=null;

        if(iterator.hasNext()) {
            user = iterator.next();
            lastUser = user;
        }
        return user;
    }

    @Override
    public Serializable checkpointInfo() throws Exception {
        return lastUser;
    }
}

My problem is that checkpoint stores the last record that was executed in the previous chunk. 我的问题是检查点存储了在上一个块中执行的最后一条记录。 If I have a chunk with next 10 users, and exception is thrown in myItemProcessor of the 5th user, then on retry the whole chunck will be executed and all 10 users will be processed again. 如果我的下一个10个用户中有一个大块,并且第5个用户的myItemProcessor中抛出异常,则在重试时将执行整个块,并再次处理所有10个用户。 I don't want notification to be sent again to the already processed users. 我不希望将通知再次发送给已经处理的用户。

Is there a way to handle this? 有办法解决吗? How should this be done efficiently? 应该如何有效地做到这一点?

Any help would be highly appreciated. 任何帮助将不胜感激。 Thanks. 谢谢。

Your current item processor is doing something outside the chunk transaction scope, which has caused the application state to be out of sync. 您当前的项目处理器正在执行块事务范围之外的操作,这导致应用程序状态不同步。 If your requirement is to send out emails only after all items in a chunk have successfully completed, then you can move the emailing part to a ItemWriterListener.afterWrite(items) . 如果您的要求是仅在成功完成一个块中的所有项目之后才发送电子邮件,则可以将电子邮件发送部分移至ItemWriterListener.afterWrite(items)

I'm going to build on the comments from @cheng. 我将以@cheng的评论为基础。 My credit to him here, and hopefully my answer provides additional value in organizing and presenting the options usefully. 我在此感谢他,希望我的回答能为组织和有效介绍这些选项提供额外的价值。

Answer: Queue up messages for another MDB to get dispatched to send emails 答案:将消息排队等待另一个MDB发送,以发送电子邮件

Background: 背景:

As @cheng pointed out, a failure means the entire transaction is rolled back, and the checkpoint doesn't advance. 正如@cheng指出的那样,失败意味着整个事务都会回滚,并且检查点不会前进。

So how to deal with the fact that your chunk has sent emails to some users but not all? 那么,如何处理您的信息块已向某些用户而非全部用户发送电子邮件的事实呢? (You might say it rolled back but with "side effects".) (您可能会说它回滚了,但有“副作用”。)

So we could restate your question then as: How to send email from a batch chunk step? 因此,我们可以将您的问题重新声明为: 如何从批处理步骤发送电子邮件?

Well, assuming you had a way to send emails through an transactional API (implementing XAResource , etc.) you could use that API. 好吧,假设您有一种方法可以通过事务性API发送电子邮件(实现XAResource等),则可以使用该API。

Assuming you don't, I would do a transactional write to a JMS queue, and then send the emails with a separate MDB (as @cheng suggested in one of his comments). 假设您不这样做,我将对JMS队列进行事务性写入,然后使用单独的MDB发送电子邮件(如@cheng在他的评论之一中建议的那样)。

Suggested Alternative: Use ItemWriter to send messages to JMS queue, then use separate MDB to actually send the emails 建议的替代方法:使用ItemWriter将消息发送到JMS队列,然后使用单独的MDB实际发送电子邮件

With this approach you still gain efficiency by batching the processing and the updates to your DB (you were only sending the emails one at a time anyway), and you can benefit from simple checkpointing and restart without having to write complicated error handling. 使用这种方法,您仍然可以通过批量处理和更新数据库来获得效率(无论如何一次只能发送一封电子邮件),并且可以从简单的检查点和重新启动中受益,而不必编写复杂的错误处理程序。

This is also likely to be reusable as a pattern across batch jobs and outside of batch even. 这也有可能作为批处理作业甚至批处理之外的模式重用。

Other alternatives 其他选择

Some other ideas that I don't think are as good, listed for the sake of discussion: 为了讨论起见,列出了一些我认为不太理想的其他想法:

Add batch application logic tracking users emailed (with ItemProcessListener) 添加跟踪通过电子邮件发送用户的批处理应用程序逻辑(使用ItemProcessListener)

You could build your own list of either/both successful/failed emails using the ItemProcessListener methods: afterProcess and onProcessError . 您可以使用ItemProcessListener方法构建自己的成功/失败电子邮件列表,包括afterProcessonProcessError

On restart, then, you could know which users had been emailed in the current chunk, which we are re-positioned to since the entire chunk rolled back, even though some emails have already been sent. 然后,在重新启动时,您可以知道当前块中已向哪些用户发送了电子邮件,自从整个块回滚以来,我们已经重新定位了哪些用户,即使已经发送了一些电子邮件。

This certainly complicates your batch logic, and you also have to persist this success or failure list somehow. 这无疑使您的批处理逻辑变得复杂,并且您还必须以某种方式保留此成功或失败列表。 Plus this approach is probably highly specific to this job (as opposed to queuing up for an MDB to process). 另外,这种方法可能非常针对此工作(而不是排队等待MDB处理)。

But it's simpler in that you have a single batch job without the need for a messaging provider and a separate app component. 但这很简单,因为您只有一个批处理作业,而无需消息传递提供程序和单独的应用程序组件。

If you go this route you might want to use a combination of both a skippable and a "no-rollback" retryable exception. 如果您走这条路线,则可能要同时使用可跳过和“无回滚”可重试异常。

single-item chunk 单项块

If you define your chunk with item-count="1" , then you avoid complicated checkpointing and error handling code. 如果使用item-count =“ 1”定义块,则可以避免复杂的检查点和错误处理代码。 You sacrifice efficiency though, so this would only make sense if the other aspects of batch were very compelling: eg scheduling and management of jobs through a common interface, the ability to restart at the failing step within a job 但是,您牺牲了效率,因此只有在批处理的其他方面非常引人注目时,这才有意义:例如,通过通用接口进行作业的调度和管理,在作业失败的步骤中重新启动的能力

If you were to go this route, you might want to consider defining socket and timeout exceptions as "no-rollback" exceptions (using 如果要走这条路线,您可能需要考虑将套接字和超时异常定义为“无回滚”异常(使用 ) since there's nothing to be gained from rolling back, and you might want to retry on a network timeout issue. ),因为回滚并没有任何好处,您可能想重试网络超时问题。

Since you specifically mentioned efficiency, I'm guessing this is a bad fit for you. 既然您特别提到了效率,我想这对您来说不合适。

use a Transaction Synchronization 使用事务同步

This could work perhaps, but the batch API doesn't especially make this easy, and you still could have a case where the chunk completes but one or more email sends fail. 这也许可以工作,但是批处理API并没有使它变得特别容易,并且您仍然可能遇到块已完成但一个或多个电子邮件发送失败的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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