我想在Java中使用Criteria API进行动态搜索。
在我编写的代码中,我们需要用 JSON 编写 url 栏中的每个实体。我不想写"普拉卡"。
网址 :> 我只想使用"city"或"plaka">
在这里,我们需要编写每个实体,即使我们只使用 1 个实体进行搜索。键入"实体",它应为空。
我的代码如下。假设有多个实体,我想做的是使用它要搜索的单个实体进行搜索。正如你在照片中看到的,我不想写一个我不需要的实体。你能帮我做什么吗?
我在存储库中的代码
public interface CityRepository extends JpaRepository<City, Integer> , JpaSpecificationExecutor<City> {
}
我的服务中的代码
@Service
public class CityServiceImp implements CityService{
private static final String CITY = "city";
private static final String PLAKA = "plaka";
@Override
public List<City> findCityByNameAndPlaka(String cityName, int plaka) {
GenericSpecification genericSpecification = new GenericSpecification<City>();
if (!cityName.equals("_"))
genericSpecification.add(new SearchCriteria(CITY,cityName, SearchOperation.EQUAL));
if (plaka != -1)
genericSpecification.add(new SearchCriteria(PLAKA,plaka, SearchOperation.EQUAL));
return cityDao.findAll(genericSpecification);
}
@Autowired
CityRepository cityDao;
我在控制器中的代码
@RestController
@RequestMapping("api/city")
public class CityController {
@Autowired
private final CityService cityService;
public CityController(CityService cityService) {
this.cityService = cityService;
@GetMapping("/query")
public List<City> query(@RequestParam String city, @RequestParam String plaka){
String c = city;
int p;
if (city.length() == 0)
c = "_";
if (plaka.length() == 0) {
p = -1;
}
else
p = Integer.parseInt(plaka);
return cityService.findCityByNameAndPlaka(c,p);
}
我在搜索条件中的代码
public class SearchCriteria {
private String key;
private Object value;
private SearchOperation operation;
public SearchCriteria(String key, Object value, SearchOperation operation) {
this.key = key;
this.value = value;
this.operation = operation;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
public SearchOperation getOperation() {
return operation;
}
我在通用规范中的代码
public class GenericSpecification<T> implements Specification<T> {
private List<SearchCriteria> list;
public GenericSpecification() {
this.list = new ArrayList<>();
}
public void add(SearchCriteria criteria){
list.add(criteria);
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
for (SearchCriteria criteria : list) {
if (criteria.getOperation().equals(SearchOperation.GREATER_THAN)) {
predicates.add(builder.greaterThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.LESS_THAN)) {
predicates.add(builder.lessThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.GREATER_THAN_EQUAL)) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.LESS_THAN_EQUAL)) {
predicates.add(builder.lessThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.NOT_EQUAL)) {
predicates.add(builder.notEqual(
root.get(criteria.getKey()), criteria.getValue()));
} else if (criteria.getOperation().equals(SearchOperation.EQUAL)) {
predicates.add(builder.equal(
root.get(criteria.getKey()), criteria.getValue()));
} else if (criteria.getOperation().equals(SearchOperation.MATCH)) {
predicates.add(builder.like(
builder.lower(root.get(criteria.getKey())),
"%" + criteria.getValue().toString().toLowerCase() + "%"));
} else if (criteria.getOperation().equals(SearchOperation.MATCH_END)) {
predicates.add(builder.like(
builder.lower(root.get(criteria.getKey())),
criteria.getValue().toString().toLowerCase() + "%"));
}
}
return builder.and(predicates.toArray(new Predicate[0]));
}
我在搜索操作中的代码
public enum SearchOperation {
GREATER_THAN,
LESS_THAN,
GREATER_THAN_EQUAL,
LESS_THAN_EQUAL,
NOT_EQUAL,
EQUAL,
MATCH,
MATCH_END,
}
CriteriaAPI 的好处是,您可以使用 CriteriaBuilder 根据您拥有的字段构建复杂的 SQL 语句。您可以使用and
语句轻松组合多个条件字段or
语句。
我过去处理类似事情的方式是使用一个GenericDao
类,该类采用具有最常见操作(等于,qualsIgnoreCase,lessThan,greaterThan等)的构建器Filter
。实际上,在我开始的一个开源项目中,我也有类似的东西:https://gitlab.com/pazvanti/logaritmical/-/blob/master/app/data/dao/GenericDao.java https://gitlab.com/pazvanti/logaritmical/-/blob/master/app/data/filter/JPAFilter.java
接下来,隐式 DAO 类扩展了这个GenericDao
,当我想做一个操作(例如:找到一个具有提供的用户名的用户)并在那里创建一个Filter
.
现在,魔力就在过滤器中。这是创建Predicate
的那个。 在您的请求中,您将收到类似这样的内容:field1=something&field2=somethingElse等等。该值前面可以加上"<"或">",如果你想要更小或更大,并且你用这些值初始化你的过滤器。如果可以将参数检索为Map<String,String>,那就更好了。
现在,对于请求中的每个字段,您可以使用JPAFilter
类中的帮助程序方法创建一个谓词,并返回 他 结果Predicate
.在下面的示例中,我假设您没有将其作为 Map,而是作为单独的字段(很容易调整 Map 的代码):
public class SearchFilter extends JPAFilter {
private Optional<String> field1 = Optional.empty();
private Optional<String> field2 = Optional.empty();
@Override
public Predicate getPredicate(CriteriaBuilder criteriaBuilder, Root root) {
Predicate predicateField1 = field1.map(f -> equals(criteriaBuilder, root, "field1", f)).orElse(null);
Predicate predicateField2 = field2.map(f -> equals(criteriaBuilder, root, "field2", f)).orElse(null);
return andPredicateBuilder(criteriaBuilder, predicateField1, predicateField2);
}
}
现在,我将字段设置为可选,因为在这种情况下,我假设您在请求映射中将它们作为可选(Spring 有这个),我知道将 Optional 作为输入参数有点争议,但在这种情况下我认为这是可以接受的(更多关于这里:https://petrepopescu.tech/2021/10/an-argument-for-using-optional-as-input-parameters/)
制作andPredicateBuilder()
的方式是,即使提供的谓词之一为 null,它也能正常工作。另外,我制作了一个简单的映射功能,调整为包括<
和>
。
现在,在你的 DAO 类中,只需提供适当的过滤器:
public class SearchDao extends GenericDAO {
public List<MyEntity> search(Filter filter) {
return get(filter);
}
}
需要进行一些调整(这只是起始代码),例如更简单的方法来初始化过滤器(并在DAO中执行此操作),并确保过滤器只能通过应用于指定的实体(可能使用泛型,JPAFIlter<T>
并具有SearchFilter extends JPAFilter<MyEntity>
)。此外,还可以添加一些错误处理。
一个缺点是字段必须与实体类中的变量名称匹配。