You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Stream.map()替换列表对象时抛出非唯一查询结果异常排查

问题背景

现有一个元素列表,业务需求如下:

  • 若根据列表元素的name字段可在数据库中查询到对应记录,则使用查询到的记录构造新对象替换列表中原有元素
  • 若未查询到匹配记录,则保留列表中原元素不做修改

实现逻辑使用Java Stream编写,示例代码如下:

result = list.stream().map(element -> {
    Optional<Object> dbRecord = repository.findByName(element.getName());

    dbRecord.ifPresent(
            x -> {
                x.setSmth(element.getSmth());
                x.setSmth1(element.getSmth1());
                // 其余属性赋值逻辑
                ...
            }
    );

    return dbRecord.orElse(element);

}).collect(Collectors.toList());
异常现象

调试时观察到所有步骤中repository.findByName()方法均返回Optional.empty(),但执行到return语句返回原element元素时,捕获到如下异常:

org.springframework.dao.IncorrectResultSizeDataAccessException: query did not return a unique result: 2; nested exception is javax.persistence.NonUniqueResultException: query did not return a unique result: 2
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:385) ~[spring-orm-5.3.14.jar:5.3.14]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235) ~[spring-orm-5.3.14.jar:5.3.14]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551) ~[spring-orm-5.3.14.jar:5.3.14]
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.3.14.jar:5.3.14]
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-5.3.14.jar:5.3.14]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) ~[spring-tx-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:145) ~[spring-data-jpa-2.6.0.jar:2.6.0]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.14.jar:5.3.14]
    at com.sun.proxy.$Proxy186.findByName(Unknown Source) ~[?:?]
    at ru.app.MyService.lambda$processRequest$4(MyService.java:166) ~[my-application-2.0.0.jar:?]
根因定位

1. 异常抛出点判断错误

异常根本不是在return dbRecord.orElse(element)这行抛出的,看异常栈最上层业务代码调用点:at ru.app.MyService.lambda$processRequest$4(MyService.java:166),这行就是调用repository.findByName(element.getName())的位置。

Spring Data JPA的规则是:返回单个实体/Optional类型的查询方法,只要匹配到的记录数不等于1,就会按如下逻辑处理:匹配0条时正常返回Optional.empty();匹配数大于1时直接抛出IncorrectResultSizeDataAccessException,根本不会构造返回值。

调试时看到所有findByName都返回Optional.empty()是调试器的干扰:IDE调试时默认会自动对断点行上的方法做求值预览,这个预览执行的查询和流实际执行的查询是隔离的,看到的empty是预览求值的结果,不是流实际运行时的结果。实际流执行到某个name对应2条数据库记录的元素时,findByName方法内部直接抛异常,根本走不到后续的ifPresent和return逻辑。

2. Stream.map运行机制理解偏差

Stream.map()是中间操作,调用它的时候不会执行任何传入的lambda逻辑,只会构造一个新的流节点挂在流管道上。只有当最终操作(这里是collect(Collectors.toList()))触发时,才会逐个遍历流中的元素,按顺序应用map里的lambda逻辑:

  • 只要某一个元素的lambda执行抛出异常,整个流操作就会立即终止,异常向上抛出
  • 不存在“所有findByName都执行完返回empty,再走到return逻辑”的执行顺序,元素是逐个处理的,处理完一个才会处理下一个

之前误以为所有findByName都执行完了才到return步骤,是对Stream懒加载特性不熟悉导致的判断偏差。

修复方向
  • 首先排查数据库中重复的name数据,确认findByName的查询逻辑是否应该允许返回多条记录,如果允许,把Repository方法返回值改成List<Object>,自行取第一条处理即可
  • 如果业务上要求name唯一,先清理脏数据,给name字段加唯一索引避免后续出现重复值

内容的提问来源于stack exchange,提问作者Anton Chaparin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:39:44