使用@OneToOne 注释保存外键时出现问题。保存为空
Problem with saving foreign key with @OneToOne annotation. Saving as null
我有两个实体(Project
、OtherData
)和一个抽象实体。我正在使用 MySQL 和 Quarkus 框架。
问题: 当我尝试保存 Project
实体字段时 project_id
仍然是 null
.
Table 架构:
在下一张图片中,fk constraint in "project_other_data" table:
抽象实体:
@MappedSuperclass
public class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
// getters and setters
}
项目实体
@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
}
其他数据实体
@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
}
使用代码保存实体:
@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;
}
}
我能够通过 POST
一个带有嵌入式 OtherData
的新 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); // This line was added
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
,return 引用 project
,...)并且 Jackson 无法解析此循环。
要解决此问题,我建议将 DTO 和数据库实体分开并在它们之间显式映射。本质上:
- 构造 Dto-object 以表示 JSON-Request 和您希望收到的响应,按 non-cyclic 顺序
- 将 JSON-related 注释从数据库实体 类 转移到 DTO 类
- 在服务或repository-layer(您的选择)中,将 DTO 映射到数据库实体,设置所有字段(包括从
project
到 otherData
和 vice-versa)
- 在同一层中,将 database-entites 映射回 non-cyclic DTO
- Return 来自 REST 端点的 DTO
我有两个实体(Project
、OtherData
)和一个抽象实体。我正在使用 MySQL 和 Quarkus 框架。
问题: 当我尝试保存 Project
实体字段时 project_id
仍然是 null
.
Table 架构:
在下一张图片中,fk constraint in "project_other_data" table:
抽象实体:
@MappedSuperclass
public class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
// getters and setters
}
项目实体
@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
}
其他数据实体
@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
}
使用代码保存实体:
@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;
}
}
我能够通过 POST
一个带有嵌入式 OtherData
的新 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); // This line was added
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
,return 引用 project
,...)并且 Jackson 无法解析此循环。
要解决此问题,我建议将 DTO 和数据库实体分开并在它们之间显式映射。本质上:
- 构造 Dto-object 以表示 JSON-Request 和您希望收到的响应,按 non-cyclic 顺序
- 将 JSON-related 注释从数据库实体 类 转移到 DTO 类
- 在服务或repository-layer(您的选择)中,将 DTO 映射到数据库实体,设置所有字段(包括从
project
到otherData
和 vice-versa) - 在同一层中,将 database-entites 映射回 non-cyclic DTO
- Return 来自 REST 端点的 DTO