2016-02-05 14 views
1

Я разрабатываю каталог продукции. Я хотел бы иметь дерево категорий, где продукты могут быть подключены только к LeafCategories, которые могут иметь одного родителя Категория. Я использую Spring Boot, Spring Data и Hibernate 4 и базу данных H2 (пока).Как создать полиморфные отношения JPA в java?

Базовый объект для выполнения этой задачи является AbstractCategory (Является ли это есть лучший способ, чтобы наследовать отношения?) (Геттеры и сеттеры опущенные, NamedEntity является @MappedSuperclass с именем Струнный и Лонг-ид)

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 в репозитории. Сессия БД открыта только для времени выполнения запроса, который возвращает категории с их подкатегориями, помеченными как ленивые. Затем сеанс закрывается (как только метод репозитория возвращается), и вы пытаетесь получить доступ к подкатегориям после этого, когда сеанса больше нет. Переместите аннотацию @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); 
    } 
} 
Смежные вопросы