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

однако, в этом контроллере мне действительно нужны ленивые данные. Как я могу запустить его загрузку?

попытка доступа к нему завершится неудачей с

не удалось лениво инициализировать коллекция роль: нет.dusken.момус.модель.Человек.роли, не удалось инициализировать прокси - нет Сессия

или другие исключения в зависимости от того, что я пытаюсь.

мой xml-описание, в случае необходимости.

спасибо.

6 111

6 ответов:

вам нужно будет сделать явный вызов ленивой коллекции, чтобы инициализировать ее (обычная практика-вызвать .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 предложение для быстрой загрузки ассоциации ролей в одном круговом переходе к базе данных и, следовательно, уменьшит штраф за производительность, понесенный двумя различными запросами в приведенном выше решении.

хотя это старый пост, пожалуйста, рассмотрите возможность использования @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
}

и затем весеннее РЕПО, как показано ниже

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}

у вас есть несколько вариантов

  • напишите метод в репозитории, который возвращает инициализированный объект, как предложил 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);
    }
}

он может быть только лениво загружен во время транзакции. Таким образом, вы можете получить доступ к коллекции в вашем репозитории, который имеет транзакцию - или что Я обычно делаю это get with association, или установите fetchmode в eager.

Я думаю, что вам нужно OpenSessionInViewFilter чтобы сохранить сеанс открытым во время визуализации вида (но это не слишком хорошая практика).

вы можете сделать то же самое, как это:

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