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

时间:2017-11-29 16:08:33

标签: java batch-processing java-ee-7 jsr352

我需要每天处理大量用户列表,根据某些情况向他们发送电子邮件和短信通知。我正在使用Java EE批处理模型。我的工作xml如下:

@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;
    }
}

MyItemReader的onOpen方法从数据库中读取所有用户,readItem()使用list iterator一次读取一个用户。在myItemProcessor中,实际的电子邮件通知被发送给用户,然后用户被保存在该块的myItemWriter类的数据库中。

.travis.yml

我的问题是检查点存储在上一个块中执行的最后一条记录。如果我有一个包含下一个10个用户的块,并且在第5个用户的myItemProcessor中抛出异常,那么在重试时将执行整个chunck并且将再次处理所有10个用户。我不希望再次向已经处理的用户发送通知。

有办法解决这个问题吗?如何有效地完成这项工作?

任何帮助都将受到高度赞赏。 感谢。

2 个答案:

答案 0 :(得分:1)

您当前的项目处理器在块事务范围之外执行某些操作,这导致应用程序状态不同步。如果您的要求是仅在成功完成块中的所有项目后发送电子邮件,则可以将电子邮件部分移至ItemWriterListener.afterWrite(items)

答案 1 :(得分:1)

我将以@cheng的评论为基础。我在这里对他很有信心,希望我的答案为组织和有用地提供选项提供了额外的价值。

答案:将另一个MDB的消息排队以分派发送电子邮件

背景

正如@cheng指出的那样,失败意味着整个事务被回滚,并且检查点不会前进。

那么如何处理你的块已经向某些用户发送电子邮件而不是所有用户的事实呢? (你可能会说它回滚了,但有“副作用”。)

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

好吧,假设您有办法通过交易API发送电子邮件(实施 XAResource 等),您可以使用该API。

假设你没有,我会对JMS队列进行事务性写入,然后使用单独的MDB发送电子邮件(在他的一条评论中建议使用@cheng)。

建议的替代方法:使用ItemWriter将消息发送到JMS队列,然后使用单独的MDB实际发送电子邮件

通过这种方法,您仍然可以通过将处理和更新批量分配到数据库来获得效率(无论如何您只是一次发送一封电子邮件),您可以从简单的检查点和重新启动中受益,而无需编写复杂的错误处理

这也可以作为批处理作业和批处理之外的模式重复使用。

其他替代方案

我认为其他一些想法并不好,为讨论而列出:

添加批量应用程序逻辑跟踪用户通过电子邮件发送(使用ItemProcessListener)

您可以使用 ItemProcessListener 方法构建自己的成功/失败电子邮件列表:afterProcessonProcessError

然后,在重新启动时,您可以知道哪些用户已经通过电子邮件发送到当前的块中,即使已经发送了一些电子邮件,我们也会重新定位,因为整个块已经回滚。

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

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

如果你走这条路线,你可能想要结合使用可跳过和“无回滚”的可重试例外。

单项块

如果使用 item-count =“1”定义块,则可以避免复杂的检查点和错误处理代码。你牺牲了效率,所以只有当批次的其他方面非常引人注目时才会有意义:例如:通过通用接口调度和管理作业,能够在作业中的失败步骤中重新启动

如果您要使用此路由,您可能需要考虑将套接字和超时异常定义为“无回滚”异常(使用 ),因为回滚没有任何结果,而您可能想要重试网络超时问题。

由于你特别提到了效率,我猜这不适合你。

使用事务同步

这也许可以工作,但是批处理API并不特别容易,而且你仍然可以遇到块完成但一个或多个电子邮件发送失败的情况。