0

I'm having trouble with a many to many relation with JPA. My code looks as follows:

The Sensor class:

@Entity
@Table(name = "sensor")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sensor {
    @Id
    private long chipId;
    @OneToMany(mappedBy = "sensor")
    @JsonBackReference
    private Set<Link> userLinks;
    private String firmwareVersion;
    private long creationTimestamp;
    private String notes;
    private long lastMeasurementTimestamp;
    private long lastEditTimestamp;
    private double gpsLatitude;
    private double gpsLongitude;
    private double gpsAltitude;
    private String country;
    private String city;
    private boolean indoor;
    private boolean published;
}

The user class:

@Entity
@Table(name = "user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonManagedReference
    private int id;
    private String firstName;
    private String lastName;
    private String email;
    private String password;
    @OneToMany(mappedBy = "user")
    private Set<Link> sensorLinks;
    private int role;
    private int status;
    private long creationTimestamp;
    private long lastEditTimestamp;
}

And the Link class (relation class):

@Entity
@Table(name = "link")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Link {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "user_id")
    @MapsId("user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "sensor_id")
    @MapsId("sensor_id")
    private Sensor sensor;

    private boolean owner;
    private String name;
    private int color;
    private long creationTimestamp;
}

The controller:

...

@RequestMapping(method = RequestMethod.GET, path = "/user/{email}", produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "Returns details for one specific user")
public User getUserByEmail(@PathVariable("email") String email) {
    return userRepository.findByEmail(email).orElse(null);
}

...

The UserRepository:

public interface UserRepository extends JpaRepository<User, Integer> {

    Optional<User> findByEmail(String email);

    @Modifying
    @Query("UPDATE User u SET u.firstName = ?2, u.lastName = ?3, u.password = ?4, u.role = ?5, u.status = ?6 WHERE u.id = ?1")
    Integer updateUser(int id, String firstName, String lastName, String password, int role, int status);
}

I want to achieve, that the user endpoint shows all linked sensors with that particular user. What I get is only an error message:

JSON mapping problem: com.chillibits.particulatematterapi.model.db.main.User["sensorLinks"]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.chillibits.particulatematterapi.model.db.main.User["sensorLinks"])

How can I fix this issue?

Thanks in advance

Marc

------------------------------------ Edit -----------------------------------

According to Abinash Ghosh's answer, I added following DTOs:

UserDto:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
    private int id;
    private String firstName;
    private String lastName;
    private Set<LinkDto> sensorLinks;
    private int role;
    private int status;
    private long creationTimestamp;
    private long lastEditTimestamp;
}

LinkDto:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LinkDto {
    private Integer id;
    private SensorDto sensor;
    private boolean owner;
    private String name;
    private int color;
    private long creationTimestamp;
}

And the mapper (I realized it a bit different, but it should be the same):

public UserDto getUserByEmail(@PathVariable("email") String email) {
    User user = userRepository.findByEmail(email).orElse(null);
    return convertToDto(user);
}

private UserDto convertToDto(User user) {
    return mapper.map(user, UserDto.class);
}

This leads to following Exception:

2020-04-13 14:22:24.383  WARN 8176 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts      : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext@68ab57c7<rs=HikariProxyResultSet@2017009664 wrapping Result set representing update count of -1>

1) Error mapping com.chillibits.particulatematterapi.model.db.main.User to com.chillibits.particulatematterapi.model.io.UserDto
1 error] with root cause
java.lang.StackOverflowError: null
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.19.jar:8.0.19]
...
ChilliBits
  • 135
  • 1
  • 9
  • Add your controller and service also to find out your problem – Eklavya Apr 13 '20 at 11:19
  • Does this answer your question? [Infinite Recursion with Jackson JSON and Hibernate JPA issue](https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue) – Lemmy Apr 13 '20 at 11:37
  • I already tried `@JsonIgnore`. When I add it for both (sensorLinks and userLinks) it works. I also tried to add `@JsonIgnore` only to userLinks in `Sensor.class`, which doesn't work. – ChilliBits Apr 13 '20 at 11:42
  • Remove `private Sensor sensor;` from LinkDTO. Don't use any Enity class again into DTO then it will result same problem – Eklavya Apr 13 '20 at 12:44

2 Answers2

2

It's working! This post helped: https://stackoverflow.com/a/57111004/6296634

Seems that you should not use Lombok @Data in such cases.

ChilliBits
  • 135
  • 1
  • 9
0

When User serialized for the response, all getter methods of User's fields are called. So, User relational field sensorLinks's getter are also called to set value. This happened recursively. That's cause of infinite recursion.

It's better to not use Entity as a response. Create a DTO class for User then map User entity value into DTO then send response. Don't use any Enity class again into DTO then it will result same problem

For dynamically map one model to another you can use ModleMapper

public class UserDTO {
   //Fields you want to show in response & don't use enity class 
   private Set<LinkDTO> sensorLinks;
}
public class LinkDTO{
   //Fields you want to show in response &don't use enity class 
}


public User getUserByEmail(@PathVariable("email") String email) {
    User user = userRepository.findByEmail(email).orElse(null);
    UserDTO userDto = merge(user,UserDTO.class)
    return userDto;
}

public static <T> void merge(T source, T target) {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    modelMapper.map(source, target);
  }
Eklavya
  • 17,618
  • 4
  • 28
  • 57