【问题标题】:Lazy fetching in a new transaction延迟获取新事务
【发布时间】:2017-06-09 08:16:22
【问题描述】:

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

这是我的代码:

// 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 已经分离,但是我正在尝试使用合并重新附加 queuedMail 和邮件,但这没有帮助。

也可能它已经在缓存中,这就是它不启动新会话的原因。

基本上我希望它像以前一样(在我添加 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 的目的是在此方法中启动事务。我是交易和实体管理的初学者,所以我会继续研究以更好地理解它,但也许我会在这里更快地得到答案,或者如果我找到解决方案,我会发布它。

【问题讨论】:

  • 在广告系列中,您有关系实体。默认情况下它是惰性的,或者您将其设置为默认值。简单的解决方案它标志着它渴望。更好的是,您可以在方法调用 sendMails 之前加载所有惰性会话事务
  • 这里的核心问题不是惰性实体,因为它很好,我不想急于加载它,这将是一个简单的解决方案,但它会降低性能。这里的问题是事务已经结束。我会尽快更新问题,因为我做了一些代码更改,改变了我得到的错误,所以它更有意义。

标签: java jpa jakarta-ee jta


【解决方案1】:

我发现了故障,这很棘手,可以说,没有进一步的研究表明真正的问题,但我在抽出时间散步时得到了答案。

正如我所说,这种方法的主要思想是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 之前启动新事务。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-13
    • 2021-12-03
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 2018-01-20
    • 2018-01-11
    • 1970-01-01
    相关资源
    最近更新 更多