我正在设计产品目录。我想要一个类别树,其中产品只能连接到 LeafCategories ,可以有一个父类别。 我正在使用Spring Boot,Spring Data和Hibernate 4和H2数据库(现在)。
任务的基本实体是 AbstractCategory (是否有更好的方式来继承关系?)(省略了Getters和Setters,NamedEntity是带有String名称的 @MappedSuperclass 和长id)
public abstract class AbstractCategory extends NamedEntity{
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "parentId")
Category parent;
}
类别实体 - 它们不是叶子,也不能连接到它们的产品:
@Entity
public class Category extends AbstractCategory {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
Collection<AbstractCategory> subcategories;
}
LeafCategory 它可以用作产品实体的属性。
@Entity
public class LeafCategory extends AbstractCategory {
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "category")
Collection<Product> products;
}
我有一个非常简单的类别的CrudRepository和 LeafCategory 的相同
@Repository
@Transactional
public interface CategoryRepository extends CrudRepository<Category, Long> {}
当我从CategoryRepository加载一个类别并访问getSubcategories()时,我得到以下异常:
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: uj.jg.domain.products.Category.subcategories, could not initialize proxy - no Session
首先 - 我如何改进设计?第二个也是更具体的问题是为什么@Transactional
没有保持会话开放?我知道我可以使用FetchType.EAGER
,但它是一个递归结构 - 如果我对Hibernate的理解是正确的,那就意味着加载整个子树,我不想要那样。我也不想使用Hibernate.initialize
。
我没有数据库或hibernate的配置。我正在使用spring.boot中的devtools:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
答案 0 :(得分:1)
如何改进设计?
对我来说这看起来很合理。
为什么
@Transactional
没有保持会话开放?
您已将@Transactional
放在存储库中。数据库会话仅在运行查询时打开,该查询返回其子类别标记为延迟加载的类别。然后,会话关闭(一旦存储库方法返回),并且您之后尝试访问子类别,此时不再有会话。如果您使用的是3层架构,请将@Transactional
注释移到调用堆栈的上方 - 服务层(参见this post)。
由于存储库方法只运行一个查询,因此无需将它们标记为@Transactional
- 它们无论如何都会在事务中运行。只有在运行多个查询或运行查询以及其他一些处理(可能会抛出异常并且您希望由于它而回滚查询)时才有@Transactional
才有意义。这就是为什么,如果你想明确地将任何内容标记为@Transactional
,那么它宁愿在服务层中。
答案 1 :(得分:1)
首先,您获得LazyInitializationException
,因为Session
已关闭,而且并非所有子项都已初始化。
即使您使用了EAGER(often a bad decision),您也只能在嵌套子树中获取单个级别。
您可以使用递归遍历所有子项并在从DAO方法返回结果之前强制进行初始化,这需要您为find方法提供自定义实现:
public Category findOne(Long id) {
Category category = entityManager.find(Category.class, id);
fetchChildren(category);
return category;
}
public void fetchChildren(Category category) {
for (Category _category : category.getSubcategories()) {
fetchChildren(_category);
}
}