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

Java 如何在Spring控制器中获取与JPA和Hibernate的FetchType.LAZY关联,java,spring,hibernate,jpa,spring-data,Java,Spring,Hibernate,Jpa,Spring Data,我有个人课: @Entity public class Person { @Id @GeneratedValue private Long id; @ManyToMany(fetch = FetchType.LAZY) private List<Role> roles; // etc } PersonRepository就是这个代码,根据 公共接口PersonRepository扩展了JpaRepository{ } 然而,在这

我有个人课:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}
PersonRepository就是这个代码,根据

公共接口PersonRepository扩展了JpaRepository{
}
然而,在这个控制器中,我实际上需要惰性数据。如何触发其加载?

尝试访问它将失败

未能延迟初始化角色集合: no.Dasken.momus.model.Person.roles,无法初始化代理-否 会议

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

我的,万一需要的话


谢谢。

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

}
公共接口PersonRepository扩展了JpaRepository{
@查询(“从Person p JOIN FETCH p.roles中选择p,其中p.id=(:id)”)
公共人物findbyiandfetchrolesegerly(@Param(“id”)Long id);
}

此方法将使用JPQL的子句在数据库的一次往返中急切地加载角色关联,因此将减轻上述解决方案中两个不同查询引起的性能损失。

它只能在事务中延迟加载。因此,您可以访问存储库中的集合,它有一个事务,或者什么,我认为您需要在视图渲染期间保持会话打开(但这不是很好的做法)。

您有一些选择

  • 在存储库上编写一个方法,按照R.J的建议返回一个初始化的实体
更多的工作,最好的表现


  • 使用OpenEntityManagerInViewFilter为整个请求保持会话打开
较少的工作,通常在web环境中可以接受

  • 需要时,使用帮助器类初始化实体
工作量更少,在OEMIV不可用时很有用,例如在Swing应用程序中,但在存储库实现中也很有用,可以一次性初始化任何实体

对于最后一个选项,我编写了一个实用程序类,以在某些深度初始化实体

例如:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}

这是一个旧的帖子,请考虑使用@ NealDeNeTyGrand(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);

}
@RepositoryRestResource(collectionResourceRel=“Employee”,path=“Employee”)
公共接口IEEmployeeRepository扩展了分页和排序存储库{
@EntityGraph(value=“EmployeeAuthories”,type=EntityGraphType.LOAD)
EmployeeEntity getByUsername(字符串用户名);
}

您也可以这样做:

@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;
}
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();
    }
}
只需在控制器中使用faqQuestions.getFaqAnswers().size(),如果惰性地初始化列表,您将获得该列表的大小,而无需获取列表本身。

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

  • getOne
    ,它返回一个适合设置
    @ManyToOne
    @OneToOne
    父关联的
  • findById
    ,它在运行从关联表加载实体的SELECT语句后返回实体POJO
但是,在您的情况下,您既没有调用
getOne
也没有调用
findById

Person person = personRepository.findOne(1L);
因此,我假设
findOne
方法是您在
PersonRepository
中定义的方法。但是,
findOne
方法在您的案例中不是很有用。由于您需要获取
Person
以及is
roles
集合,因此最好使用
findOneWithRoles
方法

自定义Spring数据方法 您可以定义
PersonRepositoryCustom
接口,如下所示:

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

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}

就这样

您是否可以编写一个方法,在给定某个参数的情况下创建一个查询来获取
Person
对象?在
Query
中,包括
fetch
子句,并为个人加载
角色。由于我的所有请求都是简单的REST调用,没有呈现等,因此事务基本上是我的整个请求。谢谢你的意见。第一个怎么办?我知道如何编写查询,但不知道如何执行您所说的。你能举个例子吗?扎吉在他的回答中提供了一个例子,谢谢你给我指出了正确的方向。我不知道你的班级会怎么称呼!未完成的解决方案浪费其他人的时间使用OpenEntityManagerViewFilter为整个请求保持会话打开。-坏主意。我将另外请求获取我的实体的所有集合。请注意,这是一个简单的解决方案,但会导致对数据库的两个不同查询(一个用于用户,另一个用于其角色)。如果您想获得更好的性能,请尝试编写一个专用方法,使用JPQL或其他人建议的Criteria API,在一个步骤中急切地获取用户及其相关角色。我现在要求提供一个Jose回答的示例,我必须承认我不完全理解。请检查所需问题的可能解决方案
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();
    }
}