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

JPA保存TacoOrder时外键与关联表数据缺失问题排查

问题:Spring MVC提交TacoOrder后关联数据未正确保存

问题描述

使用Spring MVC控制器通过两页视图提交TacoOrder表单对象,调用orderRepo.save()方法保存。日志显示提交的TacoOrder包含ID及关联的Taco数据,但H2数据库中出现两个异常:

  1. taco表的taco_order_id外键全为null
  2. ingredient_ref关联表无数据

Ingredient表与TacoOrder表数据正常,已通过data.sql预加载Ingredient表,相关实体类及配置如下:


实体类代码

Ingredient类

@Data
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class Ingredient {
    @Id
    private final String id;
    private final String name;
    @Enumerated(EnumType.STRING)
    private final Type type;

    @OneToMany(mappedBy = "ingredient")
    private List<IngredientRef> ingredientRefs = new ArrayList<>();

    public enum Type {
        WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
    }
}

TacoOrder类

@Data
@Entity
public class TacoOrder implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Date placedAt = new Date();
    @NotBlank(message = "Delivery name is required")
    private String deliveryName;
    @NotBlank
    private String deliveryStreet;
    @NotBlank(message = "City is required")
    private String deliveryCity;
    @NotBlank(message = "State is required")
    private String deliveryState;
    @NotBlank(message = "Zip Code is required")
    private String deliveryZip;
    @CreditCardNumber(message = "Invalid Credit Card Number")
    private String ccNumber;
    @Pattern(regexp = "^(0[1-9]|1[0-2])/20(2[3-9]|[3-9][0-9])$",message = "Must be of format MM/YYYY")
    private String ccExpiration;
    @NotNull
    @Digits(integer=3, fraction=0, message="Invalid CVV")
    private String ccCVV;

    @OneToMany(mappedBy = "tacoOrder", cascade = CascadeType.ALL)
    private List<Taco> tacos = new ArrayList<>();
    public void addTaco(Taco taco) {
        this.tacos.add(taco);
    }
}

Taco类

@Data
@Entity
public class Taco {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Date createdAt = new Date();

    @NotBlank
    @Size(min = 5, message = "Name must be at least 5 characters long")
    private String name;

    @ManyToMany
    private List<Ingredient> ingredients;

    @ManyToOne
    @JoinColumn(name = "taco_order_id")
    private TacoOrder tacoOrder;

    @OneToMany(mappedBy = "taco", cascade = CascadeType.ALL)
    private List<IngredientRef> ingredientRefs = new ArrayList<>();
}

IngredientRef类

@Data
@Entity
public class IngredientRef {
    @Id
    @ManyToOne
    @JoinColumn(name = "ingredient_id")
    private Ingredient ingredient;

    @ManyToOne
    @JoinColumn(name = "taco_id")
    private Taco taco;
}

日志输出

2023-08-31T02:30:35.257+05:30  INFO 16268 --- [io-8080-exec-10] com.example.tacohouse.OrderController    : Order submitted: TacoOrder(id=1, placedAt=Thu Aug 31 02:29:51 IST 2023, deliveryName=a, deliveryStreet=b, deliveryCity=b, deliveryState=b, deliveryZip=b, ccNumber=4417123456789113, ccExpiration=11/2024, ccCVV=111, tacos=[Taco(id=1, createdAt=Thu Aug 31 02:29:56 IST 2023, name=firstTaco, ingredients=[Ingredient(id=COTO, name=Corn Tortilla, type=WRAP, ingredientRefs=[])], tacoOrder=null, ingredientRefs=[]), Taco(id=2, createdAt=Thu Aug 31 02:30:02 IST 2023, name=secondTaco, ingredients=[Ingredient(id=CARN, name=Carnitas, type=PROTEIN, ingredientRefs=[])], tacoOrder=null, ingredientRefs=[]), Taco(id=3, createdAt=Thu Aug 31 02:30:07 IST 2023, name=thirdTaco, ingredients=[Ingredient(id=CHED, name=Cheddar, type=CHEESE, ingredientRefs=[]), Ingredient(id=JACK, name=Monterrey Jack, type=CHEESE, ingredientRefs=[])], tacoOrder=null, ingredientRefs=[])])

H2数据库数据

  • ingredient表:数据正常
  • taco_order表:数据正常
  • taco表:
CREATED_ATIDTACO_ORDER_IDNAME
2023-08-31 02:29:511nullfirstTaco
2023-08-31 02:30:022nullsecondTaco
2023-08-31 02:30:073nullthirdTaco
  • ingredient_ref表:无数据

应用配置(application.properties)

spring.datasource.generate-unique-name=false
spring.datasource.name=tacocloud
spring.jpa.hibernate.ddl-auto=create
spring.jpa.defer-datasource-initialization=true

问题原因及修复方案

1. Taco表taco_order_id为null的原因及修复

在双向一对多关联中,拥有外键的一方(Taco)是关联的维护端,TacoOrder是被维护端。当前addTaco方法仅将Taco加入列表,但未设置Taco的tacoOrder引用,导致Hibernate无法更新外键。

修改TacoOrder的addTaco方法:

public void addTaco(Taco taco) {
    this.tacos.add(taco);
    taco.setTacoOrder(this); // 新增该行,维护双向关联
}

2. ingredient_ref表无数据的原因及修复

(1)主键定义错误

IngredientRef使用两个@ManyToOne作为主键,但JPA无法直接识别这种复合主键,需要通过@EmbeddedId定义复合主键:

创建复合主键类:

@Data
@Embeddable
public class IngredientRefId implements Serializable {
    private String ingredient; // 对应Ingredient的id
    private Long taco; // 对应Taco的id
}

修改IngredientRef类:

@Data
@Entity
public class IngredientRef {
    @EmbeddedId
    private IngredientRefId id;

    @ManyToOne
    @MapsId("ingredient") // 映射到复合主键的ingredient字段
    @JoinColumn(name = "ingredient_id")
    private Ingredient ingredient;

    @ManyToOne
    @MapsId("taco") // 映射到复合主键的taco字段
    @JoinColumn(name = "taco_id")
    private Taco taco;
}

(2)关联维护冲突

当前Taco同时使用@ManyToMany和IngredientRef关联Ingredient,两者冲突。需移除@ManyToMany,改为手动维护IngredientRef关联:

修改Taco类:

@Data
@Entity
public class Taco {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Date createdAt = new Date();

    @NotBlank
    @Size(min = 5, message = "Name must be at least 5 characters long")
    private String name;

    @ManyToOne
    @JoinColumn(name = "taco_order_id")
    private TacoOrder tacoOrder;

    @OneToMany(mappedBy = "taco", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<IngredientRef> ingredientRefs = new ArrayList<>();

    // 新增方法,添加配料并维护关联
    public void addIngredient(Ingredient ingredient) {
        IngredientRef ref = new IngredientRef();
        IngredientRefId refId = new IngredientRefId();
        refId.setIngredient(ingredient.getId());
        refId.setTaco(this.getId());
        ref.setId(refId);
        ref.setIngredient(ingredient);
        ref.setTaco(this);
        this.ingredientRefs.add(ref);
    }
}

在创建Taco时,调用addIngredient方法添加配料,即可正确保存到ingredient_ref表中。


内容的提问来源于stack exchange,提问作者alpha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 08:27:02