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对象时只需传入userId和postId,bucket留空即可。JPA会生成不包含bucket的INSERT语句,数据库自动计算后,JPA会把生成的bucket值自动填充到实体中。 - 主键查询:因为
bucket是主键的一部分,后续查询时需要使用包含bucket的DiscoverCacheId,但插入完成后实体已经持有正确的bucket值,直接调用entity.getId()即可获取完整主键。
内容的提问来源于stack exchange,提问作者marekk




