I am trying to create a spring boot application with two entities: Question and QuestionChoices. I'm using a bidirectional onetomany relationship. When I try to create a Question entity along with a list of QuestionChoices, the foreign key in the QuestionChoice is coming out null.
Here is my QuestionChoice entity:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class QuestionChoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String choice;
#ManyToOne
#JoinColumn(name = "question_id")
private Question question;
public QuestionChoice(String choice, Question question) {
this.choice = choice;
this.question = question;
}
public QuestionChoice(String choice) {
this.choice = choice;
}
}
Here is my Question entity:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int question_id;
private String questionName;
private String questionText;
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL)
private List<QuestionChoice> questionChoices;
public Question(String questionName, String questionText, List<QuestionChoice> questionChoices) {
this.questionName = questionName;
this.questionText = questionText;
this.questionChoices = questionChoices;
this.questionChoices.forEach(x -> x.setQuestion(this));
}
}
I have a QuestionRepository and QuestionChoiceRepository:
#Repository
public interface QuestionRepository extends JpaRepository<Question, Integer> {
}
#Repository
public interface QuestionChoiceRepository extends JpaRepository<QuestionChoice, Integer> {
}
Here is my controller:
#RestController
public class Controller {
QuestionRepository questionRepository;
QuestionChoiceRepository questionChoiceRepository;
public Controller(QuestionRepository questionRepository,
QuestionChoiceRepository questionChoiceRepository) {
this.questionRepository = questionRepository;
this.questionChoiceRepository = questionChoiceRepository;
}
#PostMapping("/question")
public Question createQuestion(#RequestBody Question question) {
return questionRepository.save(question);
}
#GetMapping("/question")
public List<Question> getQuestions() {
return questionRepository.findAll();
}
}
Here is my POST request:
POST http://localhost:8080/question
Content-Type: application/json
{
"questionName": "gender",
"questionText": "What is your gender?",
"questionChoices": ["male", "female"]
}
Here is the response from the POST:
{
"id": 1,
"questionName": "gender",
"questionText": "What is your gender?",
"questionChoices": [
{
"id": 1,
"choice": "male",
"question": null
},
{
"id": 2,
"choice": "female",
"question": null
}
]
}
And here is the response from the GET request:
GET http://localhost:8080/question
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 16 Oct 2019 11:10:51 GMT
[
{
"id": 1,
"questionName": "gender",
"questionText": "What is your gender?",
"questionChoices": []
}
]
So not only are the foreign keys of the QuestionChoices null, but the list of question choices in the question entity is also null.
Any idea what I am doing wrong?
Update
I've found a good solution to this problem here: Infinite Recursion with Jackson JSON and Hibernate JPA issue. The problem is with Jackson, not Hibernate. Just add an additional annotation to the reference objects within the entities and everything works great!
You're sending an array of strings for your questionChoices in the JSON body. Your JSON mapper needs to populate a List<Question> from this array of strings. So it needs to transform each String into a QuestionChoice object. Presumably, it does that by calling the QuestionChoice constructor that takes a String as argument.
So you're saving a Question which has QuestionChoices which all have a null question property. So you're telling JPA that all QuestionChoices don't have any question (since it's null). So JPA saves what you tell it to save: QuestionChoices without any parent question.
You need to properly initialize the question property of the QuestionChoice.
The deserializer will always use the default constructor to construct the object. Your custom constructor has no effect over the deserialization.
What you can do is:
1 - Guarantee the association in your service / controller layer
#PostMapping("/question")
public Question createQuestion(#RequestBody Question question) {
question.getQuestionChoices().forEach(choice -> choice.setQuestion(question));
return questionRepository.save(question);
}
or 2 - Guarantee the association in your setter method:
public class Question {
// omitted for brevity
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL)
private List<QuestionChoice> questionChoices;
public void setQuestionChoices(List<QuestionChoice> questionChoices) {
if (questionChoices != null) {
questionChoices.forEach(choice -> choice.setQuestion(this));
}
this.questionChoices = questionChoices;
}
}
Update
To prevent the infinite recursion, simply remove the 'question' attribute from the 'questionChoice' for presentation purposes.
I can think of two options:
1 - Set the question to null inside of questionChoice
#PostMapping("/question")
public Question createQuestion(#RequestBody Question question) {
Question savedQuestion = questionRepository.save(question);
savedQuestion.getQuestionChoices().forEach(choice -> choice.setQuestion(null));
return savedQuestion;
}
#GetMapping("/question")
public List<Question> getQuestions() {
List<Question> questions questionRepository.findAll();
questions.forEach(question -> {
question.getQuestionChoices.forEach(choice -> choice.setQuestion(null));
});
return questions;
}
This will save your question choices and foreign keys into the database, but will serialize questionChoices.question as null when sending the response to prevent infinite recursion.
2 - Use of DTOs.
You create a DTOs to serialize them as response objects to return exactly what you want to.
QuestionDTO.java
public class QuestionDTO {
private int question_id;
private String questionName;
private String questionText;
// notice that here you're using composition of DTOs (QuestionChoiceDTO instead of QuestionChoice)
private List<QuestionChoiceDTO> questionChoices;
// constructors..
// getters and setters..
}
QuestionChoiceDTO.java
public class QuestionChoiceDTO {
private int id;
private String choice;
// notice that you don't need to create the Question object here
// constructors..
// getters and setters..
}
Then in your controller:
#PostMapping("/question")
public QuestionDTO createQuestion(#RequestBody Question question) {
Question savedQuestion = questionRepository.save(question);
List<QuestionChoiceDTO> questionChoices = new ArrayList<>();
savedQuestion.getQuestionChoices().forEach(choice -> {
questionChoices.add(new QuestionChoiceDTO(choice.getId(), choice.getChoice()));
});
QuestionDTO response = new QuestionDTO(savedQuestion.getQuestion_id(), savedQuestion.getQuestionName(), savedQuestion.getQuestionText(), questionChoices);
return response;
}
#GetMapping("/question")
public List<QuestionDTO> getQuestions() {
List<Question> questions = questionRepository.findAll();
List<QuestionDTO> response = new ArrayList<>();
questions.forEach(question -> {
List<QuestionChoicesDTO> questionChoices = new ArrayList<>();
question.getQuestionChoices().forEach(choice -> questionChoices.add(new QuestionChoiceDTO(choice.getId(), choice.getChoice()));
responses.add(new QuestionDTO(savedQuestion.getQuestion_id(), savedQuestion.getQuestionName(), savedQuestion.getQuestionText(), questionChoices));
});
}
I always prefer the latter, because for big projects, IMHO, the use of DTO's can be a strong tool for organizing code and making concise use of request / response objects without using your domain objects.
You don't use your constructor public Question(...) after request. You should make a method to link choices with question
Related
I have multiple objects in my array using . If I then send this to my Spring Boot backend with axios and output the FormData beforehand, I get the following image. That fits. In the backend, however, I need this list of objects as an entity. In this case, of type List. Do I do that?
Frontend code:
let data = new FormData();
...
data.append("zugeordnet", JSON.stringify(personNamen));
await axios.post("/neuerEintrag", data,...)
React:
Backend:
#PostMapping("/neuerEintrag")
public String neuerEintrag(HttpServletRequest req,#RequestParam("zugeordnet") List<?> zugeordnet,..) {
List<User> userListe = (List<User>) zugeordnet;
for(User inListe : userListe) //ERROR here
{
System.out.println("USER :" + inListe);
}
...
}
java.lang.ClassCastException: class java.lang.String cannot be cast to class com.home.calendar.User.User
UPDATE
For completeness, here is the user entity and the complete method for a new entry.
#PostMapping("/neuerEintrag")
public String neuerEintrag(HttpServletRequest req, #RequestParam("beschreibung") String beschreibung,
#RequestParam("datum") Date datum, #RequestBody List<User> zugeordnet,
#RequestBody List<Freunde> kontaktAuswahl, #RequestParam("neuAlt") String neuAlt,
#RequestParam("kalenderId") int kalenderId) { }
The User Entity:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#JsonIgnoreProperties("user")
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
private List<Kalender> kalenderEinträge;
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String name, List<Kalender> kalenderEinträge) {
super();
this.name = name;
this.kalenderEinträge = kalenderEinträge;
}
public List<Kalender> getKalenderEinträge() {
return kalenderEinträge;
}
[getter/setter]
Spring can't parse an unknown object.
To get it work, I suggest a new class for the "request".
#Data // lombok - this generates getter/setters/equals/hashcode for you
public class NeuerEintragRequest {
private List<User> zugeordnet;
private String beschreibung;
private int kalendarId;
// and your others fields
}
The controller can now use very type-safe objects.
#PostMapping("/neuerEintrag")
public String neuerEintrag(#RequestBody NeuerEintragRequest request) {
for(User user : request.getUserlist()) {
// a logging framework is a lot better. Try to use log4j or slf4j.
log.info("USER: {}", user);
}
...
}
Typescript
Let axios handle the typing and serializing. See this tutorial: https://masteringjs.io/tutorials/axios/post-json
To post all the needed data, you can create a new object.
// no formdata - just send the object
const data = { zugeordnet: personNamen, kalendarId: 123, beschreibung: 'abc' };
await axios.post("/neuerEintrag", data);
You can also create a interface in typescript, but this is going to much for a stackoverflow-answer. Try to learn more about spring and typescript.
Based on question & comments ,
your front end call data.append("zugeordnet", JSON.stringify(personNamen)); is converting your object to List<String> instead of List<User>.
So you can transform this List<String> to List<User> in your postMapping:
#PostMapping("/neuerEintrag")
public String neuerEintrag(HttpServletRequest req,#RequestParam("zugeordnet") List<?> zugeordnet,..) {
ObjectMapper mapper=new ObjectMapper();
for(String str:zugeordnet){
System.out.println(mapper.readValue(str, User.class));
}
...
}
So, I am trying my hands-on MongoDB CRUD operations using spring-data-mongodb. Below are my model classes,
#Document(collection = "alumni_students")
public class AlumniStudent {
#Id
private String id;
private String firstName;
private String lastName;
private String email;
#DBRef
private AlumniDepartment alumniDepartment;
#DBRef
private List<AlumniSubject> alumniSubjects;
... getters/setters
#Document(collection = "alumni_department")
public class AlumniDepartment {
#Id
private String id;
private String departmentName;
private String location;
... getters/setters
#Document(collection = "alumni_subjects")
public class AlumniSubject {
#Id
private String id;
private String subjectName;
private int marks;
... getters/setters
I am using MongoRepository for individual collections for their operations like below,
#Repository
public interface AlumniStudentRepository extends MongoRepository<AlumniStudent, String> { }
#Repository
public interface AlumniDepartmentRepository extends MongoRepository<AlumniDepartment, String> {}
#Repository
public interface AlumniSubjectRepository extends MongoRepository<AlumniSubject, String> {}
I have so far done good while creation and getting the student details. The issue I am facing is while updating the student data. In that also specifically while updating the data, I am confused as hell.
Below is my update code from service layer,
#Autowired
AlumniStudentRepository alumniStudentRepo;
#Autowired
AlumniDepartmentRepository alumniDeptRepo;
#Autowired
AlumniSubjectRepository alumniSubjRepo;
public AlumniStudent updateStudent(AlumniStudent student, String id) {
Optional<AlumniStudent> fetchedStudent = alumniStudentRepo.findById(id);
**// UPDATE STUDENT DATA, WORKS FINE**
if (fetchedStudent.isPresent()) {
AlumniStudent studentFromDB = fetchedStudent.get();
studentFromDB.setFirstName(student.getFirstName());
studentFromDB.setLastName(student.getLastName());
studentFromDB.setEmail(student.getEmail());
**// UPDATE DEPARTMENT DATA, WORKS FINE**
if (student.getAlumniDepartment() != null) {
Optional<AlumniDepartment> deptData = alumniDeptRepo.findById(studentFromDB.getAlumniDepartment().getId());
if (deptData.isPresent()) {
AlumniDepartment alumniDepartment = deptData.get();
alumniDepartment.setDepartmentName(student.getAlumniDepartment().getDepartmentName());
alumniDepartment.setLocation(student.getAlumniDepartment().getLocation());
alumniDeptRepo.save(alumniDepartment);
studentFromDB.setAlumniDepartment(alumniDepartment);
}
}
**// UPDATE SUBJECTS ARRAY DATA.... HOW TO DO THIS?**
if (student.getAlumniSubjects() != null && !student.getAlumniSubjects().isEmpty()) {
// Problematic area. How to perform update of arraylist here?
}
return alumniStudentRepo.save(studentFromDB);
}
}
This is the URL to hit in postman :
localhost:8080/alumnistudents/60aa384ffbf1851f56c71bef
And this is the request body:
{
"firstName": "Babita",
"lastName": "Raman",
"email": "babita#gmail.com",
"alumniDepartment": {
"departmentName": "Android Developer",
"location": "Dubai"
},
"alumniSubjects": [
{
"subjectName": "Java",
"marks": 80
},
{
"subjectName": "Unit testing",
"marks": 60
},
{
"subjectName": "Docker",
"marks": 80
}
]
}
I tried some random code but ended up with
Cannot create a reference to an object with a NULL id.
Can someone help here with how to update the arrays data which is referenced as #DbRef ?
Thanks in advance everyone.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
Hi i need work flow(how to create pojo structure and how to communicate ) to create below json structure for a REST API get Call.
please help me out
[
{
"id":"1",
"Nmae":"gourav",
"Gender":"Male",
"usertype":{
"Typeone":"Admin",
"Status":"Active"
}
{
"Typetwo":"Agent",
"Status":"Disabled"
}
},
{
"id":"2",
"Nmae":"satya",
"Gender":"Male",
"usertype":{
"Typeone":"Admin",
"Status":"disabled"
}
{
"Typetwo":"Agent",
"Status":"active"
}
}
]
You can follow something like this,
The controller class, where you will define your routes -
#GET
#Path("/users")
#Produces({ javax.ws.rs.core.MediaType.APPLICATION_JSON})
public Response getUser(){
// Create Object of your DAO or Service class and use getUserDetails() to return a list of users, maybe from the Database.
List<User> usersList = serviceExampleObject.getUserDetails();
//Set the list field to the response object
return Response.status(Status.OK).entity(usersList).build();
}
If you want to follow SpringBoot Approach, your controller will be -
#RestController
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/users")
public ResponseEntity<?> getUsers(){
List<Users> usersList = userService.getUsersDetail();
return new ResponseEntity<>(usersList, HttpStatus.OK);
}
}
Create a User PoJo (Didn't add all getter/setters to make the post short)-
class User{
private Integer id;
private String name;
private String gender;
private List<UserType> userType;
public Integer getId() {
return id;
}
...
...
public void setUserType(List<UserType> userType) {
this.userType = userType;
}
}
Create a UserType PoJo -
class UserType{
private String typeNumber;
private String typeRole;
private String status;
public String getTypeNumber() {
return typeNumber;
}
...
...
public void setStatus(String status) {
this.status = status;
}
}
Also, the json response needs to be modified a bit. In the userType field, you can have fields with different names, so, i would suggest to follow the below approach :
"usertype": [
{
"typeNumber": "one",
"typeRole": "Admin",
"status": "Active"
},
{
"typeNumber": "two",
"typeRole": "Agent",
"status": "Disabled"
}
]
You can create a class named User with the fields you want and use javax.ws.rs.core.Response so something like:
#Produces({ javax.ws.rs.core.MediaType.APPLICATION_JSON})
Response getUser(){
User user = new User();
//set fields
return Response.status(Status.OK).entity(user).build();
}
You just have to create two classes one for user and another for UserType
Public Class UserType {
private String type;
private String status;
//Constructor, Getters and Setters, etc.
}
Public Class User {
private long id;
private String name;
private String gender;
private UserType userType;
//Constructor, Getters and Setters, etc.
}
Then when you return the User it will also return the UserTypecorresponding in a json like the one you described above. it should look like this :
#RequestMapping(value = "/url", method = RequestMethod.GET)
public User getUser(PARAMS_IF_ANY) {
// Application logic to find the user
return user;
}
and the trick is done
Good evening.
I am studying reactive programming and I have encountered the following problem.
I am running two parallel queries to the database and want to combine the results and give them back
#GetMapping
public Mono<User> get(#RequestParam("id") String id, #RequestParam("cId") String cId) {
Mono<User> userMono = Mono.fromCallable(() -> userServ.get(id))
.flatMap(userMono1 -> userMono1)
.subscribeOn(Schedulers.boundedElastic());
Mono<Comment> ger = Mono.fromCallable(() -> commentServ.ger(cId))
.flatMap(commentMono -> commentMono)
.subscribeOn(Schedulers.boundedElastic());
return Mono.zip(userMono, ger)
.map(pair -> {
User t1 = pair.getT1();
t1.setComment(pair.getT2());
return t1;
});
But the point is that the comment may be empty, and then I expect to return the json of such a structure
{
"id": "5e6cbf395214a42f51b57121",
"name": "Bob",
"surname": null,
"comment": null
}
Instead I get an empty response. Apparently this is due to mono zip, but how else can I combine the results, while maintaining query parallelism
My entities:
#Document
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Comment {
#Id
String id;
String userId;
String comment;
}
#Document
#AllArgsConstructor
#NoArgsConstructor
#Data
public class User {
#Id
private String id;
private String name;
private String surname;
private Comment comment;
}
How can I resolve this situation?
Zip/ZipWith need elements to produce their output. If it could be empty , you could use below methods to set some default value.
defaultIfEmpty(new Comment()) or
switchIfEmpty(Mono.fromSupplier(() -> new Comment())
If you do not want to use new Comment() and set null to comment object, we can try this way.
userMono
.zipWith(commentMono.defaultIfEmpty(new Comment()))
.map(pair -> {
User user = pair.getT1();
Comment comment = pair.getT2();
if(Objects.nonNull(comment.getUserId()))
user.setComment(comment);
return user;
});
I'm a newbie coder having just finished a 6 month coding crash-course. I'm working on a java webapp to demonstrate my skills, and the project idea I had involves retrieving JSON data from an API, something we didn't learn about in class. I made POJOs to match the JSON, and I'm trying to parse the JSON into java objects to store in a database, however my database tables are never filled with data when I run through the app. I suspect the problem is somewhere with my method to convert the JSON but any feedback is greatly appreciated. Here's all my code I think is relevant, sorry if its TMI. I also apologize if my code is ugly, I'm a beginner... Thanks!
API returns JSON like this:
{
"result":{
"status":1,
"num_results":1,
"total_results":500,
"results_remaining":499,
"matches":[{
"match_id":3188095188,
"match_seq_num":2784956606,
"start_time":1495079320,
"lobby_type":7,
"radiant_team_id":0,
"dire_team_id":0,
"players":[{
"account_id":86920222,
"player_slot":0,
"hero_id":18
},{
"account_id":61122568,
"player_slot":1,
"hero_id":85
},{
"account_id":10208661,
"player_slot":2,
"hero_id":13
},{
"account_id":106083675,
"player_slot":132,
"hero_id":50
}]
}]
}
}
My POJOs:
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Result {
#JsonIgnore
#Id
#GeneratedValue
private int id;
#JsonProperty("status")
private int status;
#JsonProperty("num_results")
private int num_results;
#JsonProperty("total_results")
private int total_results;
#JsonProperty("results_remaining")
private int results_remaining;
#OneToMany
#JoinColumn(name = "result_id")
#ElementCollection(targetClass=Matches.class)
#JsonProperty("matches")
private List<Matches> matches;
// getters and setters
}
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Matches {
#Id
#JsonProperty("match_id")
private int match_id;
#JsonIgnore
#ManyToOne
private Result result;
#JsonProperty("match_seq_num")
private int match_seq_num;
#JsonProperty("start_time")
private int start_time;
#JsonProperty("lobby_type")
private int lobby_type;
#JsonProperty("radiant_team_id")
private int radiant_team_id;
#JsonProperty("dire_team_id")
private int dire_team_id;
#OneToMany
#JoinColumn(name = "Matches_id")
#ElementCollection(targetClass=Players.class)
#JsonProperty("players")
private List<Players> players;
// getters and setters
}
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Players {
#JsonIgnore
#Id
#GeneratedValue
private int id;
#JsonIgnore
#ManyToOne
private Matches matches;
#JsonProperty("account_id")
private int account_id;
#JsonProperty("player_slot")
private int player_slot;
#JsonProperty("hero_id")
private int hero_id;
// getters and setters
}
Services method to read and convert the JSON to objects (url is censored, don't want my API key to be public)
public class SteamService {
public static Result getMatchHistory(String steamid){
Result result = new Result();
String MatchHistoryUrl = "https:**URL**="+steamid;
RestTemplate restTemplate = new RestTemplate();
Result jsonresult = restTemplate.getForObject(MatchHistoryUrl, Result.class);
return jsonresult;
}
}
Controller
#Controller
#RequestMapping("")
public class HomeController {
#Autowired
private ResultsDao resultsDao;
#RequestMapping(value = "", method = RequestMethod.GET)
public String index(Model model){
model.addAttribute("title", "Welcome");
return "home/home";
}
#RequestMapping(value = "", method = RequestMethod.POST)
public String processSteamIdField(#RequestParam("steamid")String steamid, Model model) {
Result newresult = getMatchHistory(steamid);
resultsDao.save(newresult);
return "redirect:results";
}
}
DAO
#Repository
#Transactional
public interface ResultsDao extends CrudRepository<Result, Integer>{
}
Maybe my approach is a bit naive, but... If you want to store the JSON as string in the database, then I would use an object mapper for this:
new ObjectMapper().writeValueAsString(myObject);
and for reading a JSON and parsing it to a class I would do:
new ObjectMapper().readValue(JSON_STRING_HERE, "utf-8"), MyPOJO.class);
Also, if you already are using Spring, then your controller may look like this (for a POST, for example)
#RequestMapping(value = "/", method = RequestMethod.POST)
public MyPojo myController(#RequestBody MyPojo myBody) {
myRepository.save(myBody);
}
So, the parsing of the JSON that the client is sending to your app and your controller is already handled by Spring