javaone-to-onequarkusjpa-annotations

Problem with saving foreign key with @OneToOne annotation. Saving as null


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:

table "projects"
table "project_other_data"


On next picture there is shown, fk constraint in "project_other_data" table: enter image description here

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;
    }
}

Solution

  • I was able to reproduce the problem by POSTing 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: