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

复合主键字段混用@Id与@Generated遇插入异常,求解决方案

复合主键字段上同时使用@Id与@Generated的问题及解决方案

可以在复合主键字段上同时使用JPA的@Id和Hibernate的@Generated注解,但你遇到的问题根源在于:Hibernate处理@IdClass类型的复合主键时,会忽略@Column(insertable=false)和@Generated的配置,强制将所有主键字段包含在INSERT语句中,导致数据库默认值无法生效,最终出现字段值错位报错。

以下是几个符合你需求的可行解决方案:

解决方案1:自定义INSERT语句(推荐)

直接在实体类上使用Hibernate的@SQLInsert注解,手动编写不包含CREATED_AT的INSERT语句,让数据库自动用默认值生成该字段:

@NoArgsConstructor
@Getter
@Setter
@IdClass(BasePK.class)
@Table(name = "MY_TABLE")
@Entity
// 替换OTHER_FIELDS为你实际的其他字段,保持字段顺序与实体类一致
@SQLInsert(sql = "INSERT INTO MY_TABLE (ID, OTHER_FIELDS) VALUES (?, ?)")
public class MyEntity implements Identifiable<String> {

    @Id
    @NotNull
    @GenericGenerator(name = "ulid-generator", strategy = "com.example.common.jpa.ULIDIdentifier")
    @GeneratedValue(generator = "ulid-generator")
    @Column(name = "ID", updatable = false, nullable = false, length = 26)
    private String id;
    
    @Id
    @Generated(GenerationTime.INSERT)
    @Column(name = "CREATED_AT", insertable = false, updatable = false, nullable = false)
    private Instant createdAt;
    
    // 其他字段...
}

插入完成后,调用entityManager.refresh(myEntity)从数据库拉取生成的CREATED_AT值,确保实体对象中的该字段是最新的数据库值。

解决方案2:改用@EmbeddedId替代@IdClass

Hibernate对@EmbeddedId(嵌入式主键)的字段属性支持更友好,可以正常识别insertable=false和@Generated配置:

  1. 修改主键类为嵌入式:
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class BasePK implements Serializable {

    private String id;
    
    @Generated(GenerationTime.INSERT)
    @Column(name = "CREATED_AT", insertable = false, updatable = false, nullable = false)
    private Instant createdAt;
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BasePK)) return false;
        BasePK basePK = (BasePK) o;
        return Objects.equals(id, basePK.id) && Objects.equals(createdAt, basePK.createdAt);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id, createdAt);
    }
}
  1. 修改实体类使用@EmbeddedId:
@NoArgsConstructor
@Getter
@Setter
@Table(name = "MY_TABLE")
@Entity
public class MyEntity implements Identifiable<String> {

    @EmbeddedId
    private BasePK basePK;
    
    // 为ID单独提供getter/setter,兼容原有的Identifiable接口
    @GenericGenerator(name = "ulid-generator", strategy = "com.example.common.jpa.ULIDIdentifier")
    @GeneratedValue(generator = "ulid-generator")
    public String getId() {
        return basePK != null ? basePK.getId() : null;
    }

    public void setId(String id) {
        if (basePK == null) {
            basePK = new BasePK();
        }
        basePK.setId(id);
    }
    
    // 其他字段...
}

插入后同样需要调用entityManager.refresh(myEntity)获取数据库生成的CREATED_AT值。

解决方案3:自定义Hibernate事件监听器

通过监听Hibernate的插入事件,在插入前跳过CREATED_AT字段的插入,插入后从数据库同步值:

  1. 编写监听器类:
public class GeneratedIdListener implements PreInsertEventListener, PostInsertEventListener {

    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        if (event.getEntity() instanceof MyEntity) {
            // 找到CREATED_AT字段的索引,标记为不插入
            String[] propertyNames = event.getPersister().getPropertyNames();
            int createdAtIdx = -1;
            for (int i = 0; i < propertyNames.length; i++) {
                if ("createdAt".equals(propertyNames[i])) {
                    createdAtIdx = i;
                    break;
                }
            }
            if (createdAtIdx != -1) {
                event.getState()[createdAtIdx] = null;
                event.getPersister().setPropertyInsertable(createdAtIdx, false);
            }
        }
        return false;
    }

    @Override
    public void onPostInsert(PostInsertEvent event) {
        if (event.getEntity() instanceof MyEntity) {
            MyEntity entity = (MyEntity) event.getEntity();
            // 查询数据库获取最新的CREATED_AT值并设置回实体
            EntityManager em = event.getSession().getEntityManagerFactory().createEntityManager();
            MyEntity refreshed = em.find(MyEntity.class, new BasePK(entity.getId(), null));
            entity.setCreatedAt(refreshed.getCreatedAt());
            em.close();
        }
    }

    @Override
    public boolean requiresPostCommitHanding(EntityPersister persister) {
        return false;
    }
}
  1. 在Spring Boot配置中注册监听器(application.properties):
spring.jpa.properties.hibernate.ejb.event.pre-insert=com.example.GeneratedIdListener
spring.jpa.properties.hibernate.ejb.event.post-insert=com.example.GeneratedIdListener

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 18:40:25