ChatGPT解决这个技术问题 Extra ChatGPT

如何在 Spring Controller 中获取与 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;
    }
}

而PersonRepository就是这段代码,按照this guide写的

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

但是,在这个控制器中,我实际上需要惰性数据。我怎样才能触发它的加载?

尝试访问它会失败

无法延迟初始化角色集合:no.dusken.momus.model.Person.roles,无法初始化代理 - 没有会话

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

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

谢谢。

您能否编写一个方法,该方法将创建一个查询来获取给定某个参数的 Person 对象?在该 Query 中,包含 fetch 子句并为该人员加载 Roles

C
Community

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


请注意,这是一个简单的解决方案,但会导致对数据库的两个不同查询(一个针对用户,另一个针对其角色)。如果您想获得更好的性能,请尝试编写一个专用方法,使用 JPQL 或其他人建议的 Criteria API 在一个步骤中急切地获取用户及其相关角色。
我现在要求以 Jose 的回答为例,不得不承认我并不完全理解。
请在我更新的答案中检查所需查询方法的可能解决方案。
有趣的是,如果您只是 join 而没有 fetch,则该集合将返回 initialized = false;因此,一旦访问该集合,仍会发出第二个查询。 fetch 是确保完全加载关系并避免第二次查询的关键。
似乎同时执行 fetch 和 join 的问题是连接谓词条件被忽略,您最终会得到列表或映射中的所有内容。如果你想要所有东西,那么使用 fetch,如果你想要特定的东西,那么使用 join,但是,如前所述,join 将是空的。这违背了使用 .LAZY 加载的目的。
r
rakpan

虽然这是一篇旧文章,但请考虑使用 @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 - 代码更少,打开存储库时易于理解。
J
Jose Luis Martin

你有一些选择

在存储库上编写一个方法,该方法返回 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);
    }
}

由于我所有的请求都是简单的 REST 调用,没有渲染等,因此事务基本上是我的整个请求。感谢您的输入。
我怎么做第一个?我知道如何编写查询,但不知道如何执行您所说的。你能举个例子吗?会很有帮助。
zagyi 在他的回答中提供了一个例子,不过,感谢您为我指明了正确的方向。
我不知道你的课怎么称呼!未完成的解决方案浪费其他时间
使用 OpenEntityManagerInViewFilter 为整个请求保持会话打开。- 坏主意。我会提出一个额外的请求来获取我的实体的所有集合。
V
Vlad Mihalcea

Spring Data JpaRepository

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

getOne,它返回一个实体代理,适用于在持久化子实体时设置@ManyToOne 或@OneToOne 父关联。

findById,它在运行从关联表中加载实体的 SELECT 语句后返回实体 POJO

但是,在您的情况下,您没有调用 getOnefindById

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

而已!


您是否有理由自己编写查询并且没有使用@rakpan 答案中的 EntityGraph 之类的解决方案?这不会产生相同的结果吗?
因为 EntityGraphs 计划不像 JPQL 那样被缓存。这可能会对性能造成重大影响。
确切地。当我有时间时,我将不得不写一篇关于它的文章。
Web 层中的双向关联需要 JsonIgnore,因此它不会影响您在数据访问层中查询数据的方式。
@Vlad 如果您使用 @Query 注释(如上述 amswers 之一)(stackoverflow.com/a/15360333/924036),您是否会像您在上面的评论中描述的那样缓存查询?
C
Community

它只能在事务中延迟加载。因此,您可以访问存储库中的集合,该集合具有事务 - 或 I normally do is a get with association, or set fetchmode to eager.


M
Michail Nikolaev

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


因为我没有使用 JSP 或任何东西,只是制作一个 REST-api,@Transactional 将为我做。但在其他时候会有用。谢谢。
@Matsemann我知道现在已经很晚了...但是您甚至可以在控制器中使用 OpenSessionInViewFilter ,并且会话将存在直到编译响应...
@Matsemann 谢谢!事务性注释对我有用!仅供参考:如果您只是注释休息类的超类,它甚至可以工作。
n
neel4soft

你可以这样做:

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