如何在Spring控制器中通过JPA和Hibernate获取FetchType.LAZY关联。

问题描述 投票:140回答:7

我有一个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.role,无法初始化代理 - 没有Session

或其他例外情况,取决于我的尝试。

我的 xml-描述,以备不时之需。

谢谢,我有一个人物类:

java spring hibernate jpa spring-data
7个回答
205
投票

你必须对懒惰集合进行显式调用以初始化它(通常的做法是调用 .size() 用于此目的)。) 在Hibernate中,有一个专门的方法来实现这个目的(Hibernate.initialize()),但JPA没有与之对应的方法。当然,你必须确保在会话仍然可用的情况下完成调用,所以在你的控制器方法中注解了 @Transactional. 另一种选择是在Controller和Repository之间创建一个中间服务层,它可以暴露初始化懒惰集合的方法。

更新。

请注意,上面的解决方案很简单,但会导致对数据库进行两个不同的查询(一个是用户,另一个是角色)。如果你想获得更好的性能,请在你的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的 领取加入 子句来急切地在一次往返数据库的过程中加载角色关联,因此将减轻上述解决方案中两个不同查询所产生的性能惩罚。


36
投票

虽然这是一个老帖子,但请考虑使用@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);

}

13
投票

你有一些选择

  • 按照R.J的建议,在仓库上写一个方法,返回一个初始化的实体。

更多的工作,最好的性能。

  • 使用OpenEntityManagerInViewFilter来保持整个请求的会话开放。

更少的工作,通常在web环境下可以接受。

  • 当需要时,使用帮助类来初始化实体。

工作量较少,当OEMIV不在选项中时很有用,例如在Swing应用程序中,但在存储库实现中可能也很有用,可以一次性初始化任何实体。

对于最后一个选项,我写了一个实用类。JpaUtils 来初始化某个deph的实体。

例如,它只能在事务中被懒惰地加载。

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

8
投票

它只能在事务中被懒惰地加载。所以你可以访问你的仓库中的集合,它有一个事务--或者说是 我通常做的是一个 get with association或将fetchmode设置为eager。


6
投票

我想你需要 OpenSessionInViewFilter 在视图渲染过程中保持会话打开(但这不是太好的做法)。


3
投票

春天数据 JpaRepository

春天的数据 JpaRepository 定义了以下两个方法。

然而,在你的情况下,你没有打电话给任何一个。getOnefindById:

Person person = personRepository.findOne(1L);

所以,我假设 findOne 方法是你在 PersonRepository. 然而, findOne 方法在你的情况下不是很有用。因为你需要获取 Person 随着是 roles 集合,最好使用 findOneWithRoles 方法代替。

自定义Spring数据方法

您可以定义一个 PersonRepositoryCustom 接口,如下。

public interface PersonRepository
    extends JpaRepository<Person, Long>, PersonRepositoryCustom { 

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

然后像这样定义它的实现

public class PersonRepositoryImpl implements PersonRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Person findOneWithRoles(Long id)() {
        return entityManager.createQuery("""
            select p 
            from Person p
            left join fetch p.roles
            where p.id = :id 
            """, Person.class)
        .setParameter("id", id)
        .getSingleResult();
    }
}

就是这样!


1
投票

你也可以像这样做。

@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你就能得到懒惰初始化列表的大小,而不用获取列表本身。

© www.soinside.com 2019 - 2024. All rights reserved.