Spring REST控制器和Hibernate Session生命周期

问题描述 投票:2回答:2

假设我有AB等级,从AB一对一关联。

@Entity
class A {
    @Id Long id;
    @OneToOne(fetch = LAZY) B b;
    // getters, setters
}

@Entity
class B {
    @Id Long id;
}

使用Spring JPA,我有一个A的存储库,如下所示

@Repository
public interface ARepository extends JpaRepository<A, Long> {
    A findById(Long id);
    void delete(Long id);
}

最后,一个REST控制器

@RestController
@RequestMapping("/a")
class AController {
    @Autowired ARepository repo;

    @GetMapping("{id}")
    public A getA(@PathVariable Long id) {
        return repo.findById(id);
    }

    @Transactional
    @DeleteMapping("{id}")
    public A deleteA(@PathVariable Long id) {
        A a = repo.findById(id);
        repo.delete(id);
        return a;
    }
}

问题

假设我已经将Aid = 1实例保存到数据库中,当我向/a/1发送GET请求时,它返回A的JSON表示没有问题。

但是当我尝试通过向/a/1发送DELETE请求来删除实例时,我得到了com.fasterxml.jackson.databind.JsonMappingException,其根本原因如下

org.hibernate.LazyInitializationException:懒得初始化一个集合,无法初始化代理 - 没有Session

我的理解是,这个错误的发生是因为杰克逊试图在完成方法a.getB()之后通过调用deleteA()来序列化实例,其中Hibernate会话的生命周期已经结束,这是正确的吗?

如果是这样,我不明白为什么这个错误不会发生在方法getA()中,我认为Hibernate会话应该在repo.findById(id);完成后立即结束,对吧?

spring hibernate jpa jackson lazy-initialization
2个回答
0
投票

问题很可能与杰克逊试图初始化A.b进行序列化有关。

即使您启用open-in-view,错误仍将存在,因为您正在尝试初始化不再存在的实体的字段。

如果A.b确实应该被懒散地取出,那么考虑使用@JsonIgnore(另一方面,如果A.b应该序列化并与A一起发送到响应体中,没有必要让它懒洋洋地取出,只需使用默认的FetchType.EAGER) 。


0
投票

我不喜欢open-in-view,因为我觉得这是谎言,你应该知道你是如何访问数据库的,并且在初始交易后没有意外的访问。

我也不喜欢因为同样的原因使用FetchType.EAGER,你应该知道你是如何访问数据库的。 FetchType.EAGER可能导致您不期望的联接。

这里可能的解决方案是在存储库中创建特定方法,并使用命名图注释它们。例如。

@Entity
@NamedEntityGraph(name = "A.fetchB",attributeNodes=@NamedAttributeNode("b"))
public class A implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id Long id;
    @OneToOne(fetch = FetchType.LAZY) B b;
    // getters, setters
}

@Repository
public interface ARepository extends JpaRepository<A, Long> {
    @EntityGraph(value="A.fetchB", type=EntityGraphType.FETCH)
    A findAFetchBById(Long id);
}

然后

@RestController
@RequestMapping("/a")
public class AController {
    @Autowired 
    private ARepository repo;

    @GetMapping("{id}")
    public A getA(@PathVariable Long id) {
        A a = repo.findAFetchBById(id);
        return a;
    }

    @Transactional
    @DeleteMapping("{id}")
    public A deleteA(@PathVariable Long id) {
        A a = getA(id);
        repo.delete(id);
        return a;
    }
}

通过这种方式,您可以了解自己对数据库的操作。这将导致单个优化查询,而不是当您访问Entity的各种子项时执行的多个查询。

© www.soinside.com 2019 - 2024. All rights reserved.