Issue
I am using spring boot and hibernate with MySql and I am trying to figure out the best way to handle the error
nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: "some class field" could not initialize proxy - no Session".
I saw couple of solutions but I couldn't make them work, and also I do not understand the repercussions of implementing them.
I have the following Entities:
@Entity
@Table(name="machine_groups_to_versions")
@Getter
@Setter
//@JsonIgnoreProperties(value= {"machineGroup", "version"})
public class MachineGroupToVersion {
@Id
@GeneratedValue(strategy= GenerationType.AUTO, generator = "machine_groups_to_versions_seq")
@SequenceGenerator(name = "machine_groups_to_versions_seq", allocationSize = 1)
@Column(name = "id")
private long id;
@ManyToOne
@JoinColumn(name = "machine_group_id", nullable = false)
private MachineGroup machineGroup;
@ManyToOne
@JoinColumn(name = "version_id", nullable = false)
private Version version;
@Column(name = "state")
private String state;
@Column(name = "tested_time")
private Date testedTime;
@Column(name = "creation_time")
private Date creationTime;
}
@Entity
@Table(name="versions")
@Getter
@Setter
public class Version {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "versions_seq")
@SequenceGenerator(name = "versions_seq", allocationSize = 1)
@Column(name = "id")
private long id;
@Column(name = "name")
private String name;
@Column(name = "creation_time")
private Date creationTime;
@Column(name = "exe_file")
@Lob
private Blob exeFile;
@ManyToMany(mappedBy = "versions", cascade = CascadeType.MERGE)
private Set<MachineGroup> machineGroups = new HashSet<>();
}
@Entity
@Table(name="machine_groups")
@Getter
@Setter
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor
public class MachineGroup {
@Id
@GeneratedValue(strategy= GenerationType.AUTO, generator = "machine_groups_seq")
@SequenceGenerator(name = "machine_groups_seq", allocationSize = 1, initialValue = 2)
@Column(name = "id")
private long id;
@Column(name = "name")
private String name;
@Column(name = "creation_time")
private Date creationTime;
@Column(name = "is_official")
private boolean official;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "machine_properties_id", nullable = false)
private ContinuousIntegrationProperties defaultContinuousIntegrationProperties;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "machine_groups_to_users",
joinColumns = @JoinColumn(name = "machine_group_id"),
inverseJoinColumns = @JoinColumn(name = "user_id"))
private Set<User> users = new HashSet<>();
@ManyToMany(cascade = CascadeType.MERGE)
@JoinTable(name = "machine_groups_to_versions",
joinColumns = @JoinColumn(name = "machine_group_id"),
inverseJoinColumns = @JoinColumn(name = "version_id"))
private Set<Version> versions = new HashSet<>();
}
My Controller:
@GetMapping("/getByMachineGroupName/{machineGroupName}")
public ResponseEntity<List<MachineGroupToVersionDTO>> getAllVersions(@PathVariable String machineGroupName) {
logger.info("Incoming GetMachineGroupToVersionReport Request. Machine Group Name: {}", machineGroupName);
List<MachineGroupToVersionDTO> omgtv = machineGroupToVersionService.getByMachineGroupName(machineGroupName).stream()
.map(this::convertToDto).collect(Collectors.toList());
return new ResponseEntity<>(omgtv, HttpStatus.OK);
}
When I debug I can see that my omgtv has all the data I need but for some reason it is unable to return it.
As you can see in my MachineGroupToVersion class I extract MachineGroup and Version, the issue I see is:
- MachineGroup is referencing a set of versions and each version references a set of MachineGroup
- Same for the Version class
Looks like a cyclic issue with how the tables are created. I have tried to use the @JsonIgnoreProperties annotation but it just removes it from the response which is not the desired outcome.
I see the trace that shows what the error is but how can I fix this?
through reference chain: java.util.ArrayList[0]->entities.machinegrouptoversion.MachineGroupToVersionDTO[\"machineGroup\"]->entities.machinegroup.MachineGroup[\"users\"]->org.hibernate.collection.internal.PersistentSet[0]->entities.user.User[\"machineGroups\"]
I don't want to make everything eager unless I specifically call the get function.
How can I resolve this without changing the structure of my classes and DB?
UPDATE:
Service
- List item
@Transactional(transactionManager = "primaryTransactionManager", propagation= Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)
public List<MachineGroupToVersionDTO> getByMachineGroupName(String mgName) {
List<MachineGroupToVersionDTO> mgtvl = new ArrayList<>();
Optional<MachineGroup> mg = machineGroupService.getByName(mgName);
if(mg.isPresent())
mgtvl = machineGroupToVersionRepository.findByMachineGroup(mg.get()).stream()
.map(this::convertToDto).collect(Collectors.toList());
return mgtvl;
}
private MachineGroupToVersionDTO convertToDto(MachineGroupToVersion mgtv) {
MachineGroupToVersionDTO machineGroupToVersionDTO = new MachineGroupToVersionDTO();
machineGroupToVersionDTO.setMachineGroup(mgtv.getMachineGroup());
machineGroupToVersionDTO.setVersion(mgtv.getVersion());
machineGroupToVersionDTO.setCreationTime(mgtv.getCreationTime());
machineGroupToVersionDTO.setState(mgtv.getState());
machineGroupToVersionDTO.setTestedTime(mgtv.getTestedTime());
return machineGroupToVersionDTO;
}
Solution
Thank to @Shadov I was able to find the issue. In my case the issue was a mistake made by me when creating the DTO.
I mistakenly created the DTO with fields which are Entities and not other DTOs. Once I changed them from entities to DTOs I was able to get the response I needed.
Changed from this:
public class MachineGroupToVersionDTO {
private MachineGroup machineGroup;
private Version version;
private String state;
private Date testedTime;
private Date creationTime;
}
To this:
public class MachineGroupToVersionDTO {
private MachineGroupSimpleDTO machineGroup;
private VersionDTO version;
private String state;
private Date testedTime;
private Date creationTime;
}
Answered By - Gilad Dahan
Answer Checked By - Senaida (JavaFixing Volunteer)