Spring 数据 JPA:又一个 LazyInitializationException 问题
Spring data JPA: Yet another LazyInitializationException issue
我只想从数据库中获取单向相关的 @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)
);
}
正如我所说,从测试方法中删除@Transactional 会让测试失败,程序运行时调用端点也是如此。
我知道不想在关系上设置 fetch.EAGER
,因为对于我们所有其他代码来说,延迟获取 List<Entity2>
是有意义的。
编辑:目前我使用 @Query
注释方法,其中我手动 join fetch
列表,但在我看来这并不是一个通用的解决方案。
问题在于,由于 getEntity2List()
未在您的代码中调用,因此在 @Transactional
方法完成后首先调用它,此时不再可能延迟加载。
您可以通过多种方式解决此问题:
- 在
@Transactional
方法中调用 getEntity2List()
,在仍然可能的情况下触发延迟加载,或者
- 使用连接
@OneToMany
关系的查询获取实体,或
- 告诉您的 JSON 库如何编写延迟加载代理。对于 Jackson,您可以使用 jackson-datatype-hibernate 模块完成此操作。
我只想从数据库中获取单向相关的 @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)
);
}
正如我所说,从测试方法中删除@Transactional 会让测试失败,程序运行时调用端点也是如此。
我知道不想在关系上设置 fetch.EAGER
,因为对于我们所有其他代码来说,延迟获取 List<Entity2>
是有意义的。
编辑:目前我使用 @Query
注释方法,其中我手动 join fetch
列表,但在我看来这并不是一个通用的解决方案。
问题在于,由于 getEntity2List()
未在您的代码中调用,因此在 @Transactional
方法完成后首先调用它,此时不再可能延迟加载。
您可以通过多种方式解决此问题:
- 在
@Transactional
方法中调用getEntity2List()
,在仍然可能的情况下触发延迟加载,或者 - 使用连接
@OneToMany
关系的查询获取实体,或 - 告诉您的 JSON 库如何编写延迟加载代理。对于 Jackson,您可以使用 jackson-datatype-hibernate 模块完成此操作。