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

如何从JavaEE批处理作业发送电子邮件,java,batch-processing,java-ee-7,jsr352,Java,Batch Processing,Java Ee 7,Jsr352,我需要每天处理大量用户的列表,根据某些场景向他们发送电子邮件和短信通知。为此,我使用JavaEE批处理模型。我的工作xml如下所示: <step id="sendNotification"> <chunk item-count="10" retry-limit="3"> <reader ref="myItemReader"></reader> <processor ref="myItemProcesso

我需要每天处理大量用户的列表,根据某些场景向他们发送电子邮件和短信通知。为此,我使用JavaEE批处理模型。我的工作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的onOpen方法从数据库中读取所有用户,readItem()使用列表迭代器一次读取一个用户。在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;
    }
}
@Named
公共类MyItemReader扩展了AbstractItemReader{
私有迭代器迭代器=null;
私人用户lastUser;
@注入
私人MyService服务;
@凌驾
公共void open(可序列化检查点)引发异常{
超级开放(检查站);
List users=service.getUsers();
迭代器=users.iterator();
if(检查点!=null){
用户检查点User=(用户)检查点;
System.out.println(“找到检查点:+checkpointUser.getUserId());
while(iterator.hasNext()&&!iterator.next().getUserId().equals(checkpointUser.getUserId()){
System.out.println(“跳过已读用户…”);
}
}
}
@凌驾
公共对象readItem()引发异常{
User=null;
if(iterator.hasNext()){
user=iterator.next();
lastUser=用户;
}
返回用户;
}
@凌驾
public Serializable checkpointInfo()引发异常{
返回最后一个用户;
}
}
我的问题是,检查点存储上一个块中执行的最后一条记录。如果我有一个包含10个用户的区块,并且在第5个用户的myItemProcessor中抛出异常,那么在重试时,将执行整个chunck,并再次处理所有10个用户。我不希望再次向已处理的用户发送通知

有办法解决这个问题吗?如何有效地做到这一点

任何帮助都将不胜感激。
谢谢。

您当前的项目处理器正在区块事务范围之外执行某些操作,这导致应用程序状态不同步。如果您的要求是仅在区块中的所有项目成功完成后才发送电子邮件,那么您可以将发送电子邮件的部分移动到另一个区块

我将以@cheng的评论为基础。我在这里要感谢他,希望我的回答能为有效地组织和展示选项提供额外的价值

答:将消息排队等待另一个MDB发送电子邮件 背景: 正如@cheng所指出的,失败意味着整个事务被回滚,检查点不会前进

那么,如何处理这样一个事实:你的区块已经向一些用户发送了电子邮件,但不是全部用户?(你可能会说它回滚了,但有“副作用”。)

因此,我们可以将您的问题重申为:如何从批处理块步骤发送电子邮件?

好吧,假设您有一种通过事务API(实现XAResource等)发送电子邮件的方法,那么您可以使用该API

假设您不这样做,我会对JMS队列进行事务性写入,然后使用单独的MDB发送电子邮件(正如@cheng在他的一条评论中所建议的)

建议的替代方案:使用ItemWriter将消息发送到JMS队列,然后使用单独的MDB实际发送电子邮件 使用这种方法,您仍然可以通过批量处理和更新数据库来提高效率(您一次只发送一封电子邮件),并且您可以从简单的检查点设置和重新启动中获益,而无需编写复杂的错误处理

这也可能作为一种模式在批处理作业之间和批处理作业之外重用

其他选择 为了便于讨论,列出了一些我认为不太好的其他想法:

添加批量应用程序逻辑跟踪电子邮件用户(使用ItemProcessListener) 您可以使用ItemProcessListener方法和建立自己的成功/失败电子邮件列表

在重新启动时,您可以知道当前区块中哪些用户收到了电子邮件,在整个区块回滚后,我们将重新定位到该区块,即使一些电子邮件已经发送

这无疑会使批处理逻辑复杂化,而且您还必须以某种方式保留此成功或失败列表。另外,这种方法可能非常特定于此作业(而不是排队等待MDB处理)

但更简单的是,您只需要一个批处理作业,而不需要消息传递提供商和单独的应用程序组件

如果您走这条路线,您可能希望同时使用可跳过异常和“无回滚”可重试异常

单项组块 如果使用item count=“1”定义块,则可以避免复杂的检查点和错误处理代码。但是,您牺牲了效率,因此这只有在批处理的其他方面非常引人注目的情况下才有意义:例如,通过公共接口调度和管理作业,在作业中出现故障时重新启动的能力

如果您要走这条路线,您可能需要考虑将套接字和超时异常定义为“没有回滚”异常(使用<强> < /强>),因为从回滚中没有什么可获得的,并且您可能希望在网络超时问题上重试。 既然你特别提到效率,我猜这不适合你

使用事务同步
这可能会起作用,但批处理API并没有使这变得特别容易,而且您仍然可能遇到这样的情况:区块完成,但一封或多封电子邮件发送失败。

嘿,cheng,感谢您的回复。请告诉我h