Spring Boot一对多映射中Location表非ID字段被置空问题求助
问题:Spring Boot一对多关联导致Location字段被置空
我在Spring Boot中实现DesignationMaster(职位)与Locations(地点)的一对多映射:一个地点对应多个职位,一个职位归属一个地点。使用Microsoft SQL Server数据库,创建职位时传入仅含locationId的Location对象,职位关联成功,但Location表中除locationId外的所有字段被更新为null,查询返回的Location信息也全为null。
相关代码与配置
应用配置
#for ms server spring.datasource.url = jdbc:sqlserver://localhost\sqlexpress;databaseName=nifs_database;encrypt=true;trustServerCertificate=true spring.datasource.username = viranga spring.datasource.password = ******** spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql = true spring.jpa.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.properties.hibernate.globally_quoted_identifiers=true #spring.main.allow-circular-references=true spring.jackson.serialization.fail-on-empty-beans=false
DesignationMaster实体类
package com.nifs.backend.Admin.EmployeeDesignation; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.nifs.backend.Admin.Locations.Locations; import jakarta.persistence.*; import lombok.*; import java.util.Date; @Entity @Getter @Setter @AllArgsConstructor @NoArgsConstructor @Table(name = "designation_master") public class DesignationMaster { @Id @Column(name = "id", nullable = false, length = 10) private String id; @Column(name = "designation_name", length = 255) private String designationName; @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE}) @JoinColumn(name = "location_id", referencedColumnName = "location_id", nullable = false) @JsonIgnoreProperties("designations") private Locations location; @Temporal(TemporalType.TIMESTAMP) @Column(name="date_created") private Date dateCreated; @Temporal(TemporalType.TIMESTAMP) @Column(name="date_updated") private Date dateUpdated; }
Locations实体类
package com.nifs.backend.Admin.Locations; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.nifs.backend.Admin.EmployeeDesignation.DesignationMaster; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.util.Date; import java.util.List; @Entity @Setter @Getter @NoArgsConstructor @AllArgsConstructor @Table(name = "locations_master") public class Locations { @Id @Column(name = "location_id", nullable = false, length = 10) private String locationId; @Column(name = "location_name", length = 100) private String locationName; @Column(name = "address", length = 255) private String address; @Column(name = "tel_no", length = 15) private String telNo; @Column(name = "fax_no", length = 15) private String faxNo; @Temporal(TemporalType.TIMESTAMP) @Column(name="date_created") private Date dateCreated; @Temporal(TemporalType.TIMESTAMP) @Column(name="date_updated") private Date dateUpdated; @OneToMany(mappedBy = "location", cascade = {CascadeType.MERGE}, fetch = FetchType.LAZY) @JsonIgnoreProperties("location") private List<DesignationMaster> designations; }
Designation服务类
@Service public class DesignationService { @Autowired private DesignationRepostory desRepo; @Autowired private LocationRepository locRepo; public List<DesignationMaster> getAllDesignations() { return desRepo.findAll(); } public Boolean createDesignation(DesignationMaster desData) { if(desRepo.returnDesignation(desData.getId()) == null){ Date d = new Date(); desData.setDateCreated(d); desRepo.save(desData); return true; } return false; } }
Designation控制器
@RestController @RequestMapping("admin/designation") public class DesignationController { @Autowired private DesignationService desService; @GetMapping private List<DesignationMaster> getAllDesignations(){ return desService.getAllDesignations(); } @GetMapping("/newid") private String returnNewId(){ return desService.returnNewId(); } @PostMapping private Boolean createDesignation(@RequestBody DesignationMaster desData){ return desService.createDesignation(desData); } }
请求参数示例
{ "id": "DM1003", "designationName": "Driver", "location": { "locationId": "LM1001" } }
查询返回示例
{ "id": "DM1001", "designationName": "Lecturer", "location": { "locationId": "LM1001", "locationName": null, "address": null, "telNo": null, "faxNo": null, "dateCreated": null, "dateUpdated": null, "hibernateLazyInitializer": {} }, "dateCreated": "2022-12-21T13:56:32.429+00:00", "dateUpdated": null }
解决方案
1. 移除不必要的级联操作
当前@ManyToOne使用了CascadeType.MERGE,保存DesignationMaster时,Hibernate会尝试合并传入的仅含locationId的Location对象,导致数据库中Location的其他字段被置空。修改DesignationMaster的关联注解:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "location_id", referencedColumnName = "location_id", nullable = false) @JsonIgnoreProperties("designations") private Locations location;
2. 加载数据库中真实的Location对象
在保存DesignationMaster前,通过locationId从数据库查询完整的Location实体,替换传入的空对象:
public Boolean createDesignation(DesignationMaster desData) { if(desRepo.returnDesignation(desData.getId()) == null){ Date d = new Date(); desData.setDateCreated(d); // 从数据库加载完整Location Locations existingLocation = locRepo.findById(desData.getLocation().getLocationId()) .orElseThrow(() -> new IllegalArgumentException("Location not found with id: " + desData.getLocation().getLocationId())); desData.setLocation(existingLocation); desRepo.save(desData); return true; } return false; }
3. 修正数据库方言配置
当前使用MySQL方言却连接SQL Server,会导致Hibernate生成错误SQL,修改为SQL Server方言:
spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServerDialect
4. 优化关联查询(可选)
若希望查询Designation时返回完整Location信息,可在Repository中编写JPQL主动加载关联:
// DesignationRepostory中添加方法 @Query("SELECT d FROM DesignationMaster d JOIN FETCH d.location") List<DesignationMaster> findAllWithLocation();
然后在服务类中调用该方法替代findAll()。
内容的提问来源于stack exchange,提问作者Viranga
相关产品推荐
相关产品推荐

