#java #spring #hibernate #jpa #spring_data
реализую отношение one-to-many по этому туториалу а также этому спринговому туториалу. Я НЕ использую сессии и прочий hibernate напрямую, я использую JpaRepository и аннотации JPA. Есть объекты Owner (one) & Book (many): первая таблица: @Entity @Table(name = "owners") public class Owner implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "owner_id", nullable = false, unique = true) private Long id; @Column(name = "owner_name", nullable = false) private String name; @OneToMany(fetch = FetchType.LAZY,mappedBy = "owner") private Setbooks= new HashSet<>(0); public Worker() { } } вторая таблица: @Entity @Table(name = "books") public class Book implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "book_id", unique = true, nullable = false) private Long id; @Column(name = "book_name", nullable = false, unique = true) private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "owner_id") private Owner owner; public Task() { } } слой репозиториев стандартный, без реализации: public interface OwnerRepository extends JpaRepository {} public interface BookRepository extends JpaRepository {} Owner service: (book service такой же) @Service @Transactional public class OwnerServiceImpl implements OwnerService { @Autowired private OwnerRepository ownerRepository; @Override public Owner create(Owner owner) {return ownerRepository.save(owner);} @Override public Owner read(Long id) {return ownerRepository.findOne(id);} @Override public List readAll() {return ownerRepository.findAll();} @Override public void delete(Owner owner) {ownerRepository.delete(owner);} } Создав два класса service, я захотел протестить их. Создал несколько Owner,Book ну и пытаюсь связать их как указано здесь, т.е. добавляю в объектe Owner в Set новую книжку, а в объект Book обновляю ссылку на Owner. Сохраняю в репозиторий сначала Owner, затем Book. Затем пытаюсь прочитать Owner из репозитория, а во время обращения к полю выдаёт unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception. может я вообще неправильно работаю с репозиторием и JPA? как нужно сохранять/обновлять ссылки в сущностях? ===UPDATE=== мне кажется я решил проблему, использовав @NamedEntityGraph & @EntityGraph. Я аннотировал Owner @NamedEntityGraph(name = "Owner.books", attributeNodes = @NamedAttributeNode("books")) Но в интерфейсе репозитория пришлось переопределить стандартные методы: @Override @EntityGraph(value = "Owner.books", type = EntityGraph.EntityGraphType.LOAD) List findAll(); @Override @EntityGraph(value = "Owner.books", type = EntityGraph.EntityGraphType.LOAD) OwnerfindOne(Long aLong); Тоже самое сделал с Book. Когда я делаю запрос из репозитория Owner на считывание объекта, всё загружается нормально - ссылки в Set ссылаются на объекты Book. Но когда я делаю запрос к Book репозиторию - объект Book имеет ссылку на Owner, НО в этом внутреннем Owner ссылки на дополнительные книги бросают LazyInitException. Почему? ссылки просматриваю в Idea на брек поинте.
Ответы
Ответ 1
У вас стоит fetch = FetchType.LAZY это значит, что хибернейт не будет инициализировать эти поля пока вы к ним не обратитесь. Но т.к. вы обращаетесь к этим полям за пределами транзакционных методов, он не может это сделать и выкидывает ошибку. Чтобы этого избежать надо, что метод, который обращается к этим полям был с аннотацей Transactional. В вашем случае это EntitiesServicesTest.ooops(). Либо можно немного изменить реализацию OwnerServiceImpl.read(). Сделать такое: @Override public Owner read(Long id) { Owner owner = ownerRepository.findOne(id); owner.getBooks().iterator(); return owner; } Или как предложили в комментариях: Hibernate.initialize(owner.getBooks()); Это хак, но он заставит хибернейт инициировать коллекцию. НО! Возможно это не всегда надо и тогда надо выбрать первый вариант и отталкиваться от здравого смысла, смотреть, где надо навешивать аннотацию, а где нет.Ответ 2
Стоит также упомянуть, что fetch type по дефолту как раз LAZY. Я для решения проблемы поставил fetch = FetchType.EAGER и всё заработало.
Комментариев нет:
Отправить комментарий