Reputation: 101
I am trying to implement bidirectional one to many relationship using Spring Data JPA. I have created test cases for save and get data and there is no issue in the mapping and data is getting saved in both the tables. But when I am trying to create data by hitting the Post request, foreign key is not getting saved. Mapping between Customer and Phone is bidirectional one to many.
OneToMany1Application
package com.jwt.onetomany;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OneToMany1Application {
public static void main(String[] args) {
SpringApplication.run(OneToMany1Application.class, args);
}
}
DemoController
package com.jwt.onetomany.controller;
import java.net.URI;
import java.util.List;
import org.apache.tomcat.jni.Poll;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.jwt.onetomany.entity.Customer;
import com.jwt.onetomany.repo.CustomerRepository;
@RestController
public class DemoController {
@Autowired
CustomerRepository customerRepository;
@GetMapping("/getall")
ResponseEntity<List<Customer>> getAllCustomers() {
Iterable<Customer> findAll = customerRepository.findAll();
List<Customer> customers = (List<Customer>) findAll;
return new ResponseEntity<List<Customer>>(customers, HttpStatus.OK);
}
@PostMapping(path = "/customers", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<?> createPoll(@RequestBody Customer customer) {
customerRepository.save(customer);
// Set the location header for the newly created resource
HttpHeaders responseHeaders = new HttpHeaders();
URI newPollUri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(customer.getId())
.toUri();
responseHeaders.setLocation(newPollUri);
return new ResponseEntity<>(null, responseHeaders, HttpStatus.CREATED);
}
}
Customer
package com.jwt.onetomany.entity;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<PhoneNumber> phoneNumbers;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
public void setPhoneNumbers(Set<PhoneNumber> phoneNumbers) {
this.phoneNumbers = phoneNumbers;
}
public void addPhoneNumber(PhoneNumber number) {
if (number != null) {
if (phoneNumbers == null) {
phoneNumbers = new HashSet<>();
}
number.setCustomer(this);
phoneNumbers.add(number);
}
}
}
PhoneNumber
package com.jwt.onetomany.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name="phone_number")
public class PhoneNumber {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String number;
private String type;
@ManyToOne
@JoinColumn(name = "customer_id")
@JsonIgnore
private Customer customer;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
CustomerRepository
package com.jwt.onetomany.repo;
import org.springframework.data.repository.CrudRepository;
import com.jwt.onetomany.entity.Customer;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
}
Database use is mysql:
SQL Script
-----------
CREATE TABLE `customer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
CREATE TABLE `phone_number` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` int(11) DEFAULT NULL,
`number` varchar(20) DEFAULT NULL,
`type` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK1j552es3t8oswmbjr0rw15ew6` (`customer_id`),
CONSTRAINT `FK1j552es3t8oswmbjr0rw15ew6` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`),
CONSTRAINT `phone_number_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
Upvotes: 3
Views: 7137
Reputation: 453
"Remove the mappedBy = "customer" attribute and add the @JoinColumn(name = "customer_id") annotation to Set in the Customer class"
This might seem to solve the problem generating the foreign key in the child table (phone_number) in this case, however the solution will fall back internally to a OnetoMany relationship unidirectional, where the number of queries generated by jpa will be 2n+1.
do the following in the Customer class:
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JsonManagedReference(value = "cust_phone")
private Set<PhoneNumber> phoneNumbers;
and in the PhoneNumber Class:
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "customer_id")
@JsonIgnore
@JsonBackReference(value = "cust_phone")
private Customer customer;
This sets the foreign key properly and the number of queries generated is also N+1.
Upvotes: 0
Reputation: 358
You cannot show the data which one using foreign key. Suppose you need to show the values means to manually the data using the foreign key.
Upvotes: 0
Reputation: 99
Remove the mappedBy = "customer"
attribute and add the @JoinColumn(name = "customer_id")
annotation to Set<PhoneNumber>
in the Customer class.
The @JoinColumn
annotation can be optionally removed from PhoneNumber
class.
The mappedBy
attribute specifies that the relation is owned by the other class. So, in the above case, the responsibility of adding the Foreign Key is with the child class. Replacing it with @JoinColumn
annotation specifies that the ownership of the relation is with the parent class.
Refer: JPA JoinColumn vs mappedBy
Upvotes: 6