如何在java中设计JPA多态关系?
Posted
技术标签:
【中文标题】如何在java中设计JPA多态关系?【英文标题】:How to design JPA polymorphic relationships in java? 【发布时间】:2016-05-15 14:12:32 【问题描述】:我正在设计产品目录。我想要一个类别树,其中产品只能连接到 LeafCategories,它可以有一个父 Category。 我正在使用 Spring Boot、Spring Data 和 Hibernate 4 和 H2 数据库(目前)。
任务的基本实体是AbstractCategory(有没有更好的方法来继承关系?)(省略了Getters和Setters,NamedEntity是一个@MappedSuperclass,带有字符串名称和长ID)
public abstract class AbstractCategory extends NamedEntity
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "parentId")
Category parent;
Category 实体 - 它们不是叶子,不能将 Products 连接到它们:
@Entity
public class Category extends AbstractCategory
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
Collection<AbstractCategory> subcategories;
LeafCategory 它可以用作我的 Product 实体的属性。
@Entity
public class LeafCategory extends AbstractCategory
@OneToMany(cascade = CascadeType.PERSIST, mappedBy = "category")
Collection<Product> products;
我有一个非常简单的用于 Category 的 CrudRepository 和一个相同的 LeafCategory
@Repository
@Transactional
public interface CategoryRepository extends CrudRepository<Category, Long>
当我从 CategoryRepository 加载 Category 并访问 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
。
我没有任何数据库或休眠配置。我正在使用 spring.boot 中的 devtools:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
【问题讨论】:
你能分享你的配置吗 【参考方案1】:如何改进设计?
在我看来是合理的。
为什么
@Transactional
不保持会话打开?
您已将@Transactional
放在存储库中。数据库会话仅在运行查询时打开,该查询返回类别及其子类别标记为延迟加载。然后,会话关闭(一旦存储库方法返回),之后您将尝试访问子类别,此时不再有会话。将 @Transactional
注释移到调用堆栈的更高位置 - 如果您使用的是 3 层架构,则移至服务层(参见 this post)。
由于存储库方法只运行一个查询,因此无需将它们标记为@Transactional
- 它们无论如何都会在事务中运行。只有当您运行多个查询或运行一个查询和一些其他处理(这可能引发异常并且您希望查询因此而回滚)时,拥有@Transactional
才有意义。这就是为什么,如果你想明确地将任何东西标记为@Transactional
,它宁愿在服务层中。
【讨论】:
感谢您的精彩回答!我要把@Transactional
注解移到我的服务层!【参考方案2】:
首先,您会得到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);
【讨论】:
以上是关于如何在java中设计JPA多态关系?的主要内容,如果未能解决你的问题,请参考以下文章