Spring REST控制器和Hibernate会话生存期

时间:2018-01-01 18:30:57

标签: spring hibernate jpa jackson lazy-initialization

假设我的课程ABAB之间存在一对一的关联。

@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;
    }
}

问题

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

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

  

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

问题

我的理解是,发生此错误是因为Jackson在完成Hibernate会话的生命周期结束的方法a.getB()之后尝试通过调用deleteA()来序列化实例,这是正确的吗? / p>

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

2 个答案:

答案 0 :(得分:0)

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

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

如果A.b确实应该延迟提取,请考虑使用@JsonIgnore(另一方面,A.b是否应序列化并与{{{}一起发送到响应正文中1}},没有必要让它懒洋洋地获取,只需使用默认的A)。

答案 1 :(得分:0)

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

出于同样的原因,我也不想将FetchType.EAGER用于所有内容,您应该知道如何访问数据库。 FetchType.EAGER可能导致您不期望加入。

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

@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的各个子项时执行的多个查询。