Reputation: 437
I have gone through several Q/As on stackoverflow and several other tutorials online to find what am I exactly missing for the problem described below.
Background: I am learning to use restful APIs in my android application and for that reason, I have written a simple doctor-patient management app. There's a one to many relationship between a doctor and his patients. i.e. One doctor can have many patients.
Problem: I am using one user table that is supposed to maintain all the user information, i.e. doctor and patients' basic info is maintained in this table and this table is also used for determining what type of user is trying to log in, so that appropriate screens can be presented. Here's how the entity for that table looks like:
@Entity
public class ConcreteUser implements User{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name= "USER_ID")
private long id;
private String name;
private String email;
private int age;
private SEX sex;
private String accessLevel;
public ConcreteUser() {
}
// gettersand setters here
}
This Entity has one to one relationship with tables that maintain doctors and patient entities. And as mentioned earlier, doctors and patient entities have one to one relationship. Here's how those two entities look like:
@Entity
public class PatientEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "PATIENT_RECORD_ID")
private long recordId;
// specify this as a foreign key from ConcreteUser entity
@OneToOne(cascade = CascadeType.ALL) /*CascadeType.ALL should not be required according to almost all the tutorials I have seen - But I always get unsaved transient object error if I don't do this and try to save a patient entity */
@JoinColumn(name="USER_ID")
private ConcreteUser patient;
@ManyToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "DOCTOR_RECORD_ID")
@JsonBackReference
private DoctorEntity doctor;
public PatientEntity() {
}
public void setDoctor(DoctorEntity doctor) {
this.doctor = doctor;
//if(!doctor.getPatients().contains(this)){
// doctor.addPatient(this);
//}
/* Commented out code always leads to stack overflow error */
/* although, according to tutorial in the link below, this code is necessary */
/* http://en.wikibooks.org/wiki/Java_Persistence/OneToMany */
}
// getters and setters are not shown
}
And lastly, here's my Doctor entity:
@Entity
public class DoctorEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "DOCTOR_RECORD_ID")
private long recordId;
// specify this as a foreign key from ConcreteUser entity
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="USER_ID")
private ConcreteUser doctor;
@OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
@JsonManagedReference
private Collection<PatientEntity> patients = new ArrayList<PatientEntity>();
public DoctorEntity() {
}
public boolean addPatient(PatientEntity p) {
boolean status = false;
status = patients.add(p);
//if (p.getDoctor() != this) {
// p.setDoctor(this);
//}
return status;
}
public boolean removePatient(PatientEntity p) {
boolean status = false;
status = patients.remove(p);
//if (p.getDoctor() != this) {
// p.setDoctor(null);
//}
return status;
}
// getters and setters are not shown. Same problem with the commented out code as described above
}
Now to test the fact that, when a POJO object of PatientEntity can be saved and it retains the information, I am using the following test case:
@Test
public void TestPatientDoctorManyToOne() throws Exception{
PatientEntity p1 = TestData.getPatientEntity(patient1);
DoctorEntity d = TestData.getDoctorEntity(doctor);
p1.setDoctor(d);
p1 = patientService.addPatient(p1);
assertNotNull(p1);
PatientEntity p2 = patientService.getPatientById(p1.getRecordId());
assertNotNull(p2);
assertNotNull(p2.getDoctor());
assertEquals(p1.getRecordId(), p2.getRecordId());
assertEquals(p1.getDoctor().getRecordId(), p2.getDoctor().getRecordId());
assertEquals(p1.getDoctor().getDoctor().getEmail(), p2.getDoctor().getDoctor().getEmail());
}
In the above test case, assertNotNull(p2.getDoctor());
assertion fails, as the returned patient entity does not contain doctor object at all.
Here's the log:
Outgoing:
"{"recordId":0,"patient":{"id":0,"name":"Patient-0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","email":"0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","age":50,"sex":"MALE","accessLevel":"patient"},"doctor":{"recordId":0,"doctor":{"id":0,"name":"Doctor-f025c8ce-8c31-4681-b673-a9e322dccf5a","email":"f025c8ce-8c31-4681-b673-a9e322dccf5a","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[]}}"
Incoming:
{"recordId":16,"patient":{"id":33,"name":"Patient-0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","email":"0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","age":50,"sex":"MALE","accessLevel":"patient"}}
As you can see, the returned object doesn't have a Doctor entity at all.
However, when I try to save the doctor entity with patients in it, it is saved with no problem. i.e. the following test case passes:
@Test
public void testDoctorPatientOneToMany() throws Exception {
PatientEntity p1 = TestData.getPatientEntity(patient1);
PatientEntity p2 = TestData.getPatientEntity(patient2);
DoctorEntity d = TestData.getDoctorEntity(doctor);
d.addPatient(p1);
d.addPatient(p2);
d = doctorService.addDoctor(d);
DoctorEntity d2 = doctorService.getDoctorById(d.getRecordId());
assertNotNull(d2);
assertEquals(d2.getRecordId(), d.getRecordId());
assertEquals(d2.getDoctor().getEmail(), d.getDoctor().getEmail());
}
Transactions for the above test case: Outgoing:
"{"recordId":17,"doctor":{"id":43,"name":"Doctor-e4baeee7-eaaa-443e-8845-e0b12d7be82f","email":"e4baeee7-eaaa-443e-8845-e0b12d7be82f","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[{"recordId":21,"patient":{"id":44,"name":"Patient-d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","email":"d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","age":50,"sex":"MALE","accessLevel":"patient"}},{"recordId":22,"patient":{"id":45,"name":"Patient-5c9cfa3c-ee79-4aea-a193-4d8762f58431","email":"5c9cfa3c-ee79-4aea-a193-4d8762f58431","age":50,"sex":"MALE","accessLevel":"patient"}}]}[\r][\n]"
Incoming:
{"recordId":17,"doctor":{"id":43,"name":"Doctor-e4baeee7-eaaa-443e-8845-e0b12d7be82f","email":"e4baeee7-eaaa-443e-8845-e0b12d7be82f","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[{"recordId":21,"patient":{"id":44,"name":"Patient-d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","email":"d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","age":50,"sex":"MALE","accessLevel":"patient"}},{"recordId":22,"patient":{"id":45,"name":"Patient-5c9cfa3c-ee79-4aea-a193-4d8762f58431","email":"5c9cfa3c-ee79-4aea-a193-4d8762f58431","age":50,"sex":"MALE","accessLevel":"patient"}}]}
I apologize for along post, but I think I have exhausted all my resources. I'd absolutely worship anyone who decides to take a look at it and points out where the problem is. At this point, I am not even sure if I am testing this thing right, or my expectations are correct.
Upvotes: 2
Views: 238
Reputation: 6190
This is because by specifying mappedBy="doctor" in the DoctorEntity class
@Entity
public class DoctorEntity {
@OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
@JsonManagedReference
private Collection<PatientEntity> patients = new ArrayList<PatientEntity>();
public DoctorEntity() {
}
}
you are saying that DoctorEntity is no more the owner of the one-to-many relationship. PatientEntity is the owner. Hence during the save of PatientEntity (in the first test case) the foreign key of doctor entity is not updated in the Patient table.
mappedBy
is equivalent to specifying inverse=true
in an xml format.
Follow this link for a detailed explanation on what queries are executed when inverse=true or inverse=false is specified in the one-to-many mapping.
Upvotes: 1