问题
I am using spring boot to create a REST API. In this API, I have a One to Many relationship between check-in and Guests. I created a controller for check-in and use that save function of spring JPA. The save method is updating both checkin and guest tables but for the guest table, the check-in foreign key in the guests table is not getting added instead showing as null. Please someone help me. I need to create both guests and checkin simultaneously.
Check-in Model
@Data
@Entity
public class Checkin {
@Id
private Long id;
private Integer no_of_guests;
@OneToMany(mappedBy = "checkin", cascade = CascadeType.ALL)
private List<Guest> guests;
}
Guests Model
@Data
@Entity
public class Guest {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long guest_id;
private String name;
private String mobile_no;
private String address;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "guest_checkin_id", nullable = false )
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Checkin checkin;
}
Check-in Controller
@RestController
@RequestMapping("/checkin")
public class CheckinController {
private final CheckinRepository checkinRepo;
private final GuestRepository guestRepo;
public CheckinController(CheckinRepository checkinRepo,GuestRepository guestRepo){
this.checkinRepo = checkinRepo;
this.guestRepo = guestRepo;
}
@PostMapping("/add")
ResponseEntity<Object> roomCheckin(@RequestBody Checkin checkin){
if(checkinRepo.save(checkin) != null){
return ResponseEntity.accepted().body("Checkin Successfull");
}
return ResponseEntity.unprocessableEntity().body("Failed to Checkin");
}
}
回答1:
Using entity classes as view model classes may be a little bit tricky, especially in this case when there is a bi-directional one-to-many relation between Checkin
and Guest
.
Let's start with verifying that the entity classes and repository is working as depicted. In order to make test run, I had to add @GeneratedValue
for id field in class Checkin
.
Other changes:
- Use
Set
instead ofList
. See https://stackoverflow.com/a/6563037/14072498 - Use
@Getter
and@Setter
instead of@Data
for entity classes with relations
I've added a test class CheckinRepositoryTest
below in order to verify the code.
As mentioned earlier, using entity classes as view model classes can be tricky, hence next step will be to introduce two new view model classes: CheckinVM
and GuestVM
, and a new service class GuestService
that will be responsible for saving Checkin
with Guest
instances. Code is shown below.
Please note that I added just the skeleton for GuestService
class, I leave it to you to implement it. Code inside CheckinRepositoryTest
indicates how to implement it.
With view model classes in place, @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
should be removed.
With all pieces up and running, you should add validation to your new view model classes, and some integration tests that verifies the behaviour. If you are following TDD, you'll add the tests on your way.
You should also have a look at your controller method and use appropriate status codes for success and failure situations.
Guest class
package no.mycompany.myapp.misc;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Getter
@Setter
@Entity
public class Guest {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long guest_id;
private String name;
private String mobile_no;
private String address;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "guest_checkin_id", nullable = false)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Checkin checkin;
}
GuestVM class
package no.mycompany.myapp.misc;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class GuestVM {
private long id;
private String name;
private String mobile_no;
private String address;
public GuestVM(Guest guest) {
this.id = guest.getGuest_id();
this.name = guest.getName();
this.mobile_no = guest.getMobile_no();
this.address = guest.getAddress();
}
}
Checkin class
package no.mycompany.myapp.misc;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Getter
@Setter
@Entity
public class Checkin {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer no_of_guests;
@OneToMany(mappedBy = "checkin", cascade = CascadeType.ALL)
private Set<Guest> guests = new HashSet<>();
}
CheckinVM class
package no.mycompany.myapp.misc;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@Setter
@NoArgsConstructor
public class CheckinVM {
private long id;
private List<GuestVM> guests;
public CheckinVM(Checkin checkin) {
this.id = checkin.getId();
this.guests = checkin.getGuests().stream().map(GuestVM::new).collect(Collectors.toList());
}
}
Checkin repository
package no.mycompany.myapp.misc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CheckinRepository extends JpaRepository<Checkin, Long> { }
CheckinRepositoryTest
package no.mycompany.myapp.misc;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@DataJpaTest
public class CheckinRepositoryTest {
@Autowired
TestEntityManager testEntityManager;
@Autowired
CheckinRepository checkinRepository;
@Test
public void test() {
// create instances
var checkinInDb = new Checkin();
var guestInDb = new Guest();
// add relations
guestInDb.setCheckin(checkinInDb);
checkinInDb.getGuests().add(guestInDb);
// save check-in
checkinRepository.save(checkinInDb);
// verify that check-in has one guest
var checkin = testEntityManager.find(Checkin.class, checkinInDb.getId());
assertThat(checkin.getGuests().size()).isEqualTo(1);
// verify that guest is connected to a check-in
var guest = testEntityManager.find(Guest.class, guestInDb.getGuest_id());
assertThat(guest.getCheckin()).isNotNull();
}
}
CheckinService class: I leave this to you to implement
package no.mycompany.myapp.misc;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CheckinService {
private final CheckinRepository checkinRepository;
public CheckinVM saveCheckin(CheckinVM checkin) {
return null; // TODO: implement this
}
}
Controller class
package no.mycompany.myapp.misc;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/checkin")
@RequiredArgsConstructor
public class CheckinController {
private final CheckinService checkinService;
@PostMapping("/add")
ResponseEntity<Object> roomCheckin(@RequestBody CheckinVM checkin) {
if (checkinService.saveCheckin(checkin) != null) {
return ResponseEntity.accepted().body("Checkin Successful");
}
return ResponseEntity.unprocessableEntity().body("Failed to Checkin");
}
}
来源:https://stackoverflow.com/questions/65612311/one-to-many-relationship-in-spring-boot-rest-api