#java #spring #spring-boot #hibernate
#java #весна #весенняя загрузка #спящий режим
Вопрос:
В настоящее время я получаю следующее исключение:
org.спящий режим.Исключение LazyInitializationException: не удалось лениво инициализировать коллекцию role: com.example.model.Link.subLinks, не удалось инициализировать прокси-сервер — нет сеанса
Я погуглил и нашел другое решение для этого исключения, но я хотел бы знать, почему @Transactional в моем случае не работает. Я уверен, что я делаю что-то не так. Что я делаю не так?
Странная часть заключается в том, что я уже использовал @Transactional где-то еще в этом проекте, и там это работает.
Заранее благодарю вас за любые советы.
Мой класс ссылок: ссылка на объект содержит коллекцию субликаций.
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Link {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotEmpty
private String title;
@NotEmpty
private String description;
@OneToMany(mappedBy = "link")
private Collection<SubLink> subLinks;
}
Мой класс сублинка:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class SubLink {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotEmpty
private String type;
@NotEmpty
@URL
private String url;
@ManyToOne
@JoinColumn(name = "link_id", referencedColumnName = "id")
private Link link;
}
Мой класс LinkServiceImpl:
@Service
public class LinkServiceImpl implements LinkService {
@Autowired
public LinkRepository linkRepository;
@Override
@Transactional
public Link findById(long id) {
return linkRepository.findById(id);
}
}
В моем классе контроллера есть метод ShowLink():
@GetMapping("/link/{linkId}")
public String showLink(@PathVariable(value = "linkId") long linkId, Model model) {
Link link = linkService.findById(linkId);
Collection<SubLink> subLinkCollection = link.getSubLinks(); //Error
model.addAttribute("subLinkCollection", subLinkCollection);
return "link";
}
Комментарии:
1. Не могли бы вы предоставить структуру объекта Link? Что будет полезно для понимания.
2. Привет, @SebinThomas , я добавил класс Link. Спасибо!
3. По умолчанию FetchType уже ГОТОВ, если вы не упомянули LAZY . Содержит ли ваш класс субликации какие-либо отложенные свойства?
4. Я также добавил класс SubLink. Но чтобы ответить на ваш вопрос: я думаю, что нет
5. В вашем репозитории вы использовали Query или QueryMethod? Если запрос, вы пробовали @Aman предложил? И если найти по идентификатору, в чем причина использования метода запроса? вы могли бы использовать метод по умолчанию, предоставляемый JpaRepository, т.е. findById . Просто уточняю у вас.
Ответ №1:
LazyInitializationException
указывает на доступ к невыбранным данным вне контекста сеанса. Чтобы исправить это, вы должны получить все необходимые ассоциации в пределах вашего уровня обслуживания.
Я думаю, что в вашем случае вы извлекаете Link
без its SubLink
, но пытаетесь получить доступ к подссылкам. Итак, использование выборки СОЕДИНЕНИЯ было бы лучшей стратегией.
Примечание: вы FetchType.EAGER
тоже можете использовать, но это может повлиять на производительность. Потому что он всегда будет извлекать ассоциацию, даже если вы их не используете.
= ОБНОВИТЬ =
Спасибо crizzi за упоминание интересной функции под названием Open Session In View (OSIV) (включена по умолчанию в приложениях с весенней загрузкой). Хотя это зависит от того, кого вы можете попросить пометить его как шаблон или антипаттерн.
Основная цель — облегчить работу с отложенными ассоциациями (избегает LazyInitializationException
). Очень подробное объяснение можно найти на
Комментарии:
1. Привет, спасибо за вашу помощь. На самом деле, чтобы исправить это, я использую JOIN, но я хотел бы знать, почему «@Transactional» не работает. ‘@Transactional’ должен выполнять аналогичные действия, или я ошибаюсь?
2. Транзакция — это последовательность операций, рассматриваемых как единая единица работы. Например, в транзакции вы можете получить, а затем обновить объект. Если одна из операций завершается неудачно, считается, что обе завершились неудачно.
3. @tqnone вы сказали, что используете JOIN . это ОБЪЕДИНЕНИЕ или ОБЪЕДИНЕНИЕ ВЫБОРКИ ?
4.@tqnone, который вы помещаете
@Transactional
поверхfindById
, что означает, что контекст сохранения открыт для выполненияfindById
, и транзакция создается для выполненияfindById
. Теперь, если вы вызываетеlink.getSublinks()
изнутриfindById
, ошибки не будет. Однако ваш код вызывается заlink.getSublinks()
пределамиfindById
, где контекст сохранения уже закрыт. Я надеюсь, что это прояснит ситуацию5. Большое, очень большое вам спасибо, ребята. Я понял! 🙂 @[crizzis, Aman, doctore]
Ответ №2:
Аман хорошо объяснил причину LazyInitializationException
в вашем случае, а crizzis добавил дополнительную информацию о том, почему @Transactional
работает не так, как вы ожидали, и как ее следует использовать.
Итак, чтобы обобщить варианты, которые вы должны решить свою проблему:
Нетерпеливая выборка
По комментариям Амана и Криззиса, это проще, но не лучше с точки зрения производительности.
Чтобы сделать это, включите fetch = FetchType.EAGER
в subLinks
определение свойства:
@OneToMany(mappedBy = "link", fetch = FetchType.EAGER)
private Collection<SubLink> subLinks;
Однако каждый раз, когда вы получаете из базы данных экземпляры Link
=> SubLink
, будет включена его коллекция связанных.
Пользовательский запрос
Также прокомментировано Aman (хотя в этом нет необходимости on (link.id = sublink.link.id)
). В этом случае вы можете создать новый метод в своем репозитории, чтобы получить SubLink
определенную коллекцию Link
.
Таким образом, вы сможете иметь дело с ситуациями, в которых вы хотите получить такую «дополнительную информацию» ( findWithSubLinkById
) или нет (при условии findById
).
При двунаправленном OneToMany
достаточно чего-то вроде:
@Query("select l from Link l left join fetch l.subLinks where l.id = :id")
Link findWithSubLinkById(Long id);
При однонаправленном OneToMany
, помимо приведенного выше запроса, вы должны указать, как «объединить» обе таблицы:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "link_id")
private Collection<SubLink> subLinks;
Удаление link
свойства из SubLink
класса.
Дополнительная информация в ассоциациях гибернации и однонаправленных советах «один ко многим»
EntityGraph
Это вариант предыдущего. Таким образом, вместо создания нового метода с помощью @Query
, более простой выбор будет настроен с помощью @EntityGraph
:
@EntityGraph(attributePaths = "subLinks")
Link findWithSubLinkById(Long id);
Дополнительная информация здесь ( Example 77
)
Работа внутри транзакционного метода
Как прокомментировал Криззис, внутри метода, настроенного с @Transactional
вами, вы не получите такого исключения. Итак, другой вариант — переместить код, который использует Sublink
ваш контроллер, в вашу службу.