Spring Data JPA - 命名查询忽略空参数
Posted
技术标签:
【中文标题】Spring Data JPA - 命名查询忽略空参数【英文标题】:Spring Data JPA - Named query ignoring null parameters 【发布时间】: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<Long>
作为可选请求参数(底层服务也使用相同的参数):
ResponseEntity<List<Entity>> entities(Optional<Long> firstId, Optional<Long> secondId);
我想根据参数firstId
和secondId
过滤实体,这两个参数在数据库中永远不是null
s,但可以通过构造函数传递(用于搜索的参数是可选的)。
当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 个参数实现完全相同的过滤。
【问题讨论】:
【参考方案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.0
和 Specification.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。
简而言之,这个想法如下。为每个属性定义一个规范。然后检查搜索条件中的每个属性,如果它不为空,则将相应的规范添加到“连接”规范中。然后使用这个“串联”规范进行搜索。
【讨论】:
以上是关于Spring Data JPA - 命名查询忽略空参数的主要内容,如果未能解决你的问题,请参考以下文章
spring data JPA忽略查询方法上的fetchmode