【问题标题】:Spring REST controller and Hibernate Session lifetimeSpring REST 控制器和 Hibernate Session 生命周期
【发布时间】:2018-06-11 15:24:13
【问题描述】:

假设我的课程 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 发送删除请求来删除实例时,我得到了com.fasterxml.jackson.databind.JsonMappingException,其根本原因如下

org.hibernate.LazyInitializationException: 延迟初始化集合失败,无法初始化代理 - 没有会话

问题

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

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

【问题讨论】:

  • 您的控制器是否使用弹簧启动?
  • I don't understand why this error doesn't occur in method getA(),(如果您使用的是 Spring Boot),您会发现同样的问题 here。对于您的第一个问题和 AFAIK,您的 deleteA(Long) 方法中不应包含 LazyInitializationException,因为 Session 将用于获取类似于 getA() 方法的 B 数据。
  • @O.Badr,感谢您的回答。我使用 Spring Boot。我按照您建议的链接尝试将open-in-view 设置为false,然后方法getA() 出现错误。但是当我将open-in-view 设置为true 时,getA() 不会出错,但deleteA() 会出错。我尝试从deleteA() 中删除repo.delete(id); 并且它开始工作。所以我怀疑这可能与repo.delete(id); 方法有关?
  • 我已经测试了你的代码,deleteA() 没有任何异常,你使用的是哪个版本的 Spring Boot?尽量不要在 deleteA() 方法中返回 a 对象(用 ResponseEnityHttpStatus 代替,例如 ResponseEntity.ok() )?

标签: spring hibernate jpa jackson lazy-initialization


【解决方案1】:

问题很可能与 Jackson 尝试初始化 A.b 以进行序列化有关。

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

如果确实应该延迟获取A.b,请考虑使用@JsonIgnore(另一方面,如果A.b 应该与A 一起序列化并在响应正文中发送,则没有意义使其延迟获取,只需使用默认的FetchType.EAGER)。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2010-12-01
      • 1970-01-01
      • 2014-12-04
      • 2016-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多