[英]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);
}
你有一些选择
更多的工作,最好的表现。
更少的工作,在网络环境中通常是可以接受的。
较少的工作,当 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。
JpaRepository
Spring Data JpaRepository
定义了以下两个方法:
但是,就您而言,您没有调用getOne
或findById
:
Person person = personRepository.findOne(1L);
因此,我假设findOne
方法是您在PersonRepository
定义的方法。 但是, findOne
方法在您的情况下不是很有用。 由于您需要获取Person
和roles
集合,因此最好改用findOneWithRoles
方法。
您可以定义一个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.