首页 > 解决方案 > Spring data JPA:又一个 LazyInitializationException 问题

问题描述

我想简单地@OneToMany从数据库中获取单向相关的数据,并为这些数据提供一个响应式 Spring webFlux 端点。

但是,我无法摆脱生产代码中的 LazyInitializationException。在我的测试方法中,我使用 @Transactional 并且一切正常。但即使将 @Transactional 添加到控制器方法中也没有任何帮助。

简单示例:

实体2:

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@Entity
public class Entity2 {

    @Id
    @GeneratedValue
    private Integer id;
    private String string;

}

实体1:

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@Entity
public class Entity1 {

    @Id
    @GeneratedValue
    private Integer id;

    @JsonIgnore
    @OneToMany
    @JoinTable(
            name = "entity1_to_entity2",
            joinColumns = {@JoinColumn(name = "entity1_id", referencedColumnName = "id")},
            inverseJoinColumns = {@JoinColumn(name = "entity2_id", referencedColumnName = "id")}
    )
    private List<Entity2> entity2List;

}

样品服务:

@Service
public class SampleService {

    private final Entity1Repository entity1Repository;

    @Autowired
    public SampleService(Entity1Repository entity1Repository) {
        this.entity1Repository = entity1Repository;
    }

    public Optional<Entity1> findEntity1(Integer id) {
        return entity1Repository.findById(id);
    }

}

样品组件:

@Component
public class SampleComponent {

    private final SampleService sampleService;

    @Autowired
    public SampleComponent(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    public List<Entity2> getList(Integer entity1_id) {
        return sampleService
                .findEntity1(entity1_id)
                .map(Entity1::getEntity2List)
                .orElse(Collections.emptyList());
    }

}

样品控制器:

@RestController
public class SampleController {

    private final SampleComponent sampleComponent;

    @Autowired
    public SampleController(SampleComponent sampleComponent) {
        this.sampleComponent = sampleComponent;
    }

    @GetMapping(value = "/endpoint")
    @Transactional
    public Mono<List<Entity2>> findList(@RequestParam Integer id) {
        return Mono.just(sampleComponent.getList(id));
    }

}

测试方法:

@Test
@Transactional
public void simpleTest() {
    List<Entity2> entity2List = new ArrayList<>();
    entity2List.add(Entity2.builder().string("foo").build());
    entity2List.add(Entity2.builder().string("bar").build());
    entity2Repository.saveAll(entity2List);

    Entity1 entity1 = entity1Repository.save(Entity1.builder().entity2List(entity2List).build());

    sampleController.findList(entity1.getId())
            .subscribe(
                    list -> list.forEach(System.out::println)
            );
}

正如我所说,从测试方法中删除 @Transactrional 会让测试失败,在程序运行时调用端点也是如此。

我知道不想建立这种关系,因为我们所有其余的代码都懒惰地获取fetch.EAGER它是有意义的。List<Entity2>

编辑:目前我使用带@Query注释的方法,join fetch手动列出列表,但在我看来这并不是一个通用的解决方案。

标签: hibernatespring-data-jpalazy-loadingspring-webflux

解决方案


问题是,由于getEntity2List()没有在您的代码中调用,因此在@Transactional方法完成后首先调用它,此时不再可能进行延迟加载。

您可以通过多种方式解决此问题:

  • getEntity2List()在方法中调用@Transactional,以在仍然可能的情况下触发延迟加载,或者
  • 使用加入关系的查询获取实体@OneToMany,或
  • 告诉您的 JSON 库如何编写延迟加载代理。对于 Jackson,您可以使用jackson-datatype-hibernate模块完成此操作。

推荐阅读