I have a Spring application with 2 Entities that have a Many-to-Many relationship. There are Students and Groups. A student can be part of many groups and a group can have many students.
Student Model
#Entity
#Table(name = "STUDENTS")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Student extends AbstractUser {
//Fields
#ManyToMany(fetch = FetchType.LAZY, targetEntity = Group.class)
#JoinTable(name = "GROUPS_STUDENTS",
joinColumns = { #JoinColumn(name = "student_id") },
inverseJoinColumns = { #JoinColumn(name = "group_id") })
private List<Group> groups = new ArrayList<Group>();
//Constructors
public Student(String password, String firstName, String lastName, String email){
super(password, firstName, lastName, email);
}
public Student(){
}
//Setters and getters
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
}
Group Model
#Entity
#Table(name = "GROUPS")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Group implements Item, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
#Column(name = "year", nullable = false, length = 1)
private int year;
#ManyToMany(mappedBy = "groups", targetEntity = Student.class)
private List<Student> students;
public Group(String name, int yearOfStudy) {
this.setName(name);
this.setYear(yearOfStudy);
}
...
}
The problem is when I make a request to show all students, if 2 students are in the same group they appear in a hierarchy rather than one after another. What I mean the JSON is going too deep. I don't know how to explain exactly but I'll put an example.
How it appears
[
{
"id": 2,
"password": "$2a$10$bxieGA7kWuEYUUMbYNiYo.SbGpo7X5oh8ulUsqCcrIR2cFN2HiQP2",
"firstName": "First",
"lastName": "Last",
"email": "name#mail.com",
"groups": [
{
"id": 1,
"name": "G1",
"year": 0,
"users": [
2,
{
"id": 1,
"password": "$2a$10$9pfTdci7PeHtzxuAxjcOsOjPSswrU35/JOOeWPpgVhJI4tD2YpZdG",
"firstName": "John",
"lastName": "Smith",
"email": "john.smith#mail.com",
"groups": [
1
]
}
]
}
]
},
1
]
How it should appear
{
"id": 2,
"password": "$2a$10$bxieGA7kWuEYUUMbYNiYo.SbGpo7X5oh8ulUsqCcrIR2cFN2HiQP2",
"firstName": "First",
"lastName": "Last",
"email": "name#mail.com",
"groups": [
{
"id": 1,
"name": "G1",
"year": 0,
}
]
},
{
"id": 1,
"password": "$2a$10$9pfTdci7PeHtzxuAxjcOsOjPSswrU35/JOOeWPpgVhJI4tD2YpZdG",
"firstName": "John",
"lastName": "Smith",
"email": "john.smith#mail.com",
"groups": [
{
"id": 1,
"name": "G1",
"year": 0
}]
}
]
Do you have any idea how to solve this? I don't know exactly how to describe my problem and that's why I didn't find a solution yet. Any help will much appreciated. Thank you.
I've solved it. I've made a custom serializer. So in Group I'll serialize the students by setting a custom annotation #JsonSerialize(using = CustomStudentSerializer.class)
CustomStudentSerializer
public class CustomStudentSerializer extends StdSerializer<List<Student>> {
public CustomStudentSerializer() {
this(null);
}
public CustomStudentSerializer(Class<List<Student>> t) {
super(t);
}
#Override
public void serialize(
List<Student> students,
JsonGenerator generator,
SerializerProvider provider)
throws IOException, JsonProcessingException {
List<Student> studs = new ArrayList<>();
for (Student s : students) {
s.setGroups(null);
studs.add(s);
}
generator.writeObject(studs);
}
}
Did the same for the groups. I've just removed the students/group component when the relationship is already nested. And now it works just fine.
Took me a while to figure this out but I posted here because it may help someone else too.
Using the #JsonIgnoreProperties annotation is another alternative:
#Entity
public class Student extends AbstractUser {
#ManyToMany(fetch = FetchType.LAZY, targetEntity = Group.class)
#JoinTable(name = "GROUPS_STUDENTS",
joinColumns = { #JoinColumn(name = "student_id") },
inverseJoinColumns = { #JoinColumn(name = "group_id") })
#JsonIgnoreProperties("students")
private List<Group> groups = new ArrayList<Group>();
}
#Entity
public class Group implements Item, Serializable {
#ManyToMany(mappedBy = "groups", targetEntity = Student.class)
#JsonIgnoreProperties("groups")
private List<Student> students;
}
Find comparison between #JsonManagedReference+#JsonBackReference, #JsonIdentityInfo and #JsonIgnoreProperties here: http://springquay.blogspot.com/2016/01/new-approach-to-solve-json-recursive.html
To solve jackson infinite recursion you can use #JsonManagedReference, #JsonBackReference.
#JsonManagedReference is the forward part of reference – the one that
gets serialized normally.
#JsonBackReference is the back part of
reference – it will be omitted from serialization.
Find more details here: http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
public class Student extends AbstractUser {
#ManyToMany(fetch = FetchType.LAZY, targetEntity = Group.class)
#JoinTable(name = "GROUPS_STUDENTS",
joinColumns = { #JoinColumn(name = "student_id") },
inverseJoinColumns = { #JoinColumn(name = "group_id") })
#JsonManagedReference
private List<Group> groups = new ArrayList<Group>();
}
public class Group implements Item, Serializable {
#ManyToMany(mappedBy = "groups", targetEntity = Student.class)
#JsonBackReference
private List<Student> students;
}
Or you can use DTO (Data Transfer Object) classes. These are plain code classes wich you can set to your needs when sendind data. For example, for your case you can have:
UserDTO.java:
import java.util.List;
public class UserDTO {
private int id;
private String password;
private String firstName;
private String lastName;
private String email;
private List<GroupDTO> groups;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<GroupDTO> getGroups() {
return groups;
}
public void setGroups(List<GroupDTO> groups) {
this.groups = groups;
}
}
GroupDTO.java
package temp;
public class GroupDTO {
private int id;
private String name;
private int year;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
}
This way you can customize the information you will send in the json file.
Let's say you have a User class and you want to send all users information:
List<StudentDTO> response = new ArrayList<StudentDTO>(); //List to be send
List<Student> students = bussinessDelegate.findAllStudents(); //Or whatever you do to get all students
for (int i = 0; i < students.size(); i++) {
Student student = students.get(i);
StudentDTO studentDTO = new StudentDTO(); //Empty constructor
studentDTO.setEmail(student.getEmail());
studentDTO.setFirstName(student.getFirstName());
studentDTO.setLastName(student.getLastName());
studentDTO.setId(student.getId());
studentDTO.setPassword(student.getPassword());
List<Group> studentGroups = student.getGroups();
List<GroupDTO> studentGroupsDTO = new ArrayList<GroupDTO>();
for (int j = 0; j < studentGroups.size(); j++) {
Group group = studentGroups.get(j);
GroupDTO groupDTO = new GroupDTO();
groupDTO.setId(group.getId());
groupDTO.setName(group.getName());
groupDTO.setYear(group.getYear());
studentGroupsDTO.add(groupDTO);
}
studentDTO.setGroups(studentGroupsDTO);
response.add(studentDTO);
}
//Here you have your response list of students ready to send`
If you want to be able to dynamically define what has to be serialized you could try my jackson addon which i developed based on the question here:
https://stackoverflow.com/a/28245875/869225
This also helped me with backreferences.
Related
I have been trying to load the menu from a database using Spring JPA and Spring Boot with a MySQL database.
I have to load only active menu from database. I have tried with JPQL, i.e. findByActiveTrue() method of JPA, but I get all records.
#Entity
#Table(name = "menu_tbl")
//#Cacheable
//#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Menu implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "menu_description")
private String menuName;
#Column(name = "menu_link", nullable = false)
private String link;
#Column(name = "menu_css")
private String css;
#Column(name = "menu_ico")
private String icon;
#Column(name = "active")
#
private Boolean active;
#JsonManagedReference
#OneToMany(mappedBy = "parent")
#OrderBy("id ASC")
private Set<Menu> children;
#ManyToOne
#JsonBackReference
private Menu parent;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public String getCss() {
return css;
}
public void setCss(String css) {
this.css = css;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Set<Menu> getChildren() {
return children;
}
public void setChildren(Set<Menu> children) {
this.children = children;
}
public Menu getParent() {
return parent;
}
public void setParent(Menu parent) {
this.parent = parent;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
}
Repository
public interface MenuRepository extends JpaRepository<Menu, Long> {
List<Menu> findByActiveTrue();
}
And getting output
[
{
"id": 1,
"menuName": "Customer",
"link": "/customer",
"css": null,
"icon": null,
"active": true,
"children": [
{
"id": 2,
"menuName": "Add Customer",
"link": "/addCustomer",
"css": null,
"icon": null,
"active": false,
"children": []
},
{
"id": 3,
"menuName": "View Customer",
"link": "/viewCustomer",
"css": null,
"icon": null,
"active": false,
"children": []
}
]
}
]
I would do like this:
#Entity
#Table(name = "menu_tbl")
public class Menu {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "menu_description")
private String menuName;
#Column(name = "menu_link", nullable = false)
private String link;
#Column(name = "menu_css")
private String css;
#Column(name = "menu_ico")
private String icon;
#Column(name = "active")
private Boolean active;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "main_id",
nullable = true)
private Menu mainMenu;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainMenu", cascade = CascadeType.REMOVE)
private Set<Menu> children = new HashSet<>();
}
And
List<Menu> findAllByMainMenuIdAndActiveIsTrue(Long mainMenuid);
Enable spring-jpa-show-sql=true to watch which queries are being executed against your database from your IDE console. That will give you more insights on how you'll write your method.
Actually I want to use tree Json response for my react native application. So I build that in spring boot. But due to "children": [] in last child I face some issue in react native. So I want to hide that from my response.
1.I got this type of response
[{
"id": 1,
"name": "Martin",
"haveAccount": false,
"gender": "m",
"children": [
{
"id": 4,
"name": "Werite",
"haveAccount": false,
"gender": "f",
"children": []
}
]
}]
2.But I don't want "children": [] in last child
example:-
[{
"id": 1,
"name": "Martin",
"haveAccount": false,
"gender": "m",
"children": [
{
"id": 4,
"name": "Werite",
"haveAccount": false,
"gender": "f"
}
]
}]
3.Here is my Entity class
#Service
#Entity
#Table
public class Family {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY )
#Column(name="Family_id")
private Integer id;
private String name;
private String village;
private String wifeName;
private String talukaName;
private Boolean haveAccount;
private String username;
private String gender;
private String mobileNumber;
#OneToOne
#JoinColumn(name = "parent_id")
#JsonBackReference
private Family parent;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
#JsonManagedReference
private List<Family> children = new ArrayList<>();
public Family(Integer id, String name, String village, String wifeName, String talukaName, Boolean haveAccount,
String username, String gender, String mobileNumber, Family parent, List<Family> children) {
super();
this.id = id;
this.name = name;
this.village = village;
this.wifeName = wifeName;
this.talukaName = talukaName;
this.haveAccount = haveAccount;
this.username = username;
this.gender = gender;
this.mobileNumber = mobileNumber;
this.parent = parent;
this.children = children;
}
public Family() {
super();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Family getParent() {
return parent;
}
public void setParent(Family parent) {
this.parent = parent;
}
public List<Family> getChildren() {
return children;
}
public void setChildren(List<Family> children) {
this.children = children;
}
public void addChild(Family children) {
this.children.add(children);
}
public String getVillage() {
return village;
}
public void setVillage(String village) {
this.village = village;
}
public String getWifeName() {
return wifeName;
}
public void setWifeName(String wifeName) {
this.wifeName = wifeName;
}
public String getTalukaName() {
return talukaName;
}
public void setTalukaName(String talukaName) {
this.talukaName = talukaName;
}
public Boolean getHaveAccount() {
return haveAccount;
}
public void setHaveAccount(Boolean haveAccount) {
this.haveAccount = haveAccount;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getMobileNumber() {
return mobileNumber;
}
public void setMobileNumber(String mobileNumber) {
this.mobileNumber = mobileNumber;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
If you want to delete a field you can put it a null and use #JsonInclude(Include.NON_NULL) on your class as follow:
#Service
#Entity
#Table
#JsonInclude(Include.NON_NULL)
public class Family {
//class
}
If you can handle that you should substitute private List<Family> children = new ArrayList<>(); with private List<Family> children; and initialize it when you are going to use it.
Just a question: Why are you using #Service on your entity class?
Course.java
package com.example.jpa_training.JPAD.model;
#Entity
#Table(name = "COURSE")
public class Course implements Serializable{
public Course() {}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String description;
#ManyToOne(targetEntity=Department.class)
#JsonIgnore
private Department department;
#ManyToMany(mappedBy="courses", targetEntity=Student.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
private Set<Student> students = new HashSet<Student>();
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name="professor_id")
#JsonManagedReference
private Professor professor;
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 Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
public Set<Student> getStudents() {
return students;
}
public void addStudent(Student student) {
this.students.add(student);
}
public void removeStudent(Student student) {
this.students.remove(student);
}
#OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
private Set<Review> reviews;
}
Review.java
#Entity
public class Review implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long reviewId;
#ManyToOne
private Course course;
private String reviewDescription;
private double courseRating;
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
public String getReviewDescription() {
return reviewDescription;
}
public void setReviewDescription(String reviewDescription) {
this.reviewDescription = reviewDescription;
}
public double getCourseRating() {
return courseRating;
}
public void setCourseRating(double courseRating) {
this.courseRating = courseRating;
}
}
Postman Input
{
"course": {
"id": 4,
"name": "Data Analysis",
"description": "Just take it",
"professor": {
"name": "Kapil Dev",
"qualification": "M.Tech",
"department": {
"deptId": 1,
"deptName": "Big Data",
"buildingName": "DS-04"
}
}
},
"reviewDescription": "Good course, nice teaching",
"courseRating": 0.0
}
Error Log
Failed to evaluate Jackson deserialization for type [[simple type, class com.example.jpa_training.JPAD.model.Review]]: com.fasterxml.jackson.databind.JsonMappingException:
2020-12-30 11:45:00.869 WARN 11152 --- [nio-8080-exec-2] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.example.jpa_training.JPAD.model.Review]]: com.fasterxml.jackson.databind.JsonMappingException:
2020-12-30 11:45:00.869 WARN 11152 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type
'application/json;charset=UTF-8' not supported]
Tried solutions
Using #JsonBackReference and #JsonManagedReference
Using #JsonIdentityInfo and #JsonIgnore
but the error is the same
I can save and retrieve the data from Java but when I send data over postman or using curl command I get the above error, I tried many ways but couldn't fix it
I wouldn't suggest exposing entities directly to your controller. Entities should only contain JPA annotations in your case. You can expose a DTO (Data Transfer Object) to your controller and then map the DTO to the corresponding entity.
ReviewDto
public class ReviewDto {
private String reviewDescription;
private double courseRating;
private CourseDto course;
// getters, setters, etc
}
CourseDto
public class CourseDto {
private Long id;
private String name;
private String description;
// professorDto, getters, setters, etc
}
An example demonstrating how your controller class will be
#RestController
public class DemoController {
private final ReviewDtoMapper reviewDtoMapper;
private final ReviewService reviewService;
public DemoController(ReviewDtoMapper reviewDtoMapper,
ReviewService reviewService) {
this.reviewDtoMapper = reviewDtoMapper;
this.reviewService = reviewService;
}
#PostMapping(value = "demo")
public ResponseEntity<String> postReview(#RequestBody ReviewDto reviewDto) {
final Review review = reviewDtoMapper.mapFrom(reviewDto);
reviewService.save(review);
return ResponseEntity.ok("");
}
}
The class to map from reviewDto to review entity and the opposite.
#Component
public class ReviewDtoMapper {
public ReviewDto mapTo(final Review entity) {
ReviewDto reviewDto = new ReviewDto();
reviewDto.setReviewDescription(entity.getReviewDescription());
// set all the properties you want
return reviewDto;
}
public Review mapFrom(ReviewDto dto) {
Review review = new Review();
review.setReviewDescription(dto.getReviewDescription());
// set all the properties you want
return review;
}
}
Of course, you have to make adjustments according to your needs.
If you like this way of doing things I would suggest you check
MapStruct, it will automatically make the mappers for you.
I create application in Spring, which stores albums, musicians and bands. Album can contain multiple bands and musicians. I created association between Album and Band/Musician. Jet I am unable to delete it. I don't want to delete objects, just the association. I tried to send REST PUT request and setting musicians and bands to null on Album site, yet nothing happens:
{
"id": 2,
"title": "Lulu",
"bands": null,
"musicians": null,
"duration": {
"hours": 1,
"minutes": 20,
"seconds": 4
},
"releaseDate": "31/10/2011",
"coverPath": "https://upload.wikimedia.org/wikipedia/en/4/40/Lou_Reed_and_Metallica_-_Lulu.jpg",
"spotifyPath": null
}
I have created following class and method to link Album and Musician, yet I am unable to "unlink" them:
#RestController
public class AlbumMusicianController {
#Autowired
AlbumRepository albumRepository;
#Autowired
MusicianRepository musicianRepository;
#Transactional
#PostMapping("/musician/{musicianId}/album/{albumId}")
public List<Album> associate(#PathVariable Long musicianId, #PathVariable Long albumId) {
Album album = this.albumRepository.findById(albumId).orElseThrow(() -> new MissingResourceException("Album",
"Album", albumId.toString()));
return this.musicianRepository.findById(musicianId).map((musician) -> { musician.getAlbums().add(album);
return this.musicianRepository.save(musician).getAlbums();
}).orElseThrow(() -> new MissingResourceException("Musician", "Musician", musicianId.toString()));
}
}
Would be thankful for any help.
Below are necessary sources.
Album class:
#Entity
#Table(name="album")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Album {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="title")
private String title;
#ManyToMany(targetEntity = Band.class, mappedBy = "albums")
#JsonSerialize(using = BandsSerializer.class)
private List<Band> bands;
#ManyToMany(targetEntity = Musician.class, mappedBy = "albums")
#JsonSerialize(using = MusiciansSerializer.class)
private List<Musician> musicians;
#Embedded
#Column(name="duration")
private Duration duration;
#Column(name="releasedate")
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd/MM/yyyy", timezone="CET")
private Date releaseDate;
#Column(name="coverpath")
private String coverPath;
#Column(name="spotifypath")
private String spotifyPath;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Duration getDuration() {
return duration;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
public Date getReleaseDate() {
return releaseDate;
}
public void setReleaseDate(Date releaseDate) {
this.releaseDate = releaseDate;
}
public String getCoverPath() {
return coverPath;
}
public void setCoverPath(String coverPath) {
this.coverPath = coverPath;
}
public String getSpotifyPath() {
return spotifyPath;
}
public void setSpotifyPath(String spotifyPath) {
this.spotifyPath = spotifyPath;
}
public List<Band> getBands() {
return bands;
}
public void setBands(List<Band> bands) {
this.bands = bands;
}
public List<Musician> getMusicians() {
return musicians;
}
public void setMusicians(List<Musician> musicians) {
this.musicians = musicians;
}
}
Musician class:
#Entity
#Table(name="musician")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Musician {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="name")
private String name;
#Column(name="surname")
private String surname;
#Column(name="birthdate")
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd/MM/yyyy", timezone="CET")
private Date birthDate;
#Column(name="picturepath")
private String picturePath;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "album_musician",
joinColumns = #JoinColumn(name = "album_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "musician_id",
referencedColumnName = "id"))
private List<Album> albums;
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 String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public String getPicturePath() {
return picturePath;
}
public void setPicturePath(String picturePath) {
this.picturePath = picturePath;
}
public List<Album> getAlbums() {
return albums;
}
public void setAlbums(List<Album> albums) {
this.albums = albums;
}
}
Band class:
#Entity
#Table(name="band")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Band {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="name")
private String name;
#Column(name="picturepath")
private String picturePath;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "album_band",
joinColumns = #JoinColumn(name = "album_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "band_id",
referencedColumnName = "id"))
#JsonSerialize(using = AlbumsSerializer.class)
private List<Album> albums;
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 String getPicturePath() {
return picturePath;
}
public void setPicturePath(String picturePath) {
this.picturePath = picturePath;
}
public List<Album> getAlbums() {
return albums;
}
public void setAlbums(List<Album> albums) {
this.albums = albums;
}
}
Based on your JSON body I'm going to assume you were sending a PUT request for the Album entity. There were two things that I found missing that got it to work for me after adjusting. I'm not sure if you were avoiding using them for one reason or another.
Cascade rules to cascade changes from Album to its relations.
Proper entity mapping for the join table from Album to its relations.
Not really sure why this was an issue - Hibernate did not seem to throw any exceptions related to this at execution time, but it did not seem to persist things correctly.
Here is an adjusted relation definition for Album's relation to Musician.
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="album_musician", joinColumns = #JoinColumn(name = "musician_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "album_id",
referencedColumnName = "id"))
private List<Musician> musicians;
In this format, I was able to cascade changes from Album to Musician. You will have to do something similar for the Band entity to cascade operations from Album to Band.
When I send a GET request in POSTMAN to get all my child entity (Town) the parent entity (Province) is not shown in the JSON response.
This is my controller.
#RequestMapping(value ="api/v1/town",method = RequestMethod.GET)
public ResponseEntity<List<Town>> getAllTowns() {
List<Town> towns = townService.getAllTowns();
if(towns.isEmpty()) {
return new ResponseEntity<List<Town>>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<List<Town>>(towns, HttpStatus.OK);
}
And these are my entities.
Parent Class
#Entity
#Table(name = "PROVINCE")
public class Province {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PROVINCE_ID")
private long id;
private String name;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "province", targetEntity = Town.class)
#JsonManagedReference("Province-Town")
private List<Town> towns;
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 List<Town> getTowns() {
return towns;
}
public void setTowns(List<Town> towns) {
this.towns = towns;
}
}
Child Class
#Entity
#Table(name = "TOWN")
public class Town {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "TOWN_ID")
private long id;
private String name;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "PROVINCE_ID")
#JsonBackReference("Province-Town")
private Province province;
private long kilometer;
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 Province getProvince() {
return province;
}
public void setProvince(Province province) {
this.province = province;
}
public long getKilometer() {
return kilometer;
}
public void setKilometer(long kilometer) {
this.kilometer = kilometer;
}
}
The response that I'm getting is like this
{
"id" : 1,
"name" : "Some Town",
"kilometer" : 350
}
What I'm expecting is
{
"id" : 1,
"name" : "Some Town",
"province" : {
//Province data.....
}
"kilometer" : 350
}
I was able to show something like this, but the Objects that I used are not Spring-data-jpa entities, just simple POJOs.
Is there any problem with my Entities? Or is there anything else?
Swap #JsonBackReference and #JsonManagedReference. Basically:
#JsonManagedReference
private Province province;
#JsonBackReference
private List<Town> towns;