164

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

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

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

谢谢。

4

7 回答 7

232

您必须对惰性集合进行显式调用才能对其进行初始化(通常的做法是.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 的fetch join子句在到数据库的单次往返中急切地加载角色关联,因此将减轻上述解决方案中两个不同查询导致的性能损失。

于 2013-03-12T11:47:24.410 回答
40

虽然这是一篇旧文章,但请考虑使用 @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);

}
于 2015-01-10T17:54:09.777 回答
14

你有一些选择

  • 在存储库上编写一个方法,该方法返回 RJ 建议的初始化实体。

更多的工作,最好的表现。

  • 使用 OpenEntityManagerInViewFilter 为整个请求保持会话打开。

更少的工作,通常在 Web 环境中是可以接受的。

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

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

对于最后一个选项,我编写了一个实用程序类JpaUtils来在某个深度初始化实体。

例如:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}
于 2013-03-12T11:58:23.400 回答
10

弹簧数据JpaRepository

Spring DataJpaRepository定义了以下两种方法:

  • getOne,它返回一个实体代理,适合在持久化子实体时@ManyToOne设置或@OneToOne父关联。
  • findById,它在运行从关联表中加载实体的 SELECT 语句后返回实体 POJO

但是,就您而言,您没有致电getOneor findById

Person person = personRepository.findOne(1L);

因此,我假设该findOne方法是您在PersonRepository. 但是,该findOne方法在您的情况下不是很有用。由于您需要Personroles集合一起获取,因此最好使用findOneWithRoles方法。

自定义 Spring Data 方法

您可以定义一个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();
    }
}

就是这样!

于 2020-05-12T12:50:38.220 回答
9

它只能在事务中延迟加载。所以你可以访问你的存储库中的集合,它有一个事务——或者我通常做的是一个get with association,或者将 fetchmode 设置为 eager。

于 2013-03-12T11:51:11.580 回答
6

我认为您需要OpenSessionInViewFilter在视图呈现期间保持会话打开(但这不是很好的做法)。

于 2013-03-12T11:51:50.237 回答
2

你可以这样做:

@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(),如果延迟初始化列表,您将获得大小,而无需获取列表本身。

于 2015-04-06T11:08:14.637 回答