1

I would like to set a one to many relationship in a REST controller but I get a LazyInitializationException. I know how to solve that exception (JOIN FETCH, EntityGraph) but not in the context of the DomainClassConverter.

The DomainClassConverter allows you to use domain types in your Spring MVC controller method signatures directly, so that you don’t have to manually lookup the instances via the repository

@Entity
public class Company implements Serializable {

    @OneToMany(mappedBy = "company")
    @JsonIgnore
    private Set<Contract> contracts = new HashSet<>();

    public Company addContract(Contract contract) {
        this.agreements.add(contract);
        contract.setCompany(this);
        return this;
    }

}

@Entity
public class Contract implements Serializable {

    @ManyToOne
    private Company company;

}

The repository:

@Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {
}

@Repository
public interface ContractRepository extends JpaRepository<Company, Long> {
}

A POST /api/companies/1/contracts request maps the createContract method. The parameters are mapped correctly, so I have a Contract and a Company instance. When I try to add the contract to the company I get the LazyInitializationException. I tried to add @Transactional to the method and call .size() on company.getContracts() to initialize the collection but it's throwing the same exception.

@RestController
@RequestMapping("/api/companies/{companyId}")
public class CompanyContractResource {

    @PostMapping("/contracts")
    @Timed
    public ResponseEntity<Contract> createContract(@Valid @RequestBody Contract contract, @PathVariable("companyId") Company company) throws URISyntaxException {
        company.addContract(contract);
        Contract result = contractRepository.save(contract);
        return ResponseEntity.created(new URI("/api/contracts/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
    }

}
Sydney
  • 11,964
  • 19
  • 90
  • 142

2 Answers2

0

Try to change your controller method like this:

public ResponseEntity<Contract> createContract(@Valid @RequestBody Contract contract, @PathVariable("companyId") Long companyId) throws URISyntaxException {

        // fetching a company explicitly 
        Company company = companyRepository.findOne(companyId); 

        company.addContract(contract);

        Contract result = contractRepository.save(contract);

        return ResponseEntity.created(new URI("/api/contracts/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
}

Related info: 1, 2

Cepr0
  • 28,144
  • 8
  • 75
  • 101
0

I ended up not using DomainClassConverter. I changed the repository to fetch the lazy collection and then add the Contract.

@Repository
public interface CompanyRepository extends JpaRepository<Company, Long> {

    @Query(value = "SELECT c FROM Company c LEFT JOIN FETCH c.contracts WHERE c.id = ?1")
    Company findByIdWithContracts(Long id);

}

In the controller:

@RestController
@RequestMapping("/api/companies/{companyId}")
public class CompanyContractResource {

    @PostMapping("/contracts")
    @Timed
    public ResponseEntity<Contract> createContract(@Valid @RequestBody Contract contract, @PathVariable("companyId") Long companyId) throws URISyntaxException {
        Company company = companyRepository.findByIdWithContracts(companyId);
        company.addContract(contract);
        Contract result = contractRepository.save(contract);
        return ResponseEntity.created(new URI("/api/contracts/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
    }

}
Sydney
  • 11,964
  • 19
  • 90
  • 142