Spring-boot JPA infinite loop many to many - java

I have two entities, which are in a many to many relationship.
#Entity
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(name = "teams_rooms",
joinColumns = #JoinColumn(name= "team_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
private Set<Room> rooms;
}
To yield data, i have a repository for "Room" and "Team":
public interface RoomRepository extends CrudRepository<Room, Long> {
}
public interface TeamRepository extends CrudRepository<Team, Long> {
}
My goal is to request all rooms of a team, but prevent JPA from looping infinitely.
#RestController
#RequestMapping("....")
public class RoomController {
#Autowired
private RoomRepository roomRepository;
#GetMapping
public Iterable<Room> getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final var team = teamRepository.findById(exampleId);
return ResponseEntity.ok(team);
}
}
This is the result:
{
"id": 1,
"name": "Team1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
{
"id": 1,
"name": "Team 1",
"rooms": [
{
"id": 1,
"name": "Room 1",
"teams": [
Jackson will loop forever, until an exception occurs (Since the back reference also references the parent element, which will create a loop).
I already tried #JsonManagedReference and #JsonBackReference, but they are used for many to one relationships.
How do i stop Jackson from looping infinitely? I want to affect other repositories and queries as little as possible.

Your controller shoud not return entities ( classes with the annotation #Entity). As a best practice is to create another separate class with same attributes. This code has a little dupplication but it keeps all the layers clean. I also suggest to use #Service.
public class RoomDTO {
private String name;
private List<TeamDTO> teams = new ArrayList<>();
public RoomDTO() {
}
public RoomDTO(Room room) {
this.name = room.name;
for(Team team : room.getTeams()) {
TeamDTO teamDTO = new TeamDTO();
teamDTO.setName(team.getName);
teams.add(teamDTO);
}
}
}
public class TeamDTO {
List<RoomDTO> rooms = new ArrayList();
public TeamDTO() {
}
public TeamDTO(Team team) {
this.name = team.name;
for(Room room : team.getRooms()) {
RoomDTO roomDTO = new RoomDTO();
roomDTO.setName(team.getName);
rooms.add(roomDTO);
}
}
}
The controller should return this
#GetMapping
public Iterable<TeamDTO> getAllRoomsOfTeam() {
final long exampleId = 1;
final var team = teamRepository.findById(exampleId);
TeamDTO teamDTO = new TeamDTO(team);
return ResponseEntity.ok(teamDTO);
}
How to use DTOs in the Controller, Service and Repository pattern

Currently, there is cyclic dependency in your classes which is causing issues while converting objects to JSON. Please add #JsonIgnore annotation on rooms variable in your Team class as shown in below example:
import com.fasterxml.jackson.annotation.JsonIgnore;
#Entity
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToMany
#JoinTable(name = "teams_rooms",
joinColumns = #JoinColumn(name= "team_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
#JsonIgnore
private Set<Room> rooms;
}
If you need a solution for bidirectional conversion then you can use JsonView annotation.
First of all you need to create JSON view profiles for Team and Room as shown in below example:
public class JsonViewProfiles
{
/**
* This profile will be used while converting Team object to JSON
*/
public static class Team {}
/**
* This profile will be used while converting Room object to JSON
*/
public static class Room {}
}
Use above created JSON view profiles in your entities as shown in below example:
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView({ JsonViewProfiles.Team.class, JsonViewProfiles.Room.class })
private long id;
#JsonView(JsonViewProfiles.Room.class)
#ManyToMany(mappedBy = "rooms")
private Set<Team> teams;
}
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView({JsonViewProfiles.Team.class, JsonViewProfiles.Room.class})
private long id;
#ManyToMany
#JoinTable(name = "teams_rooms",
joinColumns = #JoinColumn(name= "team_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
#JsonView(JsonViewProfiles.Team.class)
private Set<Room> rooms;
}
While converting your object to JSON please use these profiles as shown in below example:
#GetMapping
public String getAllRoomsOfTeam() {
final long exampleId = 1; //This is just a placeholder. The id will be passed as a parameter.
final Team team = teamRepository.findById(exampleId);
String result = new ObjectMapper().writerWithView(JsonViewProfiles.Team.class)
.writeValueAsString(team);
return result;
}

Related

how to select specific column which is an object in jpa spring boot

well I have a doubt, i have a class like this:
#Entity(name = "movie_details")
#Getter #Setter
public class MovieDetailEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "movie_id", nullable = false)
#JsonIgnoreProperties(value = {"movieDetails", "handler","hibernateLazyInitializer"}, allowSetters = true)
private MovieEntity movie;
#ManyToOne
#JoinColumn(name = "character_id", nullable = false)
#JsonIgnoreProperties(value = {"movies", "handler","hibernateLazyInitializer"}, allowSetters = true)
private CharacterEntity character;
}
And I just want to show the characters from a movie, but I don't want to show the id and the MovieEntity I just want to show CharacterEntity...
#Repository
public interface MovieDetailRepository extends JpaRepository<MovieDetailEntity, Long> {
Page<MovieDetailEntity> findByMovie(Pageable pageable, MovieEntity movie);
}
And this query return a json like this
"content": [
{
"id": ...,
"movie": {
...
},
"character": {
...
}
}
],
I was reading about making an interface dto with getters but didn't work for me
It sounds like you want to turn your entity into a DTO object that you can return. You'd need to create a new object like
public class MovieDetailDto {
private CharacterDto;
}
You would then probably want to implement a setter and getter method for CharacterDto and create the CharacterDto class similarly to the MovieDetailDto class with the fields from CharacterEntity you want to return with setters and getters for them.
When you get back a MovieDetailEntity from your repository you can then just create a new CharacterDto for the CharacterEntity and set it in a new MovieDetailDto.
I have could solve it, i create a interface projection, like this...
public interface MovieCharactersDto {
CharacterEntity getCharacter();
interface CharacterEntity {
Long getId();
String getName();
String getImage();
BigDecimal getWeight();
Integer getAge();
String getStory();
}
}
And the query was as follows:
#Repository
public interface MovieDetailRepository extends JpaRepository<MovieDetailEntity, Long> {
Page<MovieCharactersDto> findByMovie(Pageable pageable, MovieEntity movie);
}

Spring Data JPA Many to Many Service Repository Problem

I am trying to add ManyToMany entity to my application. I created entity but cannot implement it.
Actor class
#Entity
#Table(name = "actor")
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, name = "actor_name")
private String actorName;
#ManyToMany(mappedBy = "actor", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Movie> movie = new HashSet<Movie>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getActorName() { return actorName; }
public void setActorName(String actorName) {
this.actorName = actorName;
}
public Set<Movie> getMovie() {
return movie;
}
public void setMovie(Set<Movie> movie) {
this.movie = movie;
}
}
In movie class I have
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "movie_actor",
joinColumns = {#JoinColumn(name = "movie_id")},
inverseJoinColumns = {#JoinColumn(name = "actor_id")}
)
Set<Actor> actor = new HashSet<Actor>();
........................
public Set<Actor> getActor () {
return actor;
}
public void setActor(Set<Actor> actor){
this.actor = actor;
}
I created my entity just like this but in MovieService;
Actor actor = ActorRepository.findByActorName(movie.getActor().getActorName());
movie.setActor(actor);
This part gives me error. movie.getActor().getActorName() method cannot find. Where do I need to look? In IDE it also says method getActorName and setActorName is never used. I am also adding my ActorRepository and ActorService to closer look to the problem.
ActorRepository
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorName(String actorName);
}
ActorService
#Service
public class ActorService {
private ActorRepository actorRepository;
#Autowired
public ActorService(ActorRepository actorRepository) {
this.actorRepository = actorRepository;
}
public List<Actor> getAllActor() {
return actorRepository.findAll();
}
}
After adding ManyToMany I was using is as OneToMany entity. Services is works for OneToMany. How can I use them for ManyToMany? I need to add multiple actors to my movies. I couldn't find MVC projects for ManyToMany implementation.
You're invoking movie.getActor().getActorName() which basically does a getActorName() on a Set<Actor> object.
You're basically treating the relation as a ManyToOne instead of a OneToMany
You could use the following to fetch the first Actor of the Set
ActorRepository.findByActorName(movie.getActors().iterator().next().getActorName());
But then of course, you don't have all your Actor's names
What you could do is the following
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorNameIn(List<String> actorName);
}
And invoke it that way
ActorRepository.findByActorNameIn(
movie.getActors()
.stream()
.map(Actor::getName)
.collect(Collectors.toList())
);

One to Many Json Reponse With Jackson is not Working

I have a problem using JPA and RelationsShips One to Many with Jackson and Spring Rest ... I try to find multiples solutions but anything is working for me , and I don't kno where is the problem.
For example I have a table Team that has One to Many/Many To One relationship
I have two repository one for Team and another for Player
Team >>> has Many >> Player
Player >>> many to one >> Team
My entity Team has the following content
#Entity
#Table(name = "teams")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Team {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long teamId;
private String abbreviation;
private String team;
private String simpleName;
private String logo;
#OneToMany(cascade = {CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "team")
#Column(nullable = false)
private List<Player> players;
Theirs getters/setter , hashcodes and string similars.
On the other hand the entity Player
#Entity
#Table(name = "player")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
private String name;
So , I have the typical get call in a controller in a repository.
#RestController
#RequestMapping("/api/public/team")
public class TeamController {
#Autowired
private TeamRepository teamRepository;
#Autowired
private GenericMethods genericMethods;
#GetMapping(value = "/{id}")
public Team getPersona(#PathVariable("id") int id) {
return teamRepository.findOne(genericMethods.toLong(id));
}
And repository
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
Now , when I call this endpoint I receive the following answer and I think that is incorrect , I only need a List With Players
{
"id":2,
"teamId":0,
"abbreviation":null,
"team":null,
"simpleName":"Betis",
"logo":null,
"players":[
{
"id":1,
"team":2,
"category":{
"id":1,
"nombre":"juvenil a",
"language":null,
"description":null,
"league":[
],
"players":[
1,
{
"id":2,
"team":2,
"category":1,
"name":"hulio"
}
]
},
"name":"pepe"
},
2
]
}
I need to acces at information with Player and Team so I can't use #JsonIgnoreProperties
Could anyone help to solve this problem ?
Depending on what you really want to achieve you may try different options. I'm not sure if you're using (or intending to use) spring-data-rest or not.
1. Dedicated repository
Spring data rest will embed the related entities if they don't have their own repository. Try creating public interface PlayersRepository extends JpaRepository...
2. Lazy loading
Why are you using FetchType.EAGER ? Try without it.
3. Projections
Projections are only applicable to lists, not to individual entities (i.e. not explicitly what you're asking for). You can hide players from the Teams collection even if it was returned by default like so:
#Projection(name = "noPlayers", types = { Team.class })
public interface TeamWithoutPlayers {
Long getId();
long getTeamId();
String getAbbreviation();
String getTeam();
String getSimpleName();
String getLogo();
}
More info - Spring Data Rest Projections
4. Ignore during serialization in Team Entity using #JsonIgnore
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
Final thought
With spring-data-rest you can extend a CrudRepository instead of JpaRepository and access the item directly through the repository. That way you don't need to write a controller.

Java Spring projection inside projection

Is it possible to use a projection and in some related object use it's own projection?
For example, a have Exam, that has List<Question>. I'd like to request a list of exams (which I have a #projection), but I'd like to define the attributes to be retrieved for each related Question
If I understand correctly you want to use Projection as children of Projection. If it is the case, yes, you can. You can create a QuestionProjection and use inside the ExamProjection.
Example:
#Projection(name = "questionProjection", types = { Question.class })
public interface QuestionProjection {
// Getters
}
#Projection(name = "examProjection", types = { Exam.class })
public interface ExamProjection {
List<QuestionProjection> getQuestionList();
// Other Getters
}
You can do it something like this:
Assuming that your Exam entity might be:
#Entity
#Table(name = "EXAM")
public class Exam implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "DESCR")
private String descr;
#OneToMany(mappedBy = "exam")
private List<Question> questions;
// Getters and Setters
and your Question entity
#Entity
#Table(name = "QUESTION")
public class Question implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "DESCR")
private String descr;
#Column(name = "CONT")
private String cont;
#ManyToOne
#JoinColumn(name = "EXAM_ID")
#JsonIgnoreProperties("questions")
private Exam exam;
So, create projections
public interface ExamProjection {
Long getId();
String getDescr();
List<QuestionProjection> getQuestions();
}
and
public interface QuestionProjection {
Long getId();
String getDescr();
}
Your repository
#Repository
public interface ExamRepository extends JpaRepository<Exam, Long> {
List<Exam> findAll();
#Query("SELECT e FROM Exam e")
List<ExamProjection> findAllProjection();
}
Observe that using the findAll() method and passing the list type as ExamProjection, for some reason, causes a incompatible return type error. To avoid that, create a custom method, in this case, findAllProjection().
The service
#Service
public class ExamService {
#Autowired
ExamRepository examRepository;
public List<ExamProjection> findAllProjection() {
return examRepository.findAllProjection();
}
}
and finally, the resource
#RestController
#RequestMapping(value = "/exam")
public class ExamResource {
#Autowired
ExamService examService;
#GetMapping
public ResponseEntity<List<ExamProjection>> findAll() {
return ResponseEntity.ok().body(examService.findAllProjection());
}
}
Using the above, the json returned don't contains the field cont, because de QuestionProjection don't have the method getCont().
[
{
"id": 1,
"descr": "First Exam",
"questions": [
{
"id": 1,
"descr": "First Question"
},
{
"id": 2,
"descr": "Second Question"
}
]
}
]
If the QuestionProjection changes to
public interface QuestionProjection {
Long getId();
String getCont();
}
the json returned changes to
[
{
"id": 1,
"descr": "First Exam",
"questions": [
{
"id": 1,
"cont": "First Question Content"
},
{
"id": 2,
"cont": "Second Question Content"
}
]
}
]

Convert inner json object to string

My entity class looks like this :
#Entity
#Table(name = "tbl_programstrm_projstream")
public class ProgramStm_Projstrm_Model {
#Id
#GeneratedValue
private int id;
#Lob
#Column(columnDefinition="TEXT")
private String programstrm;
private int programstrmId;
#ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "projstrmId")//
private ProjectStreamModel projstrmId;
//getter and setter
}
Based on this entity I am getting result json like this
{
"programstrm": "D-BSS Implementation",
"programstrmId": 3,
"projstrmId": {
"name": "Program Leadership"
}
}
But I want ProjectStreamModel response as a string not as an object so that final result json looks like this"
{
"programstrm": "D-BSS Implementation",
"programstrmId": 3,
"projstrmId": "Program Leadership"
}
If I undestood right, using #JsonSerialize will probably help you (for example link)

Categories

Resources