Reputation: 104
I am trying to implement ONE-TO-MANY Mapping in REST but getting null reference of USERDETAILS table in Companies table.
Also added the commented part, when I was using the commented part I was getting expected output while fetching the data through getAllUsers(). But it was creating one extra column don't know how to deal with the same.
Below are the Model classes :
USERDETAILS : :
package com.restapi.user.entity;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "USER_DETAIL_INFORMATION")
@Data
@AllArgsConstructor
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"createdDate","modifyDate"},allowGetters = true)
public class UserDetails {
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "user_id_sequence")
@SequenceGenerator(name = "user_id_sequence",sequenceName = "user_Detail_information_Seq")
@JsonProperty(value ="id" )
private Long userId;
@JsonProperty("name")
@Column(name = "user_name")
private String userName;
@JsonProperty("email")
@Column(name = "user_email")
private String userEmail;
@JsonProperty("address")
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "user_address_id")
@JsonManagedReference
private Address userAddress;
@Column(name ="user_creation_date")
@Temporal(TemporalType.DATE)
@CreatedDate
@JsonProperty("createdDate")
private Date userCreationDate;
@LastModifiedDate
@Temporal(TemporalType.DATE)
@JsonProperty("modifyDate")
@Column(name = "user_modification_date")
private Date modifyDate;
@JsonProperty("companies")
@OneToMany(mappedBy = "userDetailsid",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<Companies> companies;
/*@JsonProperty("companies")
@OneToMany(cascade = CascadeType.ALL)
//, mappedBy = "userDetailsid"
@JoinColumn
private List<Companies> companies;
*/
}
Address : :
package com.restapi.user.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "Address_Details")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "address_seq")
@SequenceGenerator(name = "address_seq",sequenceName = "Address_detail_seq")
@Column(name = "ad_id")
private Long addressId;
@JsonProperty(value = "city")
@Column(name = "ad_city")
private String city;
@JsonProperty(value = "state")
@Column(name = "ad_state")
private String state;
@OneToOne(mappedBy ="userAddress")
@JsonBackReference
private UserDetails userDetails;
}
COMPANIES : :
package com.restapi.user.entity;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonProperty;
@Entity
@Table(name = "COMPANY_INFO_DETAILS")
public class Companies {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "company_seq")
@SequenceGenerator(name = "company_seq",sequenceName = "Compamy_seq_generator")
private Long companyId;
@JsonProperty("c_code")
@Column(name = "c_code")
private String companyCode;
@JsonProperty("c_name")
@Column(name = "c_name")
private String companyName;
@ManyToOne
@JoinColumn(name = "COMAPNY_user_id")
private UserDetails userDetailsid;
/* @ManyToOne(cascade = CascadeType.ALL)
private UserDetails userDetailsid; */
public Companies() {
super();
// TODO Auto-generated constructor stub
}
public Companies(Long companyId, String companyCode, String companyName, UserDetails userDetails) {
super();
this.companyId = companyId;
this.companyCode = companyCode;
this.companyName = companyName;
this.userDetailsid = userDetails;
}
public Long getCompanyId() {
return companyId;
}
public void setCompanyId(Long companyId) {
this.companyId = companyId;
}
public String getCompanyCode() {
return companyCode;
}
public void setCompanyCode(String companyCode) {
this.companyCode = companyCode;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public UserDetails getUserDetails() {
return userDetailsid;
}
public void setUserDetails(UserDetails userDetails) {
System.out.println("user DETAILD"+userDetails.toString());
this.userDetailsid = userDetails;
}
}
Controller
package com.restapi.user.controller;
import java.util.List;
import java.util.Optional;
import javax.management.loading.PrivateClassLoader;
import javax.websocket.server.PathParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.restapi.user.entity.UserDetails;
import com.restapi.user.exceptionHandling.DataInsertionException;
import com.restapi.user.exceptionHandling.DataNotFoundException;
import com.restapi.user.helper.HelperMethods;
import com.restapi.user.service.UserService;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private HelperMethods helper;
/***
* This API will save the user Details in the database table.
*
* @return
*/
@PostMapping("/addnewUser")
public ResponseEntity<UserDetails> addNewUser(@RequestBody UserDetails userDetails) {
try {
if (helper.isNullEmpty(userDetails.getUserName())) {
throw new DataInsertionException("UserName is missing from the request- it is a mandatory parameter!!");
} else if (userDetails.getUserAddress()==null) {
throw new DataInsertionException(
"UserAddress is missing from the request- it is a mandatory parameter!!");
} else if (helper.isNullEmpty(userDetails.getUserEmail())) {
throw new DataInsertionException(
"User's Email address is missing from the request- it is a mandatory parameter!!");
} else {
UserDetails details = userService.saveUserDetails(userDetails);
return ResponseEntity.status(HttpStatus.CREATED).body(details);
}
} catch (Exception e) {
throw new DataInsertionException(e.getMessage());
}
}
/***
* This API will fetch the list of users available
*
* @return
* @throws Exception
*/
@GetMapping("/getAllUserDetails")
public ResponseEntity<List<UserDetails>> getListOfUsers() throws Exception {
try {
List<UserDetails> userDetails = userService.getAllUsers();
System.out.println(userDetails.size());
if (userDetails.size() < 1) {
throw new DataNotFoundException("No Data FOUND!!");
} else {
return new ResponseEntity<List<UserDetails>>(userDetails, HttpStatus.OK);
}
} catch (Exception e) {
throw new DataNotFoundException(e.getMessage());
}
}
/***
* This API will fetch the user Details by using the ID
*
* @return
* @throws Exception
*/
@GetMapping("/id/{id}")
public ResponseEntity<UserDetails> getUserById(@PathParam("id") Long id) throws Exception {
try {
Optional<UserDetails> userDetails = userService.getUserById(id);
if (!userDetails.isPresent()) {
throw new DataNotFoundException("No Data FOUND!!");
} else {
return new ResponseEntity<UserDetails>(userDetails.get(), HttpStatus.OK);
}
} catch (Exception e) {
throw new DataNotFoundException(e.getMessage());
}
}
}
REQUEST STRUCTURE ::
{
"name": "test_NAME",
"email": "abc@gmail",
"address": {
"city": "abc",
"state": "JK"
},
"companies": [
{
"c_code": "TCS",
"c_name": "TATA"
},
{
"c_code": "CTS",
"c_name": "COGNI"
}
]
}
RESPONSE_BODY ::
{
"id": 3,
"name": "test_NAME",
"email": "abc@gmail",
"address": {
"addressId": 3,
"city": "abc",
"state": "JK"
},
"createdDate": "2021-05-14T20:13:32.154+00:00",
"modifyDate": "2021-05-14T20:13:32.154+00:00",
"companies": [
{
"companyId": 5,
"userDetails": **null**,
"c_code": "TCS",
"c_name": "TATA"
},
{
"companyId": 6,
"userDetails": **null**,
"c_code": "CTS",
"c_name": "COGNI"
}
]
}
SERVICE CLASS :
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.restapi.user.entity.UserDetails;
import com.restapi.user.repository.UserRepository;
@Service
public class UserService {
@Autowired
private UserRepository repository;
public UserDetails saveUserDetails(UserDetails userDetails) {
return repository.save(userDetails);
}
public List<UserDetails> getAllUsers() {
return repository.findAll();
}
public Optional<UserDetails> getUserById(Long id) {
Optional<UserDetails> user = repository.findById(id);
return user;
}
}
Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.restapi.user.entity.UserDetails;
@Repository
public interface UserRepository extends JpaRepository<UserDetails, Long>{
}
GetRequest result After Commented part
{
"id": 1,
"name": "test_NAME",
"email": "abc@gmail",
"address": {
"addressId": 1,
"city": "abc",
"state": "JK"
},
"createdDate": "2021-05-15",
"modifyDate": "2021-05-15",
"companies": [
{
"companyId": 2,
"userDetails": null,
"c_code": "CTS",
"c_name": "COGNI"
},
{
"companyId": 1,
"userDetails": null,
"c_code": "TCS",
"c_name": "TATA"
}
]
}
]
If not using Commented part
{
"id": 1,
"name": "test_NAME",
"email": "abc@gmail",
"address": {
"addressId": 1,
"city": "abc",
"state": "JK"
},
"createdDate": "2021-05-15",
"modifyDate": "2021-05-15",
"companies": []
}
]
When using Commented part :
When not using commented part :
Someone please highlight what I am missing or doing wrong ? Thanks in advance
Upvotes: 1
Views: 1556
Reputation: 298
Thanks for sharing the UserService. Based from your code in saving user details:
public UserDetails saveUserDetails(UserDetails userDetails) {
return repository.save(userDetails);
}
You are not defining any reference between a user and his/her companies. Again, the cascade you did on the UserDetails object will only means that (since you use CascadeType.ALL) once you save the UserDetails object the save operation will also be cascaded to the Company object however, JPA still needs to know the references between these objects, thus you have to set the userDetailsid for each Company object (during construction). Update your service to something like this:
public UserDetails saveUserDetails(UserDetails userDetails) {
userDetails.getCompanies().stream().forEach(company -> company.setUserDetailsid(userDetails));
return repository.save(userDetails);
}
Note: Just remove your commented codes. They would not work anyways without the update of your service as above.
Lastly, I am seeing you are using Lombok here, so why not use it all through out of your classes to remove boilerplates and update the company something like below. I have put @JsonBackReference to avoid infinite recursion of jackson on your response during add new user and create a getter method getUserReferenceId just for you to see the response of company with a reference userId.
@Entity
@Table(name = "COMPANY_INFO_DETAILS")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Companies {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "company_seq")
@SequenceGenerator(name = "company_seq", sequenceName = "Compamy_seq_generator")
private Long companyId;
@JsonProperty("c_code")
@Column(name = "c_code")
private String companyCode;
@JsonProperty("c_name")
@Column(name = "c_name")
private String companyName;
@ManyToOne
@JsonBackReference
@JoinColumn(name = "COMAPNY_user_id")
private UserDetails userDetailsid;
public Long getUserReferenceId() {
return userDetailsid != null ? userDetailsid.getUserId() : null;
}
}
Upvotes: 3