【问题标题】:Spring Data JPA - Named query ignoring null parametersSpring Data JPA - 命名查询忽略空参数
【发布时间】:2020-05-31 06:42:19
【问题描述】:

我有以下存储库:

@Repository
public interface EntityRepository extends JpaRepository<Entity, Long> {

    List<Entity> findAllByFirstId(Long firstId);
    List<Entity> findAllBySecondId(Long secondId);
    List<Entity> findAllByFirstIdAndSecondId(Long firstId, Long secondId);
}

实现使用io.swagger:swagger-codegen-maven-plugin 生成的接口的构造函数使用Optional&lt;Long&gt; 作为可选请求参数(底层服务也使用相同的参数):

ResponseEntity<List<Entity>> entities(Optional<Long> firstId, Optional<Long> secondId);

我想根据参数firstIdsecondId 过滤实体,这两个参数在数据库中永远不是nulls,但可以通过构造函数传递(用于搜索的参数是可选的)。

null 作为可选参数传递时,命名查询出现问题,JpaReposotory 使用null 作为在数据库中搜索的标准。这就是我不想要的——我想忽略基于这个参数的过滤,只要它是null

我基于Optional 的解决方法是:

public List<Entity> entities(Optional<Long> firstId, Optional<Long> secondId) {

    return firstId
        .or(() -> secondId)
        .map(value -> {
            if (firstId.isEmpty()) {
                return entityRepository.findAllBySecondId(value);
            }
            if (secondId.isEmpty()) {
                return entityRepository.findAllByFirstId(value);
            }
            return entityRepository.findAllByFirstIdAndSecondId(
            firstId.get(), secondId.get());
        })
        .orElse(entityRepository.findAll())
        .stream()
        .map(...)     // Mapping between DTO and entity. For sake of brevity
                      // I used the same onject Entity for both controler and repository 
                      // as long as it not related to the question   

        .collect(Collectors.toList());
}

这个问题已经被问到了:Spring Data - ignore parameter if it has a null value 并且创建了一个票证DATAJPA-209

只要问题差不多 3 年了,而且票可以追溯到 2012 年,我想问是否存在更舒适和通用的方法来避免处理 Optional 和复制存储库方法的开销. 2 个这样的参数的解决方案看起来可以接受,但是我想对 4-5 个参数实现完全相同的过滤。

【问题讨论】:

    标签: java spring spring-boot spring-data-jpa optional


    【解决方案1】:

    你需要Specification这样的实用类

    public class EntitySpecifications {
        public static Specification<Entity> firstIdEquals(Optional<Long> firstId) {// or Long firstId. It is better to avoid Optional method parameters.
            return (root, query, builder) -> 
                firstId.isPresent() ? // or firstId != null if you use Long method parameter
                builder.equal(root.get("firstId"), firstId.get()) :
                builder.conjunction(); // to ignore this clause
        }
    
        public static Specification<Entity> secondIdEquals(Optional<Long> secondId) {
            return (root, query, builder) -> 
                secondId.isPresent() ? 
                builder.equal(root.get("secondId"), secondId.get()) :
                builder.conjunction(); // to ignore this clause
        }
    }
    

    那么你的EntityRepository 必须扩展JpaSpecificationExecutor

    @Repository
    public interface EntityRepository 
        extends JpaRepository<Entity, Long>, JpaSpecificationExecutor<Entity> {
    
    }
    

    用法:

    @Service
    public class EntityService {    
    
        @Autowired
        EntityRepository repository;
    
        public List<Entity> getEntities(Optional<Long> firstId, Optional<Long> secondId) {
            Specification<Entity> spec = 
                Specifications.where(EntitySpecifications.firstIdEquals(firstId)) //Spring Data JPA 2.0: use Specification.where
                              .and(EntitySpecifications.secondIdEquals(secondId));
    
            return repository.findAll(spec);        
        }
    }
    

    【讨论】:

    • Specifications 在 Spring Data JPA 中已弃用 2.0Specification.where(...)... 应改为使用。
    • @Nikolas 谢谢你的注意。 Spring data jpa 2.0 是相当新的。在我的项目中使用的是1.0版本
    【解决方案2】:

    io.swagger:swagger-codegen-maven-plugin 将它们生成为 Optional 因为我不需要它们(required: false by 默认)。我可能会将它们生成为盒装类型,例如Long,...

    这可能部分是品味问题。如果是我而且我可以,我会选择没有Optional 的版本。我不认为他们在这里贡献了任何有用的东西。

    public List<Entity> entities(Long firstId, Long secondId) {
    
        List<Dto> dtos;
        if (firstId == null) {
            if (secondId == null) {
                dtos = entityRepository.findAll();
            } else {
                dtos = entityRepository.findAllBySecondId(secondId);
            }
        } else {
            if (secondId == null) {
                dtos = entityRepository.findAllByFirstId(firstId);
            } else {
                dtos = entityRepository.findAllByFirstIdAndSecondId(firstId, secondId);
            }
        }
    
        return dtos.stream()
            .map(...)
            .collect(Collectors.toList());
    }
    

    Optional 类旨在用于可能不存在的返回值,而不是真正用于其他任何东西,所以我已经阅读了。我认为在极少数情况下我会将它们用于其他用途,但这不是其中之一。

    【讨论】:

      【解决方案3】:

      我建议您改用规范。请参阅文档和示例here

      简而言之,这个想法如下。为每个属性定义一个规范。然后检查搜索条件中的每个属性,如果它不为空,则将相应的规范添加到“连接”规范中。然后使用这个“串联”规范进行搜索。

      【讨论】:

        猜你喜欢
        • 2019-07-02
        • 2022-10-06
        • 2019-07-11
        • 2015-11-13
        • 2021-12-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-12
        相关资源
        最近更新 更多