Страницы

Поиск по вопросам

понедельник, 6 января 2020 г.

JpaRepository + Hibernate + OneToMany вылетает LazyInitializationException

#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 Set books= 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 и всё заработало.

Комментариев нет:

Отправить комментарий