问题描述
我正在尝试扩展这个 Baeldung 教程 https://www.baeldung.com/rest-api-search-language-spring-data-specifications
但我希望规范是通用的,并且我希望允许客户端按嵌入对象的值进行搜索。一切都适用于字符串和一些数字,但不适用于 id 和其他更复杂的对象,如 Date
。
我的模型:(假设一个人只能有 1 只宠物)
@Entity
public Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
private String name;
private Date dateOfBirth
private Integer age;
private Pet pet;
// Getter & Setters etc
}
@Entity
public Pet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
private String type;
private String name;
private Integer numOfLegs;
// Getter & Setters etc
}
个人仓库:
@Repository
public interface PersonRepository extends JpaRepository<Person,Integer>,JpaSpecificationExecutor<Person>{}
public class EntitySearchCriteria {
private String key;
private String operation;
private Object value;
public EntitySearchCriteria(final String key,final String operation,final Object value) {
this.key = key;
this.operation = operation;
this.value = value;
}
// Getters and Setters etc
我的通用规范类(这实际上是构建要使用的谓词的地方)。这也允许客户端在连接表的值上设置 SearchCriteria。例如“宠物名称=松饼”
public abstract class AbstractEntitySpecification<T,ID extends Serializable> implements Specification<T> {
protected EntitySearchCriteria criteria;
@Override
public Predicate toPredicate(Root<T> root,CriteriaQuery<?> query,CriteriaBuilder criteriaBuilder) {
if (criteria.getoperation().equalsIgnoreCase(">")) {
return criteriaBuilder.greaterThanorEqualTo(root.<String>get(criteria.getKey()),criteria.getValue().toString());
} else if (criteria.getoperation().equalsIgnoreCase("<")) {
return criteriaBuilder.lessthanorEqualTo(root.<String>get(criteria.getKey()),criteria.getValue().toString());
} else if (criteria.getoperation().equalsIgnoreCase(":")) {
if (criteria.getKey().contains(".")) {
String[] joinCriteriaArray = criteria.getKey().split("\\.");
Class<?> joinedClass = root.get(joinCriteriaArray[0]).getClass();
Join<T,?> joinedRelationship = root.join(joinCriteriaArray[0]);
return criteriaBuilder.equal(joinedRelationship.get(joinCriteriaArray[1]),criteria.getValue());
}
if (root.get(criteria.getKey()).getJavaType() == String.class) {
return criteriaBuilder.like(root.<String>get(criteria.getKey()),"%" + criteria.getValue() + "%");
} else {
return criteriaBuilder.equal(root.get(criteria.getKey()),criteria.getValue());
}
}
return null;
}
}
我想要允许这种类型的查询的任何实体只需要具体实现 AbstractEntitySpecification
public class PersonSpecification extends AbstractEntitySpecification<Person,Integer> {
public PersonSpecification (final EntitySearchCriteria entitySearchCriteria) {
this.criteria = entitySearchCriteria;
}
}
这些是我运行的测试。对字符串或 Int 的 Person 属性(即 Person.name、Person.age)的任何搜索都有效,但对 dateOfBirth 的搜索无效。 任何对作为字符串的宠物的属性的搜索都可以使用连接进行,但对 id(Integer) 的搜索不会,无论我是将 id 作为 Int 还是 String 传递。我已将行为放在每个测试的评论中。
public class PersonSpecificationMediumTest extends AbstractMediumTest {
@Autowired
private PersonRepository personRepository;
@Autowired
private PetRepository petRepository;
Person person1;
Person person2;
@Before
public void setUp() {
Pet muffins = new Pet(1,"cat","muffins",4);
Pet rex= new Pet(2,"dog","rex",4);
petRepository.saveAll(Arrays.asList(muffins,rex));
person1 = new Person();
person1.setName("David");
person1.setDateOfBirth(Date.parse("1979-03-01");
person1.setPet(muffins);
person1 = personRepository.saveAndFlush(person1);
person2 = new Person();
person2.setName("Mary");
person2.setDateOfBirth(Date.parse("1982-03-01");
person2.setPet(rex);
person2 = personRepository.saveAndFlush(person2);
}
@Test //Works
public void toPredicate_findByNameEquals_assertCorrectResult() {
PersonSpecification spec
= new PersonSpecification(new EntitySearchCriteria("name",":","David"));
List<Person> results = personRepository.findAll(spec);
Assert.assertEquals(person1,results.get(0));
}
@Test // Works
public void toPredicate_findByPetNameEquals_assertCorrectResult() {
PersonSpecification spec
= new PersonSpecification(new EntitySearchCriteria("client.name","Rex"));
List<Person> results = personRepository.findAll(spec);
Assert.assertEquals(person2,results.get(0));
}
@Test // Return empty list. Cannot find the pet by Id.
public void toPredicate_findByPetIdEquals_assertCorrectResult() {
PersonSpecification spec
= new PersonSpecification(new EntitySearchCriteria("pet.id",2));
List<Person> results = personRepository.findAll(spec);
Assert.assertEquals(person2,results.get(0));
}
@Test // org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [2] did not match expected type [java.lang.Integer (n/a)];
public void toPredicate_findByPetIdAsstringEquals_assertCorrectResult() {
PersonSpecification spec
= new PersonSpecification(new EntitySearchCriteria("pet.id","2"));
List<Person> results = personRepository.findAll(spec);
Assert.assertEquals(person2,results.get(0));
}
@Test // Fails on org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [2020-01-01] did not match expected type [java.util.Date (n/a)]
public void toPredicate_findByDateOfBirthBetween_assertCorrectResult() {
PersonSpecification spec1
= new PersonSpecification(new EntitySearchCriteria("dateOfBirth","<","1990-01-01"));
PersonSpecification spec2
= new PersonSpecification(new EntitySearchCriteria("dateOfBirth",">","1970-01-01"));
List<Person> results = personRepository.findAll(spec1.and(spec2));
Assert.assertTrue(results.size() == 2);
}
}
知道为什么 Date 如此成问题吗?我想在 greaterThanorEqualTo
和 lessthanorEqualTo
中使用日期,但是传入 criteria.getValue(Object) 会产生编译错误,因此它迫使我使用对象的字符串表示形式。但是显示的错误是 org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [2020-01-01] did not match expected type [java.util.Date (n/a)]
,它向我表明它无法将 String 与 Date 进行比较,这是有道理的,但为什么要阻止我传递 Date 对象?
另外,为什么 Id 在连接表上会出现这样的问题?为什么它找不到id = 2
,我还以为会直截了当,尤其是我可以通过Pets的腿数成功搜索。它必须与 id 可序列化有关。
解决方法
查看JavaDoc for Date.parse
。基本部分已经包含声明:
@已弃用
public static long parse(String s)
正如明确说明的那样,它返回一个 long
值。要获取 Date
对象,您可以使用 SimpleDateFormat
that inherits DateFormat.parse(String s)
,例如:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d1 = sdf.parse("1979-03-01");