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

我有一个 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;
}
}

人员仓库就是这个代码,根据 这本指南编写的

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

但是,在这个控制器 我实际上需要延迟数据。我如何触发它的加载?

尝试访问它将失败

未能懒惰地初始化角色集合: 不能初始化代理-不 会议

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

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

谢谢。

229474 次浏览

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

它只能在事务中进行延迟加载。因此,您可以访问存储库中的集合,该存储库具有事务-或者 我通常做的是一个 get with association,或设置接收模式渴望。

我认为您需要 OpenSessionInViewFilter保持您的会话在视图渲染期间打开(但它不是太好的做法)。

你有很多选择

  • 在存储库中编写一个方法,按照 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);
}
}

虽然这是一篇老文章,但请考虑使用@NamedEntityGraph (Javax 持久性)和@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);


}

你也可以这样做:

@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. getFaqanswer () . size () ,就可以得到大小(if 延迟初始化)列表,而不需要获取列表本身。

Spring Data JpaRepository

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

就是这样!