使用`@OneToOne`注解保存外键时出现问题。保存为null。

huangapple go评论82阅读模式
英文:

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

问题

我有两个实体(ProjectOtherData),其中一个是抽象实体。我正在使用MySQL数据库和Quarkus框架。

问题: 当我尝试保存Project实体时,字段project_id保持为null

表结构:

使用`@OneToOne`注解保存外键时出现问题。保存为null。
使用`@OneToOne`注解保存外键时出现问题。保存为null。

在下图中显示了"project_other_data"表中的外键约束:
使用`@OneToOne`注解保存外键时出现问题。保存为null。

抽象实体

@MappedSuperclass
public class AbstractEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    // 获取器和设置器
}

Project实体

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

    // 获取器和设置器
}

OtherData实体

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

    // 获取器和设置器
}

使用以下代码保存实体:

@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 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:

使用`@OneToOne`注解保存外键时出现问题。保存为null。<br>
使用`@OneToOne`注解保存外键时出现问题。保存为null。

<br>On next picture there is shown, fk constraint in "project_other_data" table:
使用`@OneToOne`注解保存外键时出现问题。保存为null。

Abstract Entity:

@MappedSuperclass
public class AbstractEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    // getters and setters
}

Project Entity

@Entity
@Table(name = &quot;projects&quot;)
public class Project extends AbstractEntity {

    @NotNull
    @Column(name = &quot;name&quot;)
    private String name;

    @NotNull
    @Column(name = &quot;surname&quot;)
    private String surname;

    @Column(name = &quot;date_create&quot;)
    @JsonbDateFormat(value = &quot;yyyy-MM-dd&quot;)
    private LocalDate dateCreate;

    @Column(name = &quot;date_update&quot;)
    @JsonbDateFormat(value = &quot;yyyy-MM-dd&quot;)
    private LocalDate dateUpdate;

    @OneToOne(mappedBy = &quot;project&quot;, cascade = CascadeType.ALL)
    private OtherData otherData;

    // getters and setters
}

OtherData Entity

@Entity
@Table(name = &quot;project_other_data&quot;)
public class OtherData extends AbstractEntity {

    @OneToOne
    @JoinColumn(name = &quot;project_id&quot;)
    private Project project;

    @Column(name = &quot;days_in_year&quot;)
    private Integer daysInYear;

    @Column(name = &quot;holidays_in_year&quot;)
    private Integer holidaysInYear;

    @Column(name = &quot;weeks_in_year&quot;)
    private Integer weeksInYear;

    @Column(name = &quot;free_saturdays&quot;)
    private Integer freeSaturdays;

    @Column(name = &quot;downtime_coefficient&quot;)
    private BigDecimal downtimeCoefficient;

    @Column(name = &quot;changes&quot;)
    private Integer changes;

    // getters and setters
}

Saving entities with code:

@Path(&quot;projects&quot;)
@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;
    }
}

答案1

得分: 1

我能够通过使用嵌入的OtherData来重现这个问题,通过POST一个新的Project来实现。我用于POST的请求体如下:

{
    "name": "John",
    "surname": "Doe",
    "otherData": {}
}

关键问题是:数据库实体也用作DTO。因此,请求体中的otherData中的project字段设置为null(因为没有传递Project,这将导致递归无限定义)。

在将实体从REST控制器传递到服务再到存储库的过程中,otherDataproject从未设置。一个快速修复方法是修改ProjectService::saveProject如下:

public Project saveProject(Project project) {
    project.getOtherData().setProject(project); // 添加了这一行
    if (project.getId() == null) {
        entityManager.persist(project);
    } else {
        entityManager.merge(project);
    }

    return project;
}

这将修复数据库问题(project_id将被设置),但会导致下一个问题。由于

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.

对象结构是循环的(project引用otherData,而otherData再引用project,...),Jackson无法解析这个循环。

为了解决这个问题,我建议分开DTO和数据库实体,并在它们之间明确进行映射。基本上:

  • 结构化DTO对象以表示您期望接收的JSON请求和响应,以非循环顺序
  • 将与数据库实体类相关的JSON相关注释从数据库实体类移动到DTO类
  • 在服务或存储库层(根据您的选择)中,将DTO映射到数据库实体,设置所有字段(包括从projectotherData和反之亦然的引用)
  • 在同一层中,将数据库实体映射回非循环DTO
  • 从REST端点返回DTO们。
英文:

I was able to reproduce the problem by POSTing a new Project with an embedded OtherData. The body I used for the POST:

{
	&quot;name&quot;: &quot;John&quot;,
	&quot;surname&quot;: &quot;Doe&quot;,
	&quot;otherData&quot;: {}
}

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:

  • Structure the Dto-object to represent the JSON-Request and -Response you expect to receive, in a non-cyclic order
  • Transfer JSON-related annotations from the database entity classes to the DTO classes
  • In the service- or repository-layer (your choice), map the DTO to the database entites, setting all fields (including the references from project to otherData and vice-versa)
  • In the same layer, map database-entites back to non-cyclic DTOs
  • Return the DTOs from the REST endpoint

huangapple
  • 本文由 发表于 2020年8月16日 19:42:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/63436437.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定