I have two entities (Project
, OtherData
) with one abstract entity. I'm using MySQL and Quarkus framework.
Problem: When I try to save Project
entity field project_id
remains null
.
Table schemas:
On next picture there is shown, fk constraint in "project_other_data" table:
Abstract Entity:
@MappedSuperclass
public class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
// getters and setters
}
Project Entity
@Entity
@Table(name = "projects")
public class Project extends AbstractEntity {
@NotNull
@Column(name = "name")
private String name;
@NotNull
@Column(name = "surname")
private String surname;
@Column(name = "date_create")
@JsonbDateFormat(value = "yyyy-MM-dd")
private LocalDate dateCreate;
@Column(name = "date_update")
@JsonbDateFormat(value = "yyyy-MM-dd")
private LocalDate dateUpdate;
@OneToOne(mappedBy = "project", cascade = CascadeType.ALL)
private OtherData otherData;
// getters and setters
}
OtherData Entity
@Entity
@Table(name = "project_other_data")
public class OtherData extends AbstractEntity {
@OneToOne
@JoinColumn(name = "project_id")
private Project project;
@Column(name = "days_in_year")
private Integer daysInYear;
@Column(name = "holidays_in_year")
private Integer holidaysInYear;
@Column(name = "weeks_in_year")
private Integer weeksInYear;
@Column(name = "free_saturdays")
private Integer freeSaturdays;
@Column(name = "downtime_coefficient")
private BigDecimal downtimeCoefficient;
@Column(name = "changes")
private Integer changes;
// getters and setters
}
Saving entities with code:
@Path("projects")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProjectRest {
@Inject
ProjectService projectService;
@POST
public Response saveProject(Project project) {
return Response.ok(projectService.saveProject(project)).build();
}
}
@RequestScoped
@Transactional
public class ProjectService {
@Inject
EntityManager entityManager;
public Project saveProject(Project project) {
if (project.getId() == null) {
entityManager.persist(project);
} else {
entityManager.merge(project);
}
return project;
}
}
I was able to reproduce the problem by POST
ing a new Project
with an embedded OtherData
. The body I used for the POST
:
{
"name": "John",
"surname": "Doe",
"otherData": {}
}
Point is: the database entity is also used as DTO. Thus, the field project
in otherData
for the request body is set to null
(since no Project
is passed along this would be a recursive infinite definition).
During processing the entity from the rest controller to the service to the repository, the project
of otherData
is never set. A quick fix is to modify ProjectService::saveProject
as follows:
public Project saveProject(Project project) {
project.getOtherData().setProject(project); // This line was added
if (project.getId() == null) {
entityManager.persist(project);
} else {
entityManager.merge(project);
}
return project;
}
This will fix the database issue (the project_id
will be set), but leads to the next issue. The response body cannot be serialized due to an
org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'otherData' from com.nikitap.org_prod.entities.Project
...
Caused by: javax.json.bind.JsonbException: Recursive reference has been found in class class com.nikitap.org_prod.entities.Project.
The object structure is cyclic (project
references otherData
, which return references project
, ...) and Jackson is unable to resolve this cycle.
To fix this issue, I would suggest to separate DTOs and database entity and explicitly map between them. In essence:
project
to otherData
and vice-versa)