Spring使用QueryDSL自定义Repository更新测试失败如何解决
问题描述
我正在尝试使用集成QueryDSL的自定义Repository测试更新操作,但测试运行失败,数据未被更新。我尝试在调用Repository方法后执行flush操作,依旧没有生效。我刚接触Spring技术栈,推测该问题与EntityManager相关,但无法定位具体原因,相关代码与配置如下:
仓库代码
@Override public Long updateNotice(String noticeId, Notice noticeInfo) { UpdateClause<JPAUpdateClause> updateBuilder = queryFactory.update(notice); if(StringUtils.isNotEmpty(noticeInfo.getTitle())) { updateBuilder.set(notice.title, noticeInfo.getTitle()); } if(StringUtils.isNotEmpty(noticeInfo.getContents())) { updateBuilder.set(notice.contents, noticeInfo.getContents()); } if(StringUtils.isNotEmpty(noticeInfo.getTheme())) { updateBuilder.set(notice.theme, noticeInfo.getTheme()); } if (StringUtils.isNotEmpty(Integer.toString(noticeInfo.getState()))) { updateBuilder.set(notice.state, noticeInfo.getState()); } if(StringUtils.isNotEmpty(noticeInfo.getLocation())) { updateBuilder.set(notice.location, noticeInfo.getLocation()); } updateBuilder.set(notice.startDate, noticeInfo.getStartDate()); updateBuilder.set(notice.endDate, noticeInfo.getEndDate()); updateBuilder.set(notice.modiDate, LocalDateTime.now()); Long count = updateBuilder .where(notice.noticeId.eq(noticeId)) .execute(); return count; }
测试代码
@Test public void updateNoticeTest() { //given Notice titleNotice = Notice.builder() .title("Update notice") .startDate(LocalDateTime.of(2021, 10, 11, 00, 00, 00)) .endDate(LocalDateTime.of(2021, 12, 12, 00, 00, 00)) .build(); List<Notice> notices = noticeRepository.findAll(); String id = notices.get(1).getNoticeId(); Notice notice = notices.get(1); log.info(notice.toString()); //when Long result = noticeRepository.updateNotice(id, titleNotice); noticeRepository.flush(); //then Notice res = noticeRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("No such notice with id " + id)); log.info(res.toString()); assertThat(res.getTitle()).isEqualTo("Update notice"); }
测试配置
# Datasource settings spring.datasource.url=jdbc:h2:mem:testdb;Mode=Oracle;DB_CLOSE_DELAY=-1 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password= # set jpa spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.properties.hibernate.show-sql=true spring.jpa.properties.hibernate.format_sql=true #logging.level.org.hibernate.type.descriptor.sql=trace # set log logging.level.root=info # set page option # 1-based spring.data.web.pageable.one-indexed-parameters=true # page size = 10 spring.data.web.pageable.default-page-size=10 spring.data.web.pageable.max-page-size=1000
修复方案
核心问题1:缺少事务配置
QueryDSL的JPAUpdateClause.execute()更新操作必须在事务环境下运行,你的Repository更新方法和测试方法都没有加事务注解,更新操作不会提交到数据库,flush操作也不会生效。
此外Spring Data JPA测试默认开启事务自动回滚,添加事务注解后如果还是看不到更新结果,需要在测试方法上加@Rollback(value = false)关闭自动回滚。
核心问题2:状态判断逻辑存在空指针风险
StringUtils.isNotEmpty(Integer.toString(noticeInfo.getState()))逻辑有问题:如果noticeInfo的state为null,调用Integer.toString()会直接抛出空指针异常,即使当前测试用例未触发该问题,生产环境也会出现故障。
核心问题3:一级缓存导致查询到旧数据
JPA EntityManager默认开启一级缓存,你在测试开头调用findAll()已经把对应notice实体加载到了缓存中,后续调用findById()会直接从缓存读取旧数据,不会查询数据库最新值,即使更新操作成功也会拿到更新前的结果。
具体修改步骤
- 给Repository的
updateNotice方法添加@Transactional注解,确保更新在事务内执行:
@Override @Transactional public Long updateNotice(String noticeId, Notice noticeInfo) { // 原有逻辑保持不变 }
- 修正state字段的判断逻辑:
// 替换原有判断 if (noticeInfo.getState() != null) { updateBuilder.set(notice.state, noticeInfo.getState()); }
- 调整测试代码:
- 给测试方法添加
@Transactional和@Rollback(false)注解 - 在调用
findById之前调用entityManager.clear()清空一级缓存,确保查询到数据库最新数据 - 建议打印
result返回值确认更新影响行数,如果返回值为0说明where条件未匹配到数据,需要检查传入的noticeId是否正确
- 可以打开配置文件中注释的SQL参数日志,确认更新语句的入参是否符合预期:
logging.level.org.hibernate.type.descriptor.sql=trace
内容的提问来源于stack exchange,提问作者jhlee
相关产品推荐
相关产品推荐

