1

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.

1 Answer 1

1

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:

  • invoke getEntity2List() within a @Transactional method, to trigger lazy loading while it is still possible, or
  • fetch the entity with a query that joins the @OneToMany relationship, or
  • tell your JSON library how to write lazy loading proxies. For Jackson, you can accomplish this with the jackson-datatype-hibernate module.
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, for clarification. This hibernate aware jackson mapper sounds as if this is the prefered solution for me. Wonder why this is not in the default spring boot configuration for @RestController.
... and how add it myself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.