【问题标题】:Why spring jpa repository always load eagerly in @GetMapping method?为什么 spring jpa 存储库总是在 @GetMapping 方法中急切加载?
【发布时间】:2019-10-04 19:38:44
【问题描述】:

我想使用 spring jpa 和 hibernate 创建一个简单的多对多关系,代码如下:

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name = "name")
    private String name;

    @ManyToMany(cascade = CascadeType.ALL,
    fetch = FetchType.LAZY)
    @JoinTable(name = "book_publisher",
        joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "publisher_id", referencedColumnName = "id"))
    private Set<Publisher> publishers;

@Entity
public class Publisher {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @ManyToMany(mappedBy = "publishers"
    , fetch = FetchType.LAZY)
    @Nullable
    private Set<Book> books = new HashSet<>();

    public Publisher(String name) {
        this.name = name;
    }

如上图所示,所有集合都是惰性获取的。

我创建了两个实体的两个存储库:

public interface PublisherRepository extends JpaRepository<Publisher, Integer>{
}
public interface BookRepository extends JpaRepository<Book, Integer>{

}

我创建了一个简单的控制器:

    @GetMapping("/getPublisher/{id}")
    public Publisher getPublisher(@PathVariable Integer id) {
        return publisherRepository.findById(id).get();
    }

发生了一些奇怪的事情:

当我通过 curl 进行 http 调用时,我收到了这样的响应:

{"id":1,"name":"YanYan","books":[{"id":1,"name":"复仇者联盟","publishers":[{"id":1, "name":"YanYan","books":[{"id":1,"name":"复仇者联盟","publishers":[{"id":1,"name":"YanYan","books ":[{"id":1,"name":"复仇者联盟","publishers":[{"id":1,"name":"YanYan","books":[{"id":1, "name":"复仇者联盟","publishers":[{"id":1,"name":"YanYan","books":[{"id":1,"name":"复仇者联盟","publishers ":[{"id" .....

这表示它们都没有被延迟获取,这会导致无限循环。

谁能告诉我为什么?

【问题讨论】:

    标签: spring hibernate jpa


    【解决方案1】:

    我可以看到 2 个问题:

    1. 延迟加载:

    检查spring.jpa.open-in-view 是否被明确配置。 不幸的是,默认是true

    您可以在以下位置获取有关此设置的更多信息:What is this spring.jpa.open-in-view=true property in Spring Boot?

    如果你没有配置,you may receive a warning on startup:

    WebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
    
    1. JSON 序列化

    您有 2 个相互交叉引用的实体,当您连载一个出版商时,您也连载了它的书,但该书有您已经访问过的出版商。你在序列化阶段进入了一个无限循环。

    要么使用 JSON 注释从序列化中排除关系的一侧,要么使用自定义 DTO 传输数据。

    更新

    我希望当您设置 spring.jpa.open-in-view = false 并且您没有指定要获取的内容时,您将开始出现 LazyInitializationException。这是因为您在将字段序列化为 JSON 时仍然尝试访问这些字段(但现在对象未附加到会话)。与您的评论相反,这证明集合是延迟加载的(这意味着您有一个代理而不是集合。您可以访问此代理,这会强制加载,但前提是会话仍然打开 - 相同的事务或打开-in-view 设置)。

    我的建议:先攻击JSON序列化,这才是真正的bug。修复后担心获取策略。

    【讨论】:

    • 感谢您给我一个很好的答案!我尝试设置 spring.jpa.open-in-view = false,但出现异常:failed to lazily initialize a collection of role 似乎我尝试访问集合但集合尚未加载,因为尚未加载(已加载懒洋洋)。这很奇怪。我想这就是为什么即使我将获取类型设置为惰性,我仍然会急切地加载所有集合。你能告诉我为什么吗?非常感谢,先生。
    • 更新了答案。
    • 非常感谢您,先生。我在双方(拥有方和反方)都添加了@JsonIgnore,并尝试在@transactional 方法中获取集合。他们按预期工作!谢谢你!
    猜你喜欢
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 2019-10-30
    • 1970-01-01
    • 2013-04-28
    相关资源
    最近更新 更多