Как загрузить ленивые элементы из 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);
    }
}

Ответ 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() в вашем контроллере, и вы получите размер, если лениво проинсталлирован список, не получая сам список.