Как загрузить ленивые элементы из Hibernate/JPA в моем контроллере
У меня есть класс Person:
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
@ManyToMany(fetch = FetchType.LAZY)
private List<Role> roles;
// etc
}
С отношением "многие ко многим", которое является ленивым.
В моем контроллере у меня есть
@Controller
@RequestMapping("/person")
public class PersonController {
@Autowired
PersonRepository personRepository;
@RequestMapping("/get")
public @ResponseBody Person getPerson() {
Person person = personRepository.findOne(1L);
return person;
}
}
И PersonRepository - это просто этот код, написанный в соответствии с этим руководством
public interface PersonRepository extends JpaRepository<Person, Long> {
}
Однако в этом контроллере мне действительно нужны ленивые данные. Как я могу запустить загрузку?
Пытаться получить доступ к нему не удастся с помощью
не удалось лениво инициализировать коллекцию роли: no.dusken.momus.model.Person.roles, не удалось инициализировать прокси-сервер Сессия
или другие исключения в зависимости от того, что я пытаюсь сделать.
My xml-description, в случае необходимости.
Спасибо.
Ответы
Ответ 1
Вам нужно будет сделать явный вызов для ленивой коллекции, чтобы ее инициализировать (общепринятой практикой является вызов .size()
для этой цели). В Hibernate для этого есть выделенный метод (Hibernate.initialize()
), но JPA не имеет эквивалента этого. Конечно, вам нужно будет убедиться, что вызов сделан, когда сеанс по-прежнему доступен, поэтому аннотируйте свой метод контроллера с помощью @Transactional
. Альтернативой является создание промежуточного уровня обслуживания между контроллером и репозиторием, который может выставлять методы, которые инициализируют ленивые коллекции.
Обновление:
Обратите внимание, что это решение легко, но приводит к двум различным запросам в базе данных (один для пользователя, другой для его ролей). Если вы хотите добиться лучшей производительности, добавьте следующий метод в свой интерфейс Spring Data JPA:
public interface PersonRepository extends JpaRepository<Person, Long> {
@Query("SELECT p FROM Person p JOIN FETCH p.roles WHERE p.id = (:id)")
public Person findByIdAndFetchRolesEagerly(@Param("id") Long id);
}
Этот метод будет использовать предложение JPQL fetch join, чтобы с нетерпением загрузить ассоциацию ролей в одном обратном направлении в базу данных и, следовательно, смягчить штраф за производительность, вызванный два разных запроса в вышеупомянутом решении.
Ответ 2
Хотя это старый пост, рассмотрите возможность использования @NamedEntityGraph (Javax Persistence) и @EntityGraph (Spring Data JPA). Комбинация работает.
Пример
@Entity
@Table(name = "Employee", schema = "dbo", catalog = "ARCHO")
@NamedEntityGraph(name = "employeeAuthorities",
attributeNodes = @NamedAttributeNode("employeeGroups"))
public class EmployeeEntity implements Serializable, UserDetails {
// your props
}
а затем репозиторий spring, как показано ниже
@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String> {
@EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
EmployeeEntity getByUsername(String userName);
}
Ответ 3
У вас есть несколько вариантов
- Напишите метод в репозитории, который возвращает инициализированный объект, как предложил R.J.
Больше работы, лучшей производительности.
- Используйте OpenEntityManagerInViewFilter, чтобы открыть сеанс для всего запроса.
Меньше работы, обычно приемлемой в веб-средах.
- Используйте вспомогательный класс для инициализации объектов, если это необходимо.
Меньшая работа, полезная, когда OEMIV не включен, например, в приложении Swing, но может быть полезен также и для реализаций репозитория для инициализации любого объекта за один снимок.
Для последней опции я написал класс утилиты JpaUtils, чтобы инициализировать объекты на некотором уровне.
Например:
@Transactional
public class RepositoryHelper {
@PersistenceContext
private EntityManager em;
public void intialize(Object entity, int depth) {
JpaUtils.initialize(em, entity, depth);
}
}
Ответ 4
он может быть только лениво загружен во время транзакции. Таким образом, вы можете получить доступ к коллекции в вашем репозитории, у которой есть транзакция - или что которую я обычно делаю, это get with association
, или настроить fetchmode на нетерпение.
Ответ 5
Я думаю, вам нужно OpenSessionInViewFilter, чтобы ваш сеанс был открыт во время рендеринга рендеринга (но это не слишком хорошая практика).
Ответ 6
Вы можете сделать то же самое:
@Override
public FaqQuestions getFaqQuestionById(Long questionId) {
session = sessionFactory.openSession();
tx = session.beginTransaction();
FaqQuestions faqQuestions = null;
try {
faqQuestions = (FaqQuestions) session.get(FaqQuestions.class,
questionId);
Hibernate.initialize(faqQuestions.getFaqAnswers());
tx.commit();
faqQuestions.getFaqAnswers().size();
} finally {
session.close();
}
return faqQuestions;
}
Просто используйте faqQuestions.getFaqAnswers(). size() в вашем контроллере, и вы получите размер, если лениво проинсталлирован список, не получая сам список.