Multiple list of Data comming in API when using ManyToOne Relationship - spring

CarMake.java
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
public class CarMake {
#SequenceGenerator(
name = "car_make_sequence",
sequenceName = "car_make_sequence",
allocationSize = 1
)
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "car_make_sequence"
)
private Long id;
private String name;
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
#OneToMany(mappedBy = "make", cascade = CascadeType.ALL)
private List<CarModel> model;
}
CarModel.java
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
public class CarModel {
#SequenceGenerator(
name = "car_model_sequence",
sequenceName = "car_model_sequence",
allocationSize = 1
)
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "car_model_sequence"
)
private Long id;
private String name;
private Integer beginDate;
private Integer endDate;
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
#ManyToOne(fetch = FetchType.EAGER)
CarMake make;
#OneToMany(mappedBy = "model", cascade = CascadeType.ALL)
private List<CarVariant> variants;
}
CarVariant.java
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
public class CarVariant {
#SequenceGenerator(
name = "car_variant_sequence",
sequenceName = "car_variant_sequence",
allocationSize = 1
)
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "car_variant_sequence"
)
private Long id;
#Column(nullable = false)
private String name;
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
#ManyToOne(fetch = FetchType.LAZY)
CarModel model;
}
If i am using repository i am getting loop of data which is not present even in database
#GetMapping("api/car/model/{id}")
#ResponseBody
public Iterable<CarMake> showCarModelsByMakeId(#PathVariable Long id){
return carMakeRepository.findAll();
}
Bellow is the response of API
[{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983","updatedAt":"2022-05-06T17:07:48.40903","model":[{"id":1,"name":"hello","beginDate":1,"endDate":3,"createdAt":"2022-05-06T17:07:59.440783","updatedAt":"2022-05-06T17:07:59.440826","make":{"id":1,"name":"hello","createdAt":"2022-05-06T17:07:48.408983",
I beleive i should get only one instance of data as only one row is present in table. what is the reason i am getting so much data, did i created onetomany relationship correctly?, how i can achieve it
Database data

This is a famous bidirectional issue. In few words, your CarMake entity references CarModel, which in its turn, references back to CarMake, what causes an infinitive loop.
Try to put #JsonIgnore annotation on CarMake make in your CarModel entity and repeat the test.
PS: better to not to return entity objects from your rest controller, use either DTO mapping, or projections
PPS: don't use #EqualsAndHashCode on #Entity classes

Related

#ManyToMany SpringBoot JSON 415 error can`t post to table or can`t get list(n>=1) because of infinite loop caused by relationship

I`ve been trying to create a #ManyToMany relationship between two entities (team&contest) but when i try to post to the contest controller api i get a error 415 saying that
Failed to evaluate Jackson deserialization for type [[simple type, class com.project.Contest.Contest]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (`java.util.List<com.project.Contest.Contest>`) not compatible with managed type (com.project.Contest.Contest)
team :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "team")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#JsonBackReference
//#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
#ManyToMany(mappedBy = "teams", cascade = CascadeType.PERSIST)
private List<Contest> contests;
private String name;
private int wins, losses;
}
contest :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "contest")
public class Contest {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany(cascade = CascadeType.PERSIST)
#JsonManagedReference
//#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
#JoinTable(
name = "team_contest",
inverseJoinColumns = #JoinColumn(name = "team_id"),
joinColumns = #JoinColumn( name = "contest_id")
)
private List<Team> teams;
#ManyToMany(cascade = CascadeType.PERSIST)
#JsonManagedReference
//#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
#JoinTable(
name = "contest_user",
joinColumns = #JoinColumn(name = "contest_id"),
inverseJoinColumns = #JoinColumn( name = "user_id")
)
private List<User> users;
}
i found out i can use #JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id") instead of #JsonBackReference & #JsonManagedReference which helped me as it let me post to the database but then i refaced the problem that i can`t retrieve contest.teams[1] as because both objects have references to one another it creates some kind of an infinite loop as to get to the reference of the second object(contest.teams[1]) it needs to show the reference the contest.teams[0] has to the contest and soo forth. please help <3
This is the most famous bi-directional issue. To break up the loop while serialization, you can choose:
#JsonIgnore
#JsonIdentityInfo
JPA Projections
#EntityGraph
Or simply make the relationship uni-directional
thanks meridbt i was already using #JsonIdentityInfo but in the wrong place and i read a bit online and fixed my issue by doing this :
team :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "team")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class , property = "id")
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany(mappedBy = "teams", cascade = CascadeType.PERSIST)
private List<Contest> contests;
private String name;
private int wins, losses;
}
contest :
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "contest")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Contest {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "team_contest",
inverseJoinColumns = #JoinColumn(name = "team_id"),
joinColumns = #JoinColumn(name = "contest_id")
)
private List<Team> teams;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "contest_user",
joinColumns = #JoinColumn(name = "contest_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> users;
}

How to implements entity with 2 entity as primary key with jpa annotation and repository

i want to implement a many to many association with quantity information in it . like this :
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Banque banque;
private int quantity;
}
the GroupSanguin and the Banque are two class stored in the database two . here is the code for the two if you need :
#Entity
#Table(name = "groupe_sanguins")
public class GroupeSanguin {
#Id
private String groupe;
#OneToMany(mappedBy = "groupeSanguin")
private List<Donneur> donneurs;
}
#Entity #Getter #Setter #NoArgsConstructor
public class Banque {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true,nullable = false)
private String nom;
private String adresse;
#Column(unique = true)
private String telephone;
private String localisation;
}
so my i want to know how to annotate the JpaRepository to take the two as primary key like this and is my annotation good for it to work ?
public interface ReserveRepository extends JpaRepository<
Reserve,
//what to put here ?
>
This isn't a JPA question in fact, it's a relationnal database conception.
If Reserve has is own data and links with other entity it has it own Id
You can add unicity constraint
#Entity
#Table(name = "reserves", uniqueConstraints={
#UniqueConstraint(columnNames = {"banque_id", "groupe_id"})
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
i've found this solutions too.
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
#IdClass(ReserveId.class) //this annotation will tell that id that the
// the id will be represented by a class
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin groupeSanguin;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
and the id class should implements Serializable like this :
#Getter #Setter
public class ReserveId implements Serializable {
private Banque banque;
private GroupeSanguin groupeSanguin;
}
and finally the repository will be like that :
#Repository
public interface ReserveRepo extends JpaRepository<Reserve, ReserveId>{}
See your Reserve class has nowhere mentioned composite primary key. First you need to fix the model, You can refer to the solution here How to create and handle composite primary key in JPA

(Do not display relationship values)

I have two entity with name of the article and article Category.
and they have one-to-many relationships.
I use #JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,property = "id")
but I cant see data of article category(category_id) in spring data rest.
ArticleCategory.class
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory", fetch = FetchType.LAZY)
private Set<Article> articles = new HashSet<>();
}
Article.class
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable {
public Article() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
#Column(name = "title")
private String title;
#Column(name = "image_url")
private String image_url;
#Column(name = "short_description")
private String short_description;
#Column(name = "text")
private String text;
#Column(name = "keywords", nullable = true)
private String keywords;
#Column(name = "visit", nullable = false)
private int visit;
#Column(name = "code", nullable = false)
private UUID code;
#Column(name = "date_created")
#CreationTimestamp
private Date dateCreated;
#Column(name = "date_updated", nullable = false)
#UpdateTimestamp
private Date dateUpdated;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public Article(String title, String image_url, String short_description, String text, String keywords, int visit, UUID code) {
this.title = title;
this.image_url = image_url;
this.short_description = short_description;
this.text = text;
this.keywords = keywords;
this.visit = visit;
this.code = code;
}
}
Article Repository
#CrossOrigin("http://localhost:4200")
#RepositoryRestResource(collectionResourceRel = "article", path = "article")
public interface ArticleRepository extends JpaRepository<Article,Long> {
Article findByCode(UUID uuid);
}
And this is output of spring data rest
enter image description here
That is exactly because you used #JsonManagedReference and #JsonBackReference. Keep in mind the following when using them:
#JsonManagedReference is the forward part of the relationship and is the one that gets serialized normally.
#JsonBackReference is the back part of the relationship and it will be omitted from serialization.
The serialized Article object does not contain a reference to the ArticleCategory object.
If you want to have any ArticleCategory data when serializing Article you can either use #JsonIdentityInfo so that one of the properties is serialized (in this case I've chosen id for both):
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
}
#Entity
#Table(name = "article_category")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If you are only interested in categoryId another possibility would be to use #JsonIgnore on private Set<Article> articles property so that it is not serialized:
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If none of those suits your needs you might need to implement your own custom serializer. You can read more about all those options at https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.
I solved the problem using the controller
And that's why #JsonManageRefrence and #JsonBackRefrence do not work
I replaced the lazy load with the eager load in both entity
#ManyToOne(fetch = FetchType.Eager)
#JoinColumn(name = "user_id")
#JsonManageRefrence
private User user;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory",
fetch = FetchType.Eager)
#JsonBackRefrence
private Set<Article> articles = new HashSet<>();
and then add a controller
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#RequestMapping("/getAllArticle")
public class MyController {
private ArticleRepository articleRepository;
// you must do constructor injection
#GetMapping("/getAllArticle")
public List<Article> allArticle()
{
return articleRepository.findAll();
}
}

Infinite JSON in ManyToMany relationship mapped by Intermediary Table

I have 2 entities that relate to one another. These 2 entities should map to each other in a Many-To-Many relationship, however, I need to also have a timestamp of their respective relationship (when it happened), so I am trying to map them using an intermediary table.
Initially, the relationship was One-To-Many, but I realized that I actually need a Many-To-Many as the business logic requires this. The structure is still the same, as in there is a Parent-Child relationship, but this time, a child should have multiple parents as well.
My BaseEntity is an abstract class that contains the fields present in all the other entities:
#Data
#MappedSuperclass
public abstract class BaseEntity {
#Id
#Min(100)
#Max(Integer.MAX_VALUE)
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#CreationTimestamp
#Column(name = "Created_At", updatable = false)
protected ZonedDateTime createdDate;
#UpdateTimestamp
#Column(name = "Updated_At")
protected ZonedDateTime updatedDate;
#NotNull
#Column(name = "Is_Active")
protected Boolean active = true;
}
Then I have my 2 entities that should relate in a Many-To-Many style. This is my first entity and should be the parent:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "User")
#EqualsAndHashCode(callSuper = true)
#TypeDefs( {
#TypeDef(name = "json", typeClass = JsonStringType.class),
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class UserEntity extends BaseEntity {
#NotBlank
#Column(name = "User_Name", columnDefinition = "varchar(255) default 'N/A'")
private String userName;
#Nullable
#JoinColumn(name = "User_Id")
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<UserRole> roleList = new ArrayList<>();
}
My second entity is considered the child entity:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "Role")
#Where(clause = "is_active = true")
#EqualsAndHashCode(callSuper = true)
public class RoleEntity extends BaseEntity {
#NotBlank
#Column(name = "Name")
private String name;
#JsonIgnore
#JoinColumn(name = "Role_Id")
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<UserRole> userList = new ArrayList<>();
}
I also have my intermediary entity:
#Data
#Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Where(clause = "is_active = true")
#EqualsAndHashCode(callSuper = true)
#Table(name = "User_Role", uniqueConstraints= #UniqueConstraint(columnNames={"User_Id", "Role_Id"}))
public class UserRole extends BaseEntity {
// Adding #JsonIgnore here will only cause an error
#JoinColumn(name = "User_Id")
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, targetEntity = UserEntity.class)
private UserEntity user;
#JoinColumn(name = "Role_Id")
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, targetEntity = RoleEntity.class)
private RoleEntity role;
}
Problem now is that when I try to get my UserEntity, I get infinite recursion.
So far I've tried using #JsonIgnore, #JsonManagedReference, #JsonBackReference and it did not work or I simply don't know where or how to use them properly.
Recap:
2 entities mapped by Many-To-Many relationship;
Many-To-Many implemented using an intermediary entity and One-To-Many + Many-To-One associations;
Getting recursion when showing my UserEntity;
Update: I managed to get this fixed using a different approach described in my answer to this question.
I fixed this by implementing a Composite Key structure and just using the #JsonIgnore annotation:
#Getter
#Setter
#Embeddable
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
public class UserRoleKey implements Serializable {
#Column(name = "User_Id")
Long userId;
#Column(name = "Role_Id")
Long roleId;
}
This gets to be used in the intermediary entity, which now doesn't use my BaseEntity anymore.
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "User_Role", uniqueConstraints= #UniqueConstraint(columnNames={"User_Id", "Role_Id"}))
public class UserRole {
#JsonIgnore
#EmbeddedId
private UserRoleKey id;
#JsonIgnore
#MapsId("userId")
#JoinColumn(name = "User_Id")
#ManyToOne(optional = false, targetEntity = UserEntity.class)
private UserEntity user;
#MapsId("roleId")
#JoinColumn(name = "Role_Id")
#ManyToOne(optional = false, targetEntity = RoleEntity.class)
private RoleEntity role;
#CreationTimestamp
#Column(name = "Created_At", updatable = false)
private ZonedDateTime createdDate;
}
Now, for my two entities, I have this definition:
UserEntity class (definition of the role):
#Nullable
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = true)
private List<UserRole> roleList = new ArrayList<>();
RoleEntity class (definition of the user)
#Nullable
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "role", orphanRemoval = true)
private List<UserRole> userList = new ArrayList<>();
This seems to be working and no longer returns an infinite JSON recursion.

Spring Framework + Spring Data + Hibernate Jpa OneToMany child removal fails

I have an unidirectional OneToMany JPA entity mapping in my (Spring Framework + Spring Data + Hibernate JPA) project. Entity classes are like in the following code.(I have removed irrelevant class members for brevity).
#Entity
#Table(name = "employees")
class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "employee_id")
private List<DepartmentAssignment> departmentAssignments = new ArrayList<>();
}
#Entity
#Table(name = "department_assignments")
class DepartmentAssignment{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#NotNull
#Column(name = "employee_id")
private Integer employeeId;
#NotNull
#Column(name = "department_id")
private Integer departmentId;
}
#Entity
#Table(name = "departments")
class Department{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
}
And, in one of my service classes have a method to remove a DepartmentAssignment from an Employee like below.
public Employee deleteDepartmentAssignment(Integer empId, Integer deptAssignmentId) {
Employee employee = employeeRepository.findOne(empId);
if(employee != null) {
for ( DepartmentAssignment da : employee.getDepartmentAssignments()) {
if(da.getId().equals(deptAssignmentId)) {
employee.getDepartmentAssignments().remove(da);
employee = employeeRepository.save(employee);
break;
}
}
}
return employee;
}
However, calling above methods gives me an error: org.hibernate.exception.ConstraintViolationException ,and in the SQL log, I can see Column 'employee_id' cannot be null error for the last SQL statement of the transaction.
Can anybody tell me what I'm doing wrong here and how to get it fixed?
You don't need to add
#NotNull
#Column(name = "employee_id")
private Integer employeeId;
to the Employee, if you use #JoinColumn(name = "employee_id"). Try to remove it.
You can try the following, not sure why you use the plain id in the object. Thats not object relational mapping.
For more details see Hibernate triggering constraint violations using orphanRemoval
#Entity
#Table(name = "employees")
class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "employee", orphanRemoval = true)
private List<DepartmentAssignment> departmentAssignments = new ArrayList<>();
}
#Entity
#Table(name = "department_assignments")
class DepartmentAssignment{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(optional=false)
private Employee employee;
#ManyToOne(optional=false)
private Department department;
}
#Entity
#Table(name = "departments")
class Department{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
You must look .hbm.xml file and you should mapping your Entity in this file and
you can look this example
http://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example/
I hope it will be useful for you.
try removing
cascade = CascadeType.ALL
but im not 100% sure..

Resources