英文:
Problem with saving foreign key with @OneToOne annotation. Saving as null
问题
我有两个实体(Project
,OtherData
),其中一个是抽象实体。我正在使用MySQL数据库和Quarkus框架。
问题: 当我尝试保存Project
实体时,字段project_id
保持为null
。
表结构:
在下图中显示了"project_other_data"表中的外键约束:
抽象实体:
@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:
<br>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;
}
}
答案1
得分: 1
我能够通过使用嵌入的OtherData
来重现这个问题,通过POST
一个新的Project
来实现。我用于POST
的请求体如下:
{
"name": "John",
"surname": "Doe",
"otherData": {}
}
关键问题是:数据库实体也用作DTO。因此,请求体中的otherData
中的project
字段设置为null
(因为没有传递Project
,这将导致递归无限定义)。
在将实体从REST控制器传递到服务再到存储库的过程中,otherData
的project
从未设置。一个快速修复方法是修改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映射到数据库实体,设置所有字段(包括从
project
到otherData
和反之亦然的引用) - 在同一层中,将数据库实体映射回非循环DTO
- 从REST端点返回DTO们。
英文:
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:
- 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
tootherData
and vice-versa) - In the same layer, map database-entites back to non-cyclic DTOs
- Return the DTOs from the REST endpoint
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论