如何从我的控制器中的Hibernate/JPA加载延迟获取的项目

Mat*_*ann 132 java spring hibernate jpa spring-data

我有一个Person类:

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany(fetch = FetchType.LAZY)
    private List<Role> roles;
    // etc
}
Run Code Online (Sandbox Code Playgroud)

与多对多的关系是懒惰的.

在我的控制器中我有

@Controller
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonRepository personRepository;

    @RequestMapping("/get")
    public @ResponseBody Person getPerson() {
        Person person = personRepository.findOne(1L);
        return person;
    }
}
Run Code Online (Sandbox Code Playgroud)

PersonRepository就是这个代码,根据本指南编写

public interface PersonRepository extends JpaRepository<Person, Long> {
}
Run Code Online (Sandbox Code Playgroud)

但是,在这个控制器中,我实际上需要惰性数据.如何触发加载?

试图访问它将失败

未能懒惰地初始化角色集合:no.dusken.momus.model.Person.roles,无法初始化代理 - 没有会话

或其他例外取决于我尝试的内容.

我的xml描述,如果需要的话.

谢谢.

zag*_*gyi 191

你将不得不作出对延迟集合的显式调用,以初始化(通常的做法是调用.size()用于此目的).在Hibernate中有一个专门的this(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);

}
Run Code Online (Sandbox Code Playgroud)

此方法将使用JPQL的fetch join子句在单个往返数据库中急切加载角色关联,从而减轻上述解决方案中两个不同查询所带来的性能损失.

  • 有趣的是要注意,如果你只是`join`而没有`fetch`,那么这个集合将以`initialized = false`返回; 因此,一旦访问该集合,仍然会发出第二个查询.`fetch`是确保关系完全加载并避免第二次查询的关键. (7认同)
  • 请注意,这是一个简单的解决方案,但会导致对数据库的两个不同查询(一个针对用户,另一个针对其角色).如果您希望获得更好的性能,请尝试编写一个专用方法,该方法使用JPQL或其他人建议的Criteria API在一个步骤中热切地提取用户及其相关角色. (3认同)

rak*_*pan 33

虽然这是一篇旧帖子,但请考虑使用@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
}
Run Code Online (Sandbox Code Playgroud)

然后是如下的弹簧回购

@RepositoryRestResource(collectionResourceRel = "Employee", path = "Employee")
public interface IEmployeeRepository extends PagingAndSortingRepository<EmployeeEntity, String>           {

    @EntityGraph(value = "employeeAuthorities", type = EntityGraphType.LOAD)
    EmployeeEntity getByUsername(String userName);

}
Run Code Online (Sandbox Code Playgroud)

  • `@EntityGraph(attributePaths = "employeeGroups")` 可以直接在 Spring Data Repository 中使用来注释方法,而无需在 @Entity 代码上使用 `@NamedEntityGraph`,打开存储库时很容易理解。 (3认同)

Jos*_*tin 13

你有一些选择

  • 在存储库上编写一个方法,返回RJ建议的初始化实体.

更多的工作,最好的表现.

  • 使用OpenEntityManagerInViewFilter为整个请求保持会话打开.

减少工作量,通常在网络环境中可以接受.

  • 使用辅助类在需要时初始化实体.

减少工作量,当OEMIV不可用时很有用,例如在Swing应用程序中,但在存储库实现上也可能有用,可以一次性初始化任何实体.

对于最后一个选项,我编写了一个实用程序类JpaUtils来在某些deph中初始化实体.

例如:

@Transactional
public class RepositoryHelper {

    @PersistenceContext
    private EntityManager em;

    public void intialize(Object entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
    }
}
Run Code Online (Sandbox Code Playgroud)


Vla*_*cea 7

春季数据 JpaRepository

Spring DataJpaRepository定义了以下两种方法:

  • getOne,它返回一个实体代理,适合在持久化子实体时设置一个@ManyToOne@OneToOne父关联
  • findById,它在运行从关联表加载实体的 SELECT 语句后返回实体 POJO

但是,就您而言,您没有致电getOnefindById

Person person = personRepository.findOne(1L);
Run Code Online (Sandbox Code Playgroud)

因此,我假设该findOne方法是您在PersonRepository. 但是,该findOne方法在您的情况下不是很有用。由于您需要Person随同一起获取roles集合,因此最好使用findOneWithRoles方法来代替。

自定义 Spring Data 方法

您可以定义一个PersonRepositoryCustom接口,如下所示:

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

}

public interface PersonRepositoryCustom {
    Person findOneWithRoles(Long id);
}
Run Code Online (Sandbox Code Playgroud)

并像这样定义它的实现:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

就是这样!

  • 因为 EntityGraphs 计划不像 JPQL 那样被缓存。这可能会对性能造成重大影响。 (3认同)
  • 确切地。当我有时间的时候,我必须写一篇关于它的文章。 (3认同)

Nim*_*sky 6

它只能在交易中延迟加载.因此,您可以访问存储库中的集合,该集合具有事务 - 或者我通常所做的是get with association,或者将fetchmode设置为eager.


Mic*_*aev 6

我认为你需要OpenSessionInViewFilter来保持你的会话在视图渲染过程中打开(但这不是太好的做法).