Spring JPA中getById方法返回null、获取Hibernate代理对象问题排查
问题根因分析
getById方法的代理特性
Spring Data JPA 中getById底层调用Hibernate的load方法,默认返回延迟加载代理对象,不会立即执行数据库查询。只有访问代理对象的非主键属性时才会触发查库操作,如果对应ID的记录不存在,此时才会抛出异常或返回空值,debug时看到的ExerciseEntity$HibernateProxy$xxx就是代理对象而非真实实体,很容易被误认为是null。- ExerciseEntity主键缺少生成策略
当前ExerciseEntity的@Id注解下没有配置主键生成策略,如果插入ExerciseEntity数据时没有手动指定ID值,数据库中对应记录的主键为空或不符合预期,查询时自然匹配不到对应记录。 - 双向关联维护缺失(非直接报错原因,但会引发后续缓存不一致问题)
配置的是双向多对多关联,当前仅给PersonalTrainingProgram设置了关联的运动实体集合,没有同步给ExerciseEntity的关联集合赋值,后续查询可能出现关联数据缺失的问题。
修复方案
- 替换
getById为findById,主动判空findById会立即触发数据库查询,返回真实实体对象,不存在的话返回空Optional,方便提前排查无效ID:
@Override public List<ExerciseEntity> findExercisesBySpecificId(List<ExerciseDto> exerciseDto) { List<ExerciseEntity> listEntity= new ArrayList<>(); for (ExerciseDto dto : exerciseDto) { Optional<ExerciseEntity> exerciseOpt = exercisesRepository.findById(dto.getId()); exerciseOpt.ifPresent(listEntity::add); // 可在这里添加日志或者异常逻辑,排查无效ID if (exerciseOpt.isEmpty()) { System.out.printf("ID为%d的运动记录不存在%n", dto.getId()); } } return listEntity; }
- 给ExerciseEntity添加主键生成策略
如果使用数据库自增主键,补充@GeneratedValue注解:
@Entity @Table(name = "exercises") public class ExerciseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 其余属性保持不变 }
- 同步维护双向关联关系
保存训练计划时同步更新运动实体的关联集合:
@Override public void addItToDatabase(List<ExerciseDto> exerciseDto, FoodProgramDto foodProgramDto,String username) { PersonalTrainingProgram personalTrainingProgram = new PersonalTrainingProgram(); List<ExerciseEntity> exercises = findExercisesBySpecificId(exerciseDto); personalTrainingProgram.setExerciseEntityList(exercises); // 同步维护ExerciseEntity端的关联 exercises.forEach(exercise -> exercise.getPersonalTrainingPrograms().add(personalTrainingProgram)); personalTrainingProgram.setFoodProgram(foodProgramService.findFoodProgramById(foodProgramDto)); personalTrainingProgram.setAppUser(appUserRepository.findByUsername(username).get()); personalTrainingRepository.save(personalTrainingProgram); }
内容的提问来源于stack exchange,提问作者George Marizov
相关产品推荐
相关产品推荐

