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

使用CrudRepository时遭遇DbActionExecutionException问题求助

使用CrudRepository时遇到DbActionExecutionException异常的解决方案

问题概述

调用CrudRepository的save方法时抛出DbActionExecutionException: Failed to execute DbAction.UpdateRoot(...),根因为java.lang.IllegalStateException: Required identifier property not found for class com.w4t3rcs.todolist.model.entity.User

相关代码

测试类

@SpringBootTest
class UserRepositoryTest {
    private final UserRepository userRepository;

    @Autowired
    public UserRepositoryTest(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Test
    void test() {
        User user = new User("test", "test", "test@gmail.com");
        userRepository.save(user);
    }
}

User实体类

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Table("users")
@Entity
public class User implements UserDetails, Persistable<String> {
    @Id
    @NotEmpty(message = "Username is empty!")
    private String username;
    @NotEmpty(message = "Password is empty!")
    private String password;
    @NotEmpty(message = "Email is empty!")
    @Email(message = "Email isn't valid")
    private String email;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String getId() {
        return getUsername();
    }

    @Override
    public boolean isNew() {
        return getId() == null;
    }
}

数据配置类

@EnableTransactionManagement
@EnableJpaRepositories(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserRepository.class}))
@Configuration
public class DataConfig {
}

Repository接口

import com.w4t3rcs.todolist.model.entity.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, String> {
}

堆栈信息

org.springframework.data.relational.core.conversion.DbActionExecutionException: Failed to execute DbAction.UpdateRoot(entity=User(username=test, password=$2a$10$jbNHYFBd9AGD8LVtXyej4.7paMKX30IjZryMCsKs3xyhfIsbF0KXC, email=test@gmail.com))
at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:118)
at org.springframework.data.jdbc.core.AggregateChangeExecutor.lambda$executeSave$0(AggregateChangeExecutor.java:61)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.springframework.data.relational.core.conversion.SaveBatchingAggregateChange.forEachAction(SaveBatchingAggregateChange.java:70)
at org.springframework.data.jdbc.core.AggregateChangeExecutor.executeSave(AggregateChangeExecutor.java:61)
at org.springframework.data.jdbc.core.JdbcAggregateTemplate.performSave(JdbcAggregateTemplate.java:491)
at org.springframework.data.jdbc.core.JdbcAggregateTemplate.save(JdbcAggregateTemplate.java:168)
at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.save(SimpleJdbcRepository.java:68)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:249)
at jdk.proxy2/jdk.proxy2.$Proxy115.save(Unknown Source)
at com.w4t3rcs.todolist.UserRepositoryTest.test(UserRepositoryTest.java:26)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

根因

java.lang.IllegalStateException: Required identifier property not found for class com.w4t3rcs.todolist.model.entity.User
at org.springframework.data.mapping.PersistentEntity.getRequiredIdProperty(PersistentEntity.java:135)
at org.springframework.data.relational.core.mapping.BasicRelationalPersistentEntity.getIdColumn(BasicRelationalPersistentEntity.java:159)
at org.springframework.data.jdbc.core.convert.SqlContext.getIdColumn(SqlContext.java:44)
at org.springframework.data.jdbc.core.convert.SqlGenerator.getIdColumn(SqlGenerator.java:807)
at org.springframework.data.jdbc.core.convert.SqlGenerator.createBaseUpdate(SqlGenerator.java:718)
at org.springframework.data.jdbc.core.convert.SqlGenerator.createUpdateSql(SqlGenerator.java:692)
at org.springframework.data.util.Lazy.getNullable(Lazy.java:135)
at org.springframework.data.util.Lazy.get(Lazy.java:113)
at org.springframework.data.jdbc.core.convert.SqlGenerator.getUpdate(SqlGenerator.java:369)
at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.update(DefaultDataAccessStrategy.java:136)
at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.updateWithoutVersion(JdbcAggregateChangeExecutionContext.java:336)
at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.executeUpdateRoot(JdbcAggregateChangeExecutionContext.java:129)
at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:93)
... 36 more

问题分析

  1. 注解混用导致ID识别失败:从配置类DataConfig的@EnableJpaRepositories(excludeFilters)可以看出,你希望用Spring Data JDBC处理UserRepository,但User实体类使用了JPA的jakarta.persistence.Id注解,而Spring Data JDBC仅识别Spring Data自身的org.springframework.data.annotation.Id注解,因此无法识别主键属性,抛出“Required identifier property not found”异常。
  2. Persistable的isNew()逻辑错误:创建User实例时传入了username(主键值),此时getId()返回非null,导致isNew()返回false,Spring Data会尝试执行update操作而非insert,但数据库中不存在该记录,进一步触发异常。

解决方案

方案1:使用Spring Data JDBC(匹配当前配置)

修改User实体类:

  1. 替换JPA的@Id为Spring Data的@Id;
  2. 移除JPA的@Entity注解;
  3. 新增标记字段修正isNew()逻辑,手动指定主键时Spring Data无法自动判断实体是否为新,需通过字段标记区分:
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Table("users")
public class User implements UserDetails, Persistable<String> {
    @Id
    @NotEmpty(message = "Username is empty!")
    private String username;
    @NotEmpty(message = "Password is empty!")
    private String password;
    @NotEmpty(message = "Email is empty!")
    @Email(message = "Email isn't valid")
    private String email;

    @Transient
    private boolean isNew = true;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String getId() {
        return getUsername();
    }

    @Override
    public boolean isNew() {
        return isNew;
    }

    // 可选:从数据库加载实体后调用此方法标记为非新实体
    public void markAsNotNew() {
        this.isNew = false;
    }
}

方案2:切换为Spring Data JPA(移除JDBC相关配置)

若原本计划使用JPA:

  1. 移除User类的@Table注解,保留@Entity;
  2. 移除DataConfig中对UserRepository的排除配置,让@EnableJpaRepositories扫描到它;
  3. 可选择移除Persistable接口,让JPA自动管理实体状态,或调整isNew()逻辑。

修改后的DataConfig:

@EnableTransactionManagement
@EnableJpaRepositories
@Configuration
public class DataConfig {
}

验证

修改完成后重新运行测试类,save方法会正确执行insert操作,不再抛出异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:37:03