假设我有A
和B
等级,从A
到B
一对一关联。
@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
我的理解是,这个错误的发生是因为杰克逊试图在完成方法a.getB()
之后通过调用deleteA()
来序列化实例,其中Hibernate会话的生命周期已经结束,这是正确的吗?
如果是这样,我不明白为什么这个错误不会发生在方法getA()
中,我认为Hibernate会话应该在repo.findById(id);
完成后立即结束,对吧?
问题很可能与杰克逊试图初始化A.b
进行序列化有关。
即使您启用open-in-view
,错误仍将存在,因为您正在尝试初始化不再存在的实体的字段。
如果A.b
确实应该被懒散地取出,那么考虑使用@JsonIgnore
(另一方面,如果A.b
应该序列化并与A
一起发送到响应体中,没有必要让它懒洋洋地取出,只需使用默认的FetchType.EAGER
) 。
我不喜欢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
的各种子项时执行的多个查询。