如何在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多态关系?的主要内容,如果未能解决你的问题,请参考以下文章

C++设计:如何在C++中设计“可以执行或可以”的关系

如何在salesforce中设计dashboard

如何填充在 JavaFx Scene Builder 中设计的 fxml 文件中定义的 TableView

如何使用hibernate jpa在内存数据库中设置h2?

如何在故事板中设计 UIScrollView

如何在 django rest 框架中设计 ArrayField?