繁体   English   中英

如何在 Spring Controller 中获取与 JPA 和 Hibernate 的 FetchType.LAZY 关联

[英]How to fetch FetchType.LAZY associations with JPA and Hibernate in a Spring Controller

我有一个 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 ,以防万一。

谢谢。

您必须对惰性集合进行显式调用才能对其进行初始化(通常为此调用.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);

}

你有一些选择

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

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

  • 使用 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。

Spring Data JpaRepository

Spring Data JpaRepository定义了以下两个方法:

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

但是,就您而言,您没有调用getOnefindById

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();
    }
}

而已!

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM