Spring Data JPA保存父对象时CascadeType.PERSIST不生效问题
Spring Data JPA级联保存CascadeType.PERSIST不生效问题排查与解决
问题描述
保存父对象Course时,已配置CascadeType.PERSIST但未生效,抛出EntityNotFoundException,提示找不到ID为104的CourseMaterial实体。
相关代码
父实体Course
public class Course { @Id private Long courseId; private String title; private Integer credit; @OneToOne(cascade = CascadeType.PERSIST) @JoinColumn(name = "fk_course_id", referencedColumnName = "courseMaterialId") private CourseMaterial courseMaterial; }
子实体CourseMaterial
public class CourseMaterial { @Id private Long courseMaterialId; private String url; }
控制器方法
@PostMapping() public void saveStudent(@RequestBody Course course) { LOGGER.info("inside CourseController.saveStudent method"); courseService.saveCourse(course); }
请求示例
{ "courseId":4, "courseMaterial":{ "courseMaterialId":104, "url":"www.java.com" }, "credit":40, "title":"learn java" }
错误日志
javax.persistence.EntityNotFoundException: Unable to find com.sachin.demo.entity.CourseMaterial with id 104 at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:163) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:216) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:332) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:108) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:110) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1186) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1051) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:697) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:240) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.EntityType.replace(EntityType.java:358) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.AbstractType.replace(AbstractType.java:164) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.type.TypeHelper.replace(TypeHelper.java:204) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:488) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:241) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:318) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:172) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:99) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:793) ~[hibernate-core-5.4.33.jar:5.4.33] at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:780) ~[hibernate-core-5.4.33.jar:5.4.33] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na] at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362) ~[spring-orm-5.3.14.jar:5.3.14] at com.sun.proxy.$Proxy130.merge(Unknown Source) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na] at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311) ~[spring-orm-5.3.14.jar:5.3.14] at com.sun.proxy.$Proxy130.merge(Unknown Source) ~[na:na] at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:600) ~[spring-data-jpa-2.5.7.jar:2.5.7] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
问题原因
- CascadeType.PERSIST作用范围有限:该级联类型仅对JPA的
persist操作生效,而Spring Data JPA的save方法在实体携带非null ID时,会触发merge操作而非persist,此时PERSIST级联规则不生效。 - 显式指定子实体ID触发加载逻辑:请求中给
CourseMaterial指定了courseMaterialId=104,JPA会判定该实体已存在于数据库,尝试加载而非新建,找不到则抛出异常。 - 实体ID无生成策略:当前实体ID为手动指定,未配置自增或其他生成策略,进一步强化了JPA对“实体已存在”的判断。
解决方案
方案一:扩展级联类型,支持MERGE操作
修改Course实体中的@OneToOne注解,添加CascadeType.MERGE,使merge父实体时级联处理子实体:
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(name = "fk_course_id", referencedColumnName = "courseMaterialId") private CourseMaterial courseMaterial;
此方案适用于需要保留手动指定ID的场景,merge操作会自动插入不存在的子实体。
方案二:使用persist方法强制新建实体
如果业务明确是新建实体,在Service层直接调用EntityManager的persist方法,而非Spring Data JPA的save:
@Service public class CourseService { @Autowired private EntityManager entityManager; public void saveCourse(Course course) { entityManager.persist(course); } }
同时需要给实体添加ID自增策略:
Course实体修改:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long courseId;
CourseMaterial实体修改:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long courseMaterialId;
请求体中去掉手动指定的ID:
{ "title":"learn java", "credit":40, "courseMaterial":{ "url":"www.java.com" } }
方案三:规范双向关联(可选)
如果需要双向维护关联关系,在CourseMaterial中添加关联字段并指定mappedBy:
public class CourseMaterial { @Id private Long courseMaterialId; private String url; @OneToOne(mappedBy = "courseMaterial") private Course course; }
此操作主要用于规范关联逻辑,避免后续操作出现关联异常。
验证步骤
- 根据业务场景选择对应方案修改代码
- 调整请求参数(如方案二需移除ID字段)
- 重新发起请求,检查数据库中
Course和CourseMaterial是否均成功插入
内容的提问来源于stack exchange,提问作者Sachin Mankotia
相关产品推荐
相关产品推荐

