1
resposta

[Dúvida] Como salvar objeto cuja entidade está associada a outra? (Spring, JPA)

Boa tarde a todos! Estou implementando um pequeno projeto para estudar JPA e aprimorar. Estou implementando 2 tabelas, com relação 1:N. Ao tentar salvar novo objeto do tipo cidade, no qual está associado a país, não estou tendo sucesso. Estou usando esse json no endpoint via Postman:

{
    "city": "Cidade teste 1",
    "countryId": {
        "country": "Pais teste 1"
    }
}

Objetivo: criar um registro de cidade e automaticamente de país passando os nomes, tendo os id´s de cada entidade gerados automaticamente.

Gostaria de saber:

  1. Como salvar um objeto no endpoint de save para criar uma cidade e automaticamente um país?
  2. Quais são as melhores práticas pra criar registros (objetos) em endpoints cujas entidades (tabelas) estão relacionadas entre sí?

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Código da classe City:

@Entity
@Table(name = "city")
public class City implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(columnDefinition = "SMALLINT UNSIGNED NOT NULL")
    private BigInteger cityId;

    @NotBlank
    private String city;

    @Past
    private Date lastUpdate;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "countryId", columnDefinition = "SMALLINT UNSIGNED NOT NULL")
    private Country countryId;

    public City() {
    }

    public City(BigInteger cityId, @NotBlank String city, @NotBlank @Past Date lastUpdate, Country countryId) {
        this.cityId = cityId;
        this.city = city;
        this.lastUpdate = lastUpdate;
        this.countryId = countryId;
    }

    public BigInteger getCityId() {
        return cityId;
    }

    public void setCityId(BigInteger cityId) {
        this.cityId = cityId;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Date getLastUpdate() {
        return lastUpdate;
    }

    public void setLastUpdate(Date lastUpdate) {
        this.lastUpdate = lastUpdate;
    }

    public Country getCountryId() {
        return countryId;
    }

    public void setCountryId(Country countryId) {
        this.countryId = countryId;
    }    
}

Código da classe CityService:


@Service
public class CityService {

    private CityRepository cityRepository;
    private CountryRepository countryRepository;

    public CityService(CityRepository cityRepository, CountryRepository countryRepository) {
        this.cityRepository = cityRepository;
        this.countryRepository = countryRepository;
    }

    @Transactional
    public City save(City obj) {

        try {
            City city = new City();

            city.setCityId(null);
            city.setCity(obj.getCity());
            city.setLastUpdate(new Date());

            return cityRepository.save(city);
        } catch (RuntimeException e) {
            throw new ResourceNotFoundException(obj);
        }
    }    
}

Código da classe CityController:

@RestController
@RequestMapping("/api/v1")
@Tag(name = "City")
public class CityController {

    private CityService cityService;

    public CityController(CityService cityService) {
        this.cityService = cityService;
    }    

    @PostMapping(value = "/cities", produces = MediaType.APPLICATION_JSON_VALUE)
    @Operation(summary = "Cria uma cidade")
    public ResponseEntity<City> save(@RequestBody City city) {
        city = cityService.save(city);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/api/v1/cities/{cityId}")
                .buildAndExpand(city.getCityId()).toUri();
        return ResponseEntity.created(uri).body(city);
    }
}

Código da classe Country:


@Entity
@Table(name = "country")
public class Country implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(columnDefinition = "SMALLINT UNSIGNED NOT NULL")
    private BigInteger countryId;

    @NotBlank(message = "O country é obrigatório")
    private String country;

    @Past(message = "Informe uma data passada")
    private Date lastUpdate;

    @OneToMany(mappedBy = "countryId", cascade = CascadeType.ALL)
    @JsonIgnore
    private Set<City> cityList = new HashSet<>();

    public Country() {
    }

    public Country(BigInteger countryId, String country, Date lastUpdate) {
        this.countryId = countryId;
        this.country = country;
        this.lastUpdate = lastUpdate;
    }

    public BigInteger getCountryId() {
        return countryId;
    }

    public void setCountryId(BigInteger countryId) {
        this.countryId = countryId;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public Date getLastUpdate() {
        return lastUpdate;
    }

    public void setLastUpdate(Date lastUpdate) {
        this.lastUpdate = lastUpdate;
    }

    public Set<City> getCityList() {
        return cityList;
    }

}
1 resposta

Boa tarde Wesley, tudo bem ?

Reparei alguns pontos que podem te ajudar:

  1. Você poderia tratar os cadastros de forma isolada. Ter um endpoint para cadastrar cidade e outro para cadastrar país. Quem consumir sua API precisa respeitar a ordem das chamadas.
  2. Outro ponto, ao invés de trabalhar com classes Modelo diretamente, você poderia usar DTOs para mapear as informações que estão entrando no JSON e depois usar um Mapper para converter nas classes modelos (City e Country).
  3. Para você trabalhar com os relacionamentos das Classes e por consequência das tabelas, você precisa sempre usar a PK e FK, ou seja os IDs.
  4. Se as PKs são autogeradas você não consegue usar o método Set no atributo @Id da sua Classe @Entity.

Espero ter ajudado, caso tenha mais alguma dúvida, por favor nos envie novamente.

Bons estudos!