求助:Detached entity passed to persist异常,CascadeType配置无效
保存TeamMember时遇到detached entity异常的解决方案
异常信息
2023-07-06 16:30:23.683 ERROR 28095 --- [ main] c.f.webapp.impl.TeamMemberServiceImpl : Error has occurred while saving team member org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.feastech.webapp.entities.Company; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.feastech.webapp.entities.Company
相关实体类代码
TeamMember
@Entity @Table(name = Constants.TEAM_MEMBER) @ToString @SuperBuilder(toBuilder = true) @EqualsAndHashCode(callSuper = true) public class TeamMember extends BaseEntity implements Serializable { private static final String COLUMN_COMPANY_ID_FK = "company_id_fk"; private static final long serialVersionUID = 413726318355320936L; @ManyToOne @JoinColumn(name = COLUMN_COMPANY_ID_FK, referencedColumnName = Constants.ID, nullable = false) private Company company; // Other columns }
Company
@Entity @Table(name = Constants.COMPANY) @ToString(callSuper = true) @SuperBuilder(toBuilder = true) public class Company extends BaseEntity implements Serializable { private static final long serialVersionUID = 6747639319802376867L; @Column(name = Company.COLUMN_COMPANY_NAME, nullable = false, unique = true) private String name; // Other columns }
业务层代码
@Override public Response<Boolean> addTeamMember(Long companyId, AddTeamMemberRequest addTeamMemberRequest) { try { TeamMember teamMember = buildTeamMember(companyId, addTeamMemberRequest); teamMemberRepository.save(teamMember); return new Response<>(ResponseStatus.SUCCESS, true); } catch (Exception ex) { log.error("Error has occurred while saving team member {}", ExceptionUtils.getStackTrace(ex)); } return new Response<>(ResponseStatus.SUCCESS, false); }
private TeamMember buildTeamMember(Long companyId, AddTeamMemberRequest addTeamMemberRequest) { Company company = companyRepository.findById(companyId) .orElseThrow(() -> new FeastechException(ResponseStatus.INVALID_REQUEST)); TeamMember teamMember = new TeamMember(); teamMember.setCompany(company); // Other setters return teamMember; }
已尝试方案
在TeamMember的@ManyToOne注解上添加CascadeType.MERGE和CascadeType.ALL,问题未解决。
解决方案
问题根源
companyRepository.findById获取的Company实体,若当前方法未处于事务管理中,方法执行完后该实体会变为游离态(detached)。保存TeamMember时,Hibernate会尝试对关联的游离态Company执行persist操作,但persist仅支持瞬时态(transient)对象,因此抛出异常。
具体解决方法
方法一:为业务方法添加事务注解
在addTeamMember方法上添加@Transactional,确保方法执行过程中EntityManager处于打开状态,获取的Company保持持久态:
@Override @Transactional public Response<Boolean> addTeamMember(Long companyId, AddTeamMemberRequest addTeamMemberRequest) { // 原有代码逻辑 }
此时关联的Company是持久态实体,保存TeamMember时Hibernate不会触发对Company的额外操作,仅维护外键关联。
方法二:获取Company的代理对象
使用EntityManager.getReference()(或Spring Data JPA的getById())获取Company的代理对象,该代理是持久态且不会触发实际数据库查询,避免实体变为游离态:
// 注入EntityManager @Autowired private EntityManager entityManager; private TeamMember buildTeamMember(Long companyId, AddTeamMemberRequest addTeamMemberRequest) { // 获取代理对象,而非实际加载实体 Company company = entityManager.getReference(Company.class, companyId); // 若使用Spring Data JPA 2.x+,也可使用companyRepository.getById(companyId) TeamMember teamMember = new TeamMember(); teamMember.setCompany(company); // Other setters return teamMember; }
方法三:移除不必要的级联配置
移除之前添加的CascadeType.MERGE/CascadeType.ALL,因为Company是已存在的实体,不需要级联任何操作。仅保留@ManyToOne和外键配置即可:
@ManyToOne @JoinColumn(name = COLUMN_COMPANY_ID_FK, referencedColumnName = Constants.ID, nullable = false) private Company company;
内容的提问来源于stack exchange,提问作者Karan Mehta
相关产品推荐
相关产品推荐

