我有一个 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就是这段代码,按照this guide写的
public interface PersonRepository extends JpaRepository<Person, Long> {
}
但是,在这个控制器中,我实际上需要惰性数据。我怎样才能触发它的加载?
尝试访问它会失败
无法延迟初始化角色集合:no.dusken.momus.model.Person.roles,无法初始化代理 - 没有会话
或其他例外情况,具体取决于我的尝试。
我的 xml-description,以备不时之需。
谢谢。
Person
对象?在该 Query
中,包含 fetch
子句并为该人员加载 Roles
。
您必须对惰性集合进行显式调用才能对其进行初始化(通常的做法是为此目的调用 .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 子句在到数据库的单次往返中急切地加载角色关联,因此将减轻上述解决方案中两个不同查询导致的性能损失。
虽然这是一篇旧文章,但请考虑使用 @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);
}
@NamedEntityGraph
是 JPA 2.1 API 的一部分,在 4.3.0 版本之前的 Hibernate 中未实现。
@EntityGraph(attributePaths = "employeeGroups")
可以直接在 Spring Data Repository 中用于注释方法,而无需在 @Entity 上添加 @NamedEntityGraph
- 代码更少,打开存储库时易于理解。
你有一些选择
在存储库上编写一个方法,该方法返回 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);
}
}
Spring Data JpaRepository
Spring Data JpaRepository
定义了以下两种方法:
getOne,它返回一个实体代理,适用于在持久化子实体时设置@ManyToOne 或@OneToOne 父关联。
findById,它在运行从关联表中加载实体的 SELECT 语句后返回实体 POJO
但是,在您的情况下,您没有调用 getOne
或 findById
:
Person person = personRepository.findOne(1L);
因此,我假设 findOne
方法是您在 PersonRepository
中定义的方法。但是,在您的情况下,findOne
方法不是很有用。由于您需要获取 Person
以及 roles
集合,因此最好使用 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();
}
}
而已!
@Query
注释(如上述 amswers 之一)(stackoverflow.com/a/15360333/924036),您是否会像您在上面的评论中描述的那样缓存查询?
它只能在事务中延迟加载。因此,您可以访问存储库中的集合,该集合具有事务 - 或 I normally do is a get with association
, or set fetchmode to eager.
我认为您需要 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(),如果延迟初始化列表,您将获得大小,而无需获取列表本身。
不定期副业成功案例分享
join
而没有fetch
,则该集合将返回initialized = false
;因此,一旦访问该集合,仍会发出第二个查询。fetch
是确保完全加载关系并避免第二次查询的关键。