Reputation: 1716
I want to simply get unidirectionally related @OneToMany
data from a database, and provide this data with a reactive Spring webFlux endpoint.
However, I can't get rid of the LazyInitializationException in the productive code. On my test methods, I use @Transactional and all works nicely. But even adding @Transactional to the controller methods didn't help at all.
Simple sample:
Entity2:
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@Entity
public class Entity2 {
@Id
@GeneratedValue
private Integer id;
private String string;
}
Entity1:
@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;
}
SampleService:
@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);
}
}
SampleComponent:
@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());
}
}
SampleController:
@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 method:
@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)
);
}
As I said, removing @Transactrional from the test method let's the test fail, same for calling the endpoint when the program runs.
I know don't want to set fetch.EAGER
on the relationship, since it makes sense for all the rest of our code to have the List<Entity2>
lazily fetched.
Edit: At present I use a @Query
annotated method, where I join fetch
the List manually, but this in my eyes isn't really a general solution.
Upvotes: 1
Views: 870
Reputation: 70584
The problem is that, since getEntity2List()
is not invoked in your code, it is first invoked after the @Transactional
method has completed, when lazy loading is no longer possible.
You can solve this in various ways:
getEntity2List()
within a @Transactional
method, to trigger lazy loading while it is still possible, or@OneToMany
relationship, orUpvotes: 1