You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 22:45:40