Spring 数据 JPA 和可以为 null 的参数
Spring data JPA and parameters that can be null
我的理解是,对于 Spring 数据 JPA,我无法使用查询方法来获取列等于给定非空方法参数的所有行,也无法使用相同的方法来获取此列的所有行当方法参数为空时,列为空。
对吗?
所以我必须在我的 JAVA 代码中区分这一点,并且我必须使用单独的查询方法明确要求空值,如下例所示?
// Query methods
List<Something> findByParameter(Parameter parameter);
List<Something> findByParameterIsNull();
...
List<Something> result = new ArrayList<>();
if (parameter == null)
result = findByParameterIsNull();
else
result = findByParameter(parameter);
太糟糕了,如果我有 4 个可能为 null 的参数并且必须编写 16 种不同的查询方法。
你是对的。
已请求支持更好地处理空参数。
https://jira.spring.io/browse/DATAJPA-121
对于你的情况,我建议你编写存储库实现并使用自定义 CriteriaQuery 来处理你的情况。
您也可以使用 @Query annotation with the is null 语法:
@Query("[...] where :parameter is null"
public List<Something> getSomethingWithNullParameter();
编辑
自Spring data jpa 2.0,spring现在支持@Nullable注解。这有助于处理传递的空参数。
来自documentation :
@Nullable – to be used on a parameter or return value that can be null.
看来Query by Example可能是你需要的。
示例查询是 Spring 数据(since version Hopper,2016 年 4 月发布)中的一项新功能,它允许使用这样的代码创建简单的动态查询
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
personRepository.count(example);
personRepository.findOne(example);
personRepository.findAll(example);
方法 count/findOne/findAll
将 org.springframework.data.domain.Example
的实例作为参数(其中一些还采用 sorting/pagination 参数)来自 org.springframework.data.repository.query.QueryByExampleExecutor<T>
接口,即通过 org.springframework.data.jpa.repository.JpaRepository<T, ID extends Serializable>
接口扩展。
简而言之,所有 JpaRepository
个实例现在都有这些方法。
今天截至 2018 年 6 月,通过查看 https://jira.spring.io/browse/DATAJPA-121,如果您的参数为空,查询将自动形成 is null
。
我在我的项目中就是这么做的,确实如此:
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.0.7.RELEASE'
--
public interface AccountDao extends CrudRepository<T, ID> {
//this can accept null and it will become isNull
public List<MyAccount> findByEmail(String email);
}
如果参数为空:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email is null
如果参数不为空:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email=?
11:02:41.623 [qtp1507181879-72] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [testing@hotmail.com]
然后有一个有趣的问题,一些开发者希望更好地控制查询中的参数是否为空时忽略,这个还在研究中https://jira.spring.io/browse/DATAJPA-209。
我发现了一些东西......如果你像这样将参数放在 jpa 方法中
@Param("value") String value,
那么它可以为 null,在查询中您将遇到这种情况:
(table.value = :value OR :value IS NULL)
如果该值为空,它将自动 return 为真,如果不为空,它将在 table.
中搜索该值
在我的例子中,membershipNumber 是可以为 null 的,我是这样处理的。这也将处理 table.membershipNumber 为 null 的所有情况。
@Query(value = "SELECT pr FROM ABCTable pr " +
"WHERE LOWER(pr.xyz) = LOWER(:xyz) " +
"and LOWER(pr.subscriptionReference) = LOWER(:subscriptionReference) " +
"and pr.billId = :billId " +
"and ((pr.membershipNumber = :membershipId) or (pr.membershipNumber = null and :membershipId = null))")
List<PaymentRequest> getSomething (@Param("xyz") String xyz,
@Param("subscriptionReference") String subscriptionReference,
@Param("billId") Integer billId,
@Param("membershipId") String membershipNumber);
虽然这个问题已经得到回答并且被接受的答案与当前问题相关,但是还有另一种方法可以在 JpaRespository 中处理您的空参数。将其张贴在这里,因为当有人想通过忽略 null 字段并构建动态查询来进行查询时,可以利用这一点。
下面的代码示例应该展示相同的
public class User{
private String firstName;
private String lastName;
}
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long>{
public Page<AppUser> findAll(Specification<AppUser> user,Pageable page);
public default Page<AppUser> findAll(User user,Pageable page){
return findAll(search(user),page);
}
static Specification<User> search(User entity) {
return (root, cq, cb) -> {
//To ensure we start with a predicate
Predicate predicate = cb.isTrue(cb.literal(true));
if(entity.getFirstName() != null && !entity.getFirstName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("firstName")), "%"+entity.getFirstName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
if(entity.getLastName() != null && !entity.getLastName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("lastName")), "%"+entity.getLastName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
return predicate;
}
}
}
我能够使用以下解决方法在空输入的情况下适当应用 IS NULL
。
@Query("SELECT c FROM ConfigRLLOAContent c WHERE ((:suffixId IS NOT NULL AND c.suffixId = :suffixId) OR (:suffixId IS NULL AND c.suffixId IS NULL))")
Optional<ConfigRLLOAContent> findByRatableUnitId(@Param("suffixId") String suffixId);
以上方法仅在 suffixId
为非空时应用过滤器,
否则,将应用 IS NULL
过滤器。
在 github 上也提出了一个问题,建议引入 @NullMeans
here。
我在执行类似任务时遇到了同样的问题 - 查询中的一个参数是可选的,因此为了消除此错误,我设法使用以下带有 2 个转换的查询:
@Query(value = "select distinct name from table "
+ "where coalesce(cast(table.field_optional as text) = cast(?1 as text), true) "
+ "and lower(table.non_optional_field) like ?2 "
+ "limit ?3", nativeQuery = true)
List<String> method(String optionalParam, String param, int limit);
如果 optionalParam
为 null
,此合并部分将转换为简单的“true”
我的理解是,对于 Spring 数据 JPA,我无法使用查询方法来获取列等于给定非空方法参数的所有行,也无法使用相同的方法来获取此列的所有行当方法参数为空时,列为空。
对吗?
所以我必须在我的 JAVA 代码中区分这一点,并且我必须使用单独的查询方法明确要求空值,如下例所示?
// Query methods
List<Something> findByParameter(Parameter parameter);
List<Something> findByParameterIsNull();
...
List<Something> result = new ArrayList<>();
if (parameter == null)
result = findByParameterIsNull();
else
result = findByParameter(parameter);
太糟糕了,如果我有 4 个可能为 null 的参数并且必须编写 16 种不同的查询方法。
你是对的。
已请求支持更好地处理空参数。 https://jira.spring.io/browse/DATAJPA-121
对于你的情况,我建议你编写存储库实现并使用自定义 CriteriaQuery 来处理你的情况。
您也可以使用 @Query annotation with the is null 语法:
@Query("[...] where :parameter is null"
public List<Something> getSomethingWithNullParameter();
编辑
自Spring data jpa 2.0,spring现在支持@Nullable注解。这有助于处理传递的空参数。
来自documentation :
@Nullable – to be used on a parameter or return value that can be null.
看来Query by Example可能是你需要的。
示例查询是 Spring 数据(since version Hopper,2016 年 4 月发布)中的一项新功能,它允许使用这样的代码创建简单的动态查询
Person person = new Person();
person.setFirstname("Dave");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIncludeNullValues();
Example<Person> example = Example.of(person, matcher);
personRepository.count(example);
personRepository.findOne(example);
personRepository.findAll(example);
方法 count/findOne/findAll
将 org.springframework.data.domain.Example
的实例作为参数(其中一些还采用 sorting/pagination 参数)来自 org.springframework.data.repository.query.QueryByExampleExecutor<T>
接口,即通过 org.springframework.data.jpa.repository.JpaRepository<T, ID extends Serializable>
接口扩展。
简而言之,所有 JpaRepository
个实例现在都有这些方法。
今天截至 2018 年 6 月,通过查看 https://jira.spring.io/browse/DATAJPA-121,如果您的参数为空,查询将自动形成 is null
。
我在我的项目中就是这么做的,确实如此:
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.0.7.RELEASE'
--
public interface AccountDao extends CrudRepository<T, ID> {
//this can accept null and it will become isNull
public List<MyAccount> findByEmail(String email);
}
如果参数为空:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email is null
如果参数不为空:
select
myaccount0_.id as id1_0_,
myaccount0_.email as email2_0_,
myaccount0_.password as password3_0_,
myaccount0_.user_id as user_id4_0_
from
my_account myaccount0_
where
myaccount0_.email=?
11:02:41.623 [qtp1507181879-72] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [testing@hotmail.com]
然后有一个有趣的问题,一些开发者希望更好地控制查询中的参数是否为空时忽略,这个还在研究中https://jira.spring.io/browse/DATAJPA-209。
我发现了一些东西......如果你像这样将参数放在 jpa 方法中
@Param("value") String value,
那么它可以为 null,在查询中您将遇到这种情况:
(table.value = :value OR :value IS NULL)
如果该值为空,它将自动 return 为真,如果不为空,它将在 table.
中搜索该值在我的例子中,membershipNumber 是可以为 null 的,我是这样处理的。这也将处理 table.membershipNumber 为 null 的所有情况。
@Query(value = "SELECT pr FROM ABCTable pr " +
"WHERE LOWER(pr.xyz) = LOWER(:xyz) " +
"and LOWER(pr.subscriptionReference) = LOWER(:subscriptionReference) " +
"and pr.billId = :billId " +
"and ((pr.membershipNumber = :membershipId) or (pr.membershipNumber = null and :membershipId = null))")
List<PaymentRequest> getSomething (@Param("xyz") String xyz,
@Param("subscriptionReference") String subscriptionReference,
@Param("billId") Integer billId,
@Param("membershipId") String membershipNumber);
虽然这个问题已经得到回答并且被接受的答案与当前问题相关,但是还有另一种方法可以在 JpaRespository 中处理您的空参数。将其张贴在这里,因为当有人想通过忽略 null 字段并构建动态查询来进行查询时,可以利用这一点。 下面的代码示例应该展示相同的
public class User{
private String firstName;
private String lastName;
}
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User,Long>{
public Page<AppUser> findAll(Specification<AppUser> user,Pageable page);
public default Page<AppUser> findAll(User user,Pageable page){
return findAll(search(user),page);
}
static Specification<User> search(User entity) {
return (root, cq, cb) -> {
//To ensure we start with a predicate
Predicate predicate = cb.isTrue(cb.literal(true));
if(entity.getFirstName() != null && !entity.getFirstName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("firstName")), "%"+entity.getFirstName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
if(entity.getLastName() != null && !entity.getLastName().isBlank()) {
Predicate _predicate = cb.like(cb.lower(root.get("lastName")), "%"+entity.getLastName().toLowerCase()+"%");
predicate = cb.and(predicate,_predicate);
}
return predicate;
}
}
}
我能够使用以下解决方法在空输入的情况下适当应用 IS NULL
。
@Query("SELECT c FROM ConfigRLLOAContent c WHERE ((:suffixId IS NOT NULL AND c.suffixId = :suffixId) OR (:suffixId IS NULL AND c.suffixId IS NULL))")
Optional<ConfigRLLOAContent> findByRatableUnitId(@Param("suffixId") String suffixId);
以上方法仅在 suffixId
为非空时应用过滤器,
否则,将应用 IS NULL
过滤器。
在 github 上也提出了一个问题,建议引入 @NullMeans
here。
我在执行类似任务时遇到了同样的问题 - 查询中的一个参数是可选的,因此为了消除此错误,我设法使用以下带有 2 个转换的查询:
@Query(value = "select distinct name from table "
+ "where coalesce(cast(table.field_optional as text) = cast(?1 as text), true) "
+ "and lower(table.non_optional_field) like ?2 "
+ "limit ?3", nativeQuery = true)
List<String> method(String optionalParam, String param, int limit);
如果 optionalParam
为 null