PeMa
PeMa

Reputation: 1716

Spring data JPA: Yet another LazyInitializationException issue

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

Answers (1)

meriton
meriton

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:

  • 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.

Upvotes: 1

Related Questions