#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 и всё заработало.
Комментариев нет:
Отправить комментарий