2016-02-05 29 views
1

我正在設計一個產品目錄。我想有一個分類樹,其中產品只能連接到葉子分類,可以有一個父母類別。 我正在使用Spring Boot,Spring Data和Hibernate 4和H2數據庫(現在)。該任務如何在java中設計JPA多態關係?

基礎機構是AbstractCategory(難道還有更好的方式來繼承關係?)(getter和setter省略,NamedEntity是@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

我沒有任何配置數據庫或休眠。我使用devtools從spring.boot:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-devtools</artifactId> 
</dependency> 
+0

你分享你的配置 –

回答

1

如何提高設計?

對我來說這看起來很合理。

爲什麼@Transactional未使會話保持打開狀態?

您已將@Transactional置於存儲庫上。數據庫會話僅在運行查詢時打開,該查詢返回帶有標記爲延遲加載的子類別的類別。然後,關閉會話(一旦存儲庫方法返回),並且在之後沒有會話時嘗試訪問子類別。如果您使用3層體系結構,請將@Transactional註釋移到呼叫堆棧上方 - 參考服務層(參見this post)。

由於存儲庫方法只運行一個查詢,因此無需將它們標記爲@Transactional - 無論如何它們都將在事務中運行。只有在運行多個查詢或運行查詢和其他一些處理(可能會引發異常,並且希望查詢由於它回滾)時纔有@Transactional。這就是爲什麼,如果你想明確標記爲@Transactional,它寧可在服務層。

+0

謝謝你的好回答!我將把'@ Transactional'註釋移動到我的服務層! –

1

首先,您得到LazyInitializationException,因爲Session已關閉,並非所有的孩子都已初始化。

即使您使用EAGER(即often a bad decision),您也只能在嵌套子樹中獲取單個級別。

您可以使用遞歸遍歷所有的孩子和從DAO方法,這就需要你提供的查找方法的自定義實現返回結果之前強迫初始化:

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); 
    } 
}