JPA多对多实体持久化问题:关联缺失与性能优化诉求
JPA双向多对多关联的CRUD问题与优化
实体映射代码
WorkflowTaskEntity 实体
@Entity @Table(name = "workflow_tasks") @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class WorkflowTaskEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(length = 30) private String workflowTaskId; @Column(unique = true) private String name; @ManyToMany( cascade = CascadeType.ALL, fetch = FetchType.EAGER ) @JoinTable( name="workflow_tasks_workflow_tasks_settings", joinColumns = @JoinColumn(name = "workflow_task_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "setting_id", referencedColumnName = "id") ) private Set<WorkflowTaskSettingEntity> settings; ... }
WorkflowTaskSettingEntity 实体
@Entity @Table(name = "workflow_task_settings") @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class WorkflowTaskSettingEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(length = 30) private String workflowTaskSettingId; @Column(unique = true) private String name; @ManyToMany( cascade = CascadeType.ALL, mappedBy = "settings", fetch = FetchType.EAGER ) private Set<WorkflowTaskEntity> workflowTasks; }
仓库接口实现
public interface WorkflowTaskSettingRepository extends CrudRepository<WorkflowTaskSettingEntity, Long> { Page<WorkflowTaskSettingEntity> findAll(Pageable pageRequest); List<WorkflowTaskSettingEntity> findAll(); WorkflowTaskSettingEntity findByWorkflowTaskSettingId(String workflowTaskSettingId); WorkflowTaskSettingEntity findByValue(String name); @Modifying @Transactional @Query(value = "DELETE FROM WorkflowTaskSettingEntity c WHERE c.id = :id") void deleteById(@Param("id") Long id); }
需处理的业务场景
- 一次性创建
WorkflowTaskSettingEntity和WorkflowTaskEntity对象 - 创建
WorkflowTaskSettingEntity并关联多个WorkflowTaskEntity对象 - 删除两者间的关联关系
- 删除
WorkflowTaskSettingEntity及其关联 - 删除
WorkflowTaskEntity及其关联
遇到的问题
问题一:关联关系持久化异常
先创建4个WorkflowTaskEntity实例,再尝试持久化10个关联这4个实例的WorkflowTaskSettingEntity,仅第一个WorkflowTaskSettingEntity的关联正常,后续实例的关联结果不符合预期。
问题二:性能与代码冗余
基础查询和插入操作生成过多SQL语句,存在分离实体问题,单个对象需要两次持久化,疑似实体映射配置不合理。
创建逻辑代码
@Override public WorkflowTaskSettingDto create(WorkflowTaskSettingDto workflowTaskSettingDto) { Set<WorkflowTaskDto> workflowTaskDtos = workflowTaskSettingDto.getWorkflowTasks(); WorkflowTaskSettingEntity workflowTaskSettingEntity = modelMapper.map( workflowTaskSettingDto, WorkflowTaskSettingEntity.class); workflowTaskSettingEntity.setWorkflowTasks(null); workflowTaskSettingEntity = repository.save(workflowTaskSettingEntity); Set<WorkflowTaskEntity> workflowTaskEntities = getWorkflowTasks(workflowTaskDtos); workflowTaskSettingEntity.addWorkflowTasks(workflowTaskEntities); workflowTaskSettingEntity = repository.save(workflowTaskSettingEntity); return modelMapper.map(workflowTaskSettingEntity, WorkflowTaskSettingDto.class); } private Set<WorkflowTaskEntity> getWorkflowTasks(Set<WorkflowTaskDto> workflowTaskDtos) { Set<WorkflowTaskEntity> workflowTaskEntities = new HashSet<>(); workflowTaskDtos.forEach(workflowTaskDto -> { WorkflowTaskEntity workflowTaskEntity = appService.workflowTaskService().getById(workflowTaskDto.getWorkflowTaskId()); if (workflowTaskEntity == null) { WorkflowTaskDto create = appService.workflowTaskService().create(workflowTaskDto); workflowTaskEntity = appService.workflowTaskService().getById(create.getWorkflowTaskId()); } workflowTaskEntity.setSettings(workflowTaskEntity.getSettings()); workflowTaskEntities.add(workflowTaskEntity); }); return workflowTaskEntities; }
解决方案
1. 修复关联关系持久化问题
双向多对多中,仅拥有方(WorkflowTaskEntity,因它定义了@JoinTable)的变更会同步到关联表。当前代码操作反方WorkflowTaskSettingEntity的workflowTasks集合,无法正确更新关联表。
修改关联维护逻辑:
- 在
WorkflowTaskSettingEntity中添加双向关联维护方法:
public void addWorkflowTasks(Set<WorkflowTaskEntity> tasks) { this.workflowTasks.addAll(tasks); tasks.forEach(task -> { if (task.getSettings() == null) { task.setSettings(new HashSet<>()); } task.getSettings().add(this); }); }
- 移除创建逻辑中两次保存的冗余操作,直接构建完整关联后一次性保存。
2. 优化性能与实体映射
- 调整级联策略:
CascadeType.ALL过于激进,若无需级联删除/更新,可改为CascadeType.PERSIST+CascadeType.MERGE,避免意外删除关联实体。 - 修改Fetch类型:
FetchType.EAGER会引发N+1查询问题,建议改为FetchType.LAZY,在需要时通过JOIN FETCH主动加载关联集合。 - 优化创建逻辑:避免两次保存,直接构建完整关联后一次性持久化:
@Override public WorkflowTaskSettingDto create(WorkflowTaskSettingDto workflowTaskSettingDto) { Set<WorkflowTaskDto> workflowTaskDtos = workflowTaskSettingDto.getWorkflowTasks(); WorkflowTaskSettingEntity settingEntity = modelMapper.map(workflowTaskSettingDto, WorkflowTaskSettingEntity.class); Set<WorkflowTaskEntity> taskEntities = getWorkflowTasks(workflowTaskDtos); settingEntity.addWorkflowTasks(taskEntities); settingEntity = repository.save(settingEntity); return modelMapper.map(settingEntity, WorkflowTaskSettingDto.class); }
- 批量操作优化:批量创建场景使用
saveAll代替循环save,减少SQL执行次数。
3. 关联关系删除逻辑
- 删除关联关系时,需从拥有方的集合中移除元素:
public void removeTaskFromSetting(Long settingId, Long taskId) { WorkflowTaskSettingEntity setting = repository.findById(settingId).orElseThrow(); WorkflowTaskEntity task = taskRepository.findById(taskId).orElseThrow(); task.getSettings().remove(setting); setting.getWorkflowTasks().remove(task); taskRepository.save(task); // 保存拥有方,更新关联表 }
- 删除实体及其关联:若使用
CascadeType.REMOVE,删除拥有方时会自动清理关联表记录;若需手动清理,可在删除前清空关联集合。
内容的提问来源于stack exchange,提问作者davidvera
相关产品推荐
相关产品推荐

