如何使用多个@Transactional 注解的方法?

How do I use multiple @Transactional annotated methods?

我有一个 Spring-boot 项目,其中有一个带有 2 个 @Transactional 注释方法的服务 bean。

这些方法执行只读 JPA(休眠)操作以从 HSQL 文件数据库中获取数据,同时使用实体中的 JPA 存储库和延迟加载的 getter。

我还有一个处理命令的 cli bean(使用 PicoCLI)。从这些命令之一,我尝试调用两个 @Transactional 注释方法,但在执行第二种方法期间出现以下错误:

org.hibernate.LazyInitializationException - could not initialize proxy - no Session
        at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
        at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
        at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581)
        at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148)
        at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
        at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
        at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
        at <mypackage>.SomeImpl.getThings(SomeImpl.java:<linenr>)
...

如果我用 @Transactional 本身标记调用两个 @Transactional 注释方法的方法,该代码似乎可以工作(因为我认为现在只有 1 个顶级事务?)。

我只是想知道为什么我不能在单个会话中启动多个事务,或者如果有 none,为什么第二个事务不启动新会话。

所以我的问题是:

编辑:我想明确表示,我不会在事务方法之外公开实体,所以从表面上看,这两种事务方法应该相互独立工作。

EDIT2:更多说明:交易方法需要在 api 中可用,并且 api 的用户应该能够调用多个这些交易方法,而无需使用交易注释,没有得到 LazyInitializationException

Api:

public interface SomeApi {
    List<String> getSomeList();
    List<Something> getThings(String somethingGroupName);
}

实施:

public class SomeImpl implements SomeApi {

    @Transactional
    public List<String> getSomeList() {
        return ...; //Do jpa stuff to get the list
    }

    @Transactional
    public List<Something> getThings(String somethingGroupName) {
        return ...; //Do other jpa stuff to get the result from the group name
    }
}

第 3 方的使用(他们可能不知道什么是事务性):

public someMethod(String somethingGroupName) {
    ...

    SomeApi someApi = ...; // Get an implementation of the api in some way

    List<String> someList = someApi.someList();
    if (someList.contains(somethingGroupName) {
        System.out.println(someApi.getThings(somethingGroupName));
    }

    ...
}

您似乎在交易结束后从您的实体访问一些未初始化的数据。在那种情况下,持久性提供程序可能会抛出延迟初始化异常。

如果您需要检索一些未随实体急切加载的信息,您可以使用以下两种策略之一:

  • 调用方法也用@Transactional注解,就像你做的那样:它不会为每次调用启动一个新的事务,而是使打开的事务处于活动状态,直到你的调用方法结束,避免异常;或
  • 使用 JOIN FETCH JPQL 习惯用法使被调用的方法急切地加载所需的字段。

事务边界需要对您的场景进行一些分析。请阅读 并搜索更好的书籍或教程来掌握它。可能只有你才能恰当地定义你的需求。

我发现开箱即用的 hibernate 不会重新打开会话,因此在第一个事务结束后不会启用延迟加载,无论后续的 jpa 语句是否在事务中。然而在 hibernate 中有一个 属性 来启用这个特性:

spring:
  jpa:
    properties:
      hibernate.enable_lazy_load_no_trans: true

这将确保如果没有会话,则会创建一个临时会话。我相信它也会阻止会话在交易后结束,但我不确定。

部分功劳来自其他 Whosebug 问题的以下答案:

警告:在 hibernate 4.1.8 中存在一个可能导致数据丢失的错误!确保您使用的是 4.2.12、4.3.5 或更新版本的 hibernate。参见:https://hibernate.atlassian.net/browse/HHH-7971.