在新事务中进行延迟提取

时间:2017-06-09 08:16:23

标签: java jpa java-ee jta

在此队列处理中,我希望将发送本身放入单独的事务中,以避免回滚已发送的状态。但是,由于在延迟提取时关闭的会话或分离的实体,我遇到了问题。

这是我的代码:

// Before this is called queueToSend is fetched in a separate transaction

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void sendMails(List<QueuedMail> queueToSend)
{
    for (QueuedMail queuedMail: queueToSend) {
        sendMail(queuedMail);
    }
}

public void sendMail(QueuedMail queuedMail)
{
    System.out.println("Checking "+queuedMail);

    crud.getEntityManager().merge(queuedMail);

    Mail mail = (Mail)crud.find(queuedMail.getMail().getId(),Mail.class);
    System.out.println("mail id: "+queuedMail.getMail().getId());

    crud.getEntityManager().merge(mail);

    Boolean unsubscribed = false;
    Set<Campaign> campaigns = mail.getCampaigns();
    for (Campaign campaign: campaigns) {
        if (campaign.getUnsubscribedUsers().contains(queuedMail.getUser())) {
            unsubscribed = true;
        }
    }

    // ...

}

错误是:

failed to lazily initialize a collection of role: dps.simplemailing.entities.Mail.campaigns, could not initialize proxy - no Session

排在第一位:

for (Campaign campaign: campaigns) {

我认为可能是因为queuedMail已经分离,但是我尝试使用merge重新连接queuedMail和mail,但它没有帮助。

也许它已经在缓存中,这就是为什么它不会启动新会话。

基本上我希望它像以前一样(在我添加TransactionAttribute之前),但作为循环中的单独事务。我不认为我应该做任何特定于供应商的解决方案,因为它似乎是一项微不足道的任务。

更新

我做了一些研究,发现我必须使用合并操作的结果才能重新附加分离的实体。这将特定于供应商的错误(因为使用未定义的分离实体的延迟加载属性)更改为与供应商无关的错误,我认为它显示了真正的问题。

修改后的代码是:

public void sendMail(QueuedMail queuedMail)
{
    System.out.println("Checking "+queuedMail);

    queuedMail = crud.getEntityManager().merge(queuedMail);
    Mail mail = (Mail)crud.getEntityManager().merge(queuedMail.getMail());

    Boolean unsubscribed = false;
    Set<Campaign> campaigns = mail.getCampaigns();
    for (Campaign campaign: campaigns) {
        if (campaign.getUnsubscribedUsers().contains(queuedMail.getUser())) {
            unsubscribed = true;
        }
    }

我还更改了实体以定义级联类型:

@ManyToMany(mappedBy = "mails",cascade = CascadeType.MERGE)
private Set<Campaign> campaigns;

现在错误是这样的:

Transaction is required to perform this operation (either use a transaction or extended persistence context)

我对此错误并不十分理解,该事务应该已经启动,因为类默认为TransactionAttributeType.REQUIRED。将其他方法设置为TransactionAttributeType.NOT_SUPPORTED的意图是在此方法中启动事务。我是交易和实体经理的初学者,所以我会继续研究以更好地理解它,但也许我会尽快得到答案,或者如果我找到解决方案,我会发布它。

1 个答案:

答案 0 :(得分:0)

我发现了一个小故障,这很难说,没有进一步的研究表明真正的问题,但我在抽出时间和走路时得到了答案。

正如我所说,这种方法的主要思想是sendMails没有交易,而sendMail(应该)有交易,这必须为每个项目提供一个交易待处理。

更新中的错误向我显示sendMail中没有任何交易,我对此感到很困惑。

真正的原因在于使用代理执行bean的事实。通常这是从bean本身外部完成的。如果bean直接调用它自己的方法,则容器没有机会代理请求,也不能启动事务。

更正后的代码在bean中有一个注入点,用于注入它自己的实例,但使用容器:

@Stateless
public class MailQueue {

    @Inject MailQueue mailQueue;

    // ...

}

使用此注入点,一个方法可以通过代理调用另一个方法:

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void sendMails(List<QueuedMail> queueToSend)
{
    for (QueuedMail queuedMail: queueToSend) {
        mailQueue.sendMail(queuedMail);
    }
}

public void sendMail(QueuedMail queuedMail)
{

    // ...

}

使用此解决方案,容器有机会在调用sendMail之前启动新事务。