使用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
问题分析
- 注解混用导致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”异常。 - Persistable的isNew()逻辑错误:创建
User实例时传入了username(主键值),此时getId()返回非null,导致isNew()返回false,Spring Data会尝试执行update操作而非insert,但数据库中不存在该记录,进一步触发异常。
解决方案
方案1:使用Spring Data JDBC(匹配当前配置)
修改User实体类:
- 替换JPA的
@Id为Spring Data的@Id; - 移除JPA的
@Entity注解; - 新增标记字段修正
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:
- 移除
User类的@Table注解,保留@Entity; - 移除
DataConfig中对UserRepository的排除配置,让@EnableJpaRepositories扫描到它; - 可选择移除
Persistable接口,让JPA自动管理实体状态,或调整isNew()逻辑。
修改后的DataConfig:
@EnableTransactionManagement @EnableJpaRepositories @Configuration public class DataConfig { }
验证
修改完成后重新运行测试类,save方法会正确执行insert操作,不再抛出异常。
内容的提问来源于stack exchange,提问作者w4t3r
相关产品推荐
相关产品推荐

