You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

JPA保存含GENERATED ALWAYS生成列的实体报错,如何让数据库计算?

解决JPA持久化MySQL生成列(作为主键)的问题

这个问题的核心是:MySQL的GENERATED ALWAYS AS列不允许手动赋值,但JPA默认会把所有主键字段都包含在INSERT语句中,导致抛出SQLException。要让JPA完全交由数据库计算该列,只需调整实体类的注解配置即可,不需要改用原生查询。

具体修改步骤

1. 调整实体类中bucket字段的注解

bucket字段添加insertable = false, updatable = false,告诉JPA在插入和更新时不要处理这个列;同时添加@Generated(GenerationTime.INSERT),让JPA在插入完成后从数据库获取自动生成的bucket值,填充到实体中。

2. 简化主键类(IdClass)的配置

主键类中的字段只需保持和实体类主键字段名一致即可,不需要额外的@Column注解(实体类已经定义了列的详细配置)。

修改后的代码示例

DiscoverCache 实体类

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "discover_cache")
@IdClass(DiscoverCacheId.class)
@EntityListeners(AuditingEntityListener.class) // 配合@CreatedDate使用,确保自动填充创建时间
public class DiscoverCache {
    @Id
    @Column(length = 36)
    private String userId;

    @Id
    private int postId;

    @Id
    @Column(
        columnDefinition = "tinyint(4) GENERATED ALWAYS AS (floor(TO_SECONDS(`created_date`) / 900) % 17) STORED NOT NULL",
        insertable = false, // 插入时不包含该列
        updatable = false   // 更新时也不处理该列
    )
    @Generated(GenerationTime.INSERT) // 插入后从数据库获取生成的值
    private short bucket;

    @CreatedDate
    @Column(name = "created_date", updatable = false, columnDefinition = "DATETIME(3)")
    @JsonIgnore
    private Instant createdDate;

    public DiscoverCache(String userId, int postId) {
        this.userId = userId;
        this.postId = postId;
    }
}

DiscoverCacheId 主键类

@EqualsAndHashCode
@NoArgsConstructor
public class DiscoverCacheId implements Serializable {
    private String userId;
    private int postId;
    private short bucket;

    public DiscoverCacheId(String userId, int postId, short bucket) {
        this.userId = userId;
        this.postId = postId;
        this.bucket = bucket;
    }
}

关键注意事项

  • 开启JPA审计:如果使用@CreatedDate,必须确保开启审计功能——要么在实体类添加@EntityListeners(AuditingEntityListener.class),要么在Spring配置类上添加@EnableJpaAuditing,否则created_date会为null,导致数据库计算bucket时出错。
  • 测试save方法:创建DiscoverCache对象时只需传入userIdpostIdbucket留空即可。JPA会生成不包含bucket的INSERT语句,数据库自动计算后,JPA会把生成的bucket值自动填充到实体中。
  • 主键查询:因为bucket是主键的一部分,后续查询时需要使用包含bucketDiscoverCacheId,但插入完成后实体已经持有正确的bucket值,直接调用entity.getId()即可获取完整主键。

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

火山引擎 最新活动