Spring 中的动态查询和 JpaSpecificationExecutor
Dynamic queries and JpaSpecificationExecutor in Spring
我正在尝试创建一个简单的 Spring 项目,餐厅可以在其中将菜单项添加到共享数据库,用户可以使用 html 表单根据一系列标准搜索菜肴 - 特别是饮食要求
表格示例:
Restaurant Name: Chez Hans
Gluten Free: (X)
Egg Free: (X)
Vegan: ()
示例SQL命令
Select all FROM "dishes" Dish WHERE restaurant_name = "Chez Hans" AND egg_free = TRUE AND
gluten_Free = TRUE;
然后会将符合其标准的菜肴列表返回给用户。
表单中的任何字段都可以留空,并且不勾选一个框,例如,“素食主义者”并不意味着条件应设置为 'false',而是不包含在查询中。
因此,处理该问题的最佳方法似乎是使用 JpaSpecificationExecutor 创建动态 SQL 查询(类似于下面问题答案中的实现)
Filtering database rows with spring-data-jpa and spring-mvc
我根据我的研究和先验知识创建了一个解决方案。然而,当我实施我的解决方案时,没有返回任何菜品——即使数据库中有符合搜索条件的菜品。没有产生任何错误,只是一片空白 table,所以我不确定哪里出错了。
我研究了无数 articles/videos 关于 JpaSpecificationExecutor/dynamic 的查询,但我仍然不确定该理论的某些部分。这是我收集的有关 JpaSpecificationExecutor/dynamic 查询的信息(但我可能错了)
元模型不需要创建动态查询,而是验证数据库查询语句的正确性
要使用元模型创建查询 classes 需要包装器 class(在我的示例中 - DishSearch)
以下几行是将元模型 SingularAttribute class 转换回原始 class 值。
路径 dname = root.get(Dish_.dname);
路径 vegan= root.get(Dish_.vegan);
我是 Spring 的新手,但仍然觉得很难。非常感谢任何帮助或建议!
请看下面我的菜谱class:
package com.bron.demoJPA.specification;
public class DishSpecification implements Specification<Dish> {
private final DishSearch criteria;
public DishSpecification(DishSearch ds) {
criteria =ds;
}
@Override
public Predicate toPredicate(Root<Dish> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
Path<String> dname = root.get(Dish_.dname);
Path<Boolean> vegan= root.get(Dish_.vegan);
Path<Boolean> eggFree= root.get(Dish_.eggFree);
Path<Boolean> glutenFree= root.get(Dish_.glutenFree);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getDname()!=null) {
predicates.add(cb.equal(dname, criteria.getDname()));
}
if(criteria.isVegan()!=false) {
predicates.add(cb.equal(vegan, criteria.isVegan()));
}
if(criteria.isEggFree()!=false) {
predicates.add(cb.equal(eggFree, criteria.isEggFree()));
}
if(criteria.isGlutenFree()!=false) {
predicates.add(cb.equal(glutenFree, criteria.isGlutenFree()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
请看我的DishSearch Class:
package com.bron.demoJPA.specification;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class DishSearch {
private Long dishId;
private String dname;
private String description;
private double price;
private boolean vegan;
private boolean glutenFree;
private boolean eggFree;
private AppUser app;
}
请看我的SearchControllerClass:
@Controller
public class SearchController {
@Autowired
DishRepository drep;
@GetMapping("/showSearchForm")
public String showNewDishForm(Model model) {
// Create model attribute to bind form data
DishSearch dishSearch = new DishSearch();
model.addAttribute("dishSearch", dishSearch);
return "search_Dish";
}
@PostMapping("/showDishList")
public String saveUser(@ModelAttribute("dishSearch")DishSearch dishSearch) {
Specification<Dish> spec = new DishSpecification(dishSearch);
drep.findAll(spec);
return "show_dish_List";
}
}
请查看我的 DishRepository Class:
@Repository
public interface DishRepository extends JpaRepository<Dish, Long>, JpaSpecificationExecutor<Dish>{
@Transactional
@Modifying
List<Dish> findAll(Specification<Dish> spec);
}
请查看我的 search_Dish.html Thymeleaf 模板:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Dish Management System</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<br>
<div class="col-sm-10 offset-sm-1 text-center">
<div class="container">
<h2> Manage Dishes </h2>
<hr>
</div>
<form action="#" th:action="@{/showDishList}" th:object="${dishSearch}" method="POST">
<div class="col-sm-10 offset-sm-1 text-center">
<input type="text" th:field="*{dname}"
placeholder="Dish Name" class="form-control mb-4 col-10">
</div>
<div class="form-check form-check-inline">
<label class=" form-check-label" for="inlineCheckbox1 ">Vegan?</label>
<input type="checkbox" th:field="*{vegan}" />
<label class="form-check-label" for="inlineCheckbox1">Gluten Free?</label>
<input type="checkbox" th:field="*{glutenFree}" />
<label class="form-check-label" for="inlineCheckbox1">Egg Free?</label>
<input type="checkbox" th:field="*{EggFree}" />
</div>
<br>
<br>
<br>
<br>
<button type="submit" class="btn btn-info col-4"> Search Database</button>
</form>
</div>
<hr>
</body>
</html>
请查看我的 show_dish_List.html Thymeleaf 模板:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Search Results</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<br>
<div class="col-sm-10 offset-sm-1 text-center">
<h1>Dish List</h1>
</div>
<table border="1" class="table table-striped table-responsive-md">
<thead>
<tr>
<th>Dish Name</th>
<th>Dish description</th>
<th>Dish Price</th>
<th>Restaurant</th>
</tr>
</thead>
<tbody>
<tr th:each="dishSearch : ${listDishSearch}">
<td th:text="${dishSearch.dname}"></td>
<td th:text="${dishSearch.description}"></td>
<td th:text="${dishSearch.price}"></td>
</tr>
</tbody>
</table>
<div class="col-sm-10 offset-sm-1 text-center">
<a th:href="@{/showNewDishForm}"
class="btn btn-primary btn-sm mb-3"> Search Again</a>
</div>
----------------------------更新------------ ------------------
除了下面提供的答案外,在 Dish Specification Class 我更改了
if(criteria.getDname()!=null) {
predicates.add(cb.equal(dname, criteria.getDname()));
}
至
if(criteria.getDname()!="") {
predicates.add(cb.equal(dname, criteria.getDname()));
}
现在搜索工作正常!
我认为问题是您没有在用于呈现页面 show_dish_List.html
的 Model
中添加结果,因此 UI 中没有填充任何内容.您的 UI 期望数据在 listDishSearch
中,但该变量中没有任何内容。
将您的代码更新为:
@PostMapping("/showDishList")
public String saveUser(@ModelAttribute("dishSearch") DishSearch dishSearch, Model model) {
Specification<Dish> spec = new DishSpecification(dishSearch);
model.addAttribute("listDishSearch", drep.findAll(spec));
return "show_dish_List";
}
一切都应该工作正常。
从您的 DishRepository
存储库中删除方法 findAll
。它已经由接口 JpaSpecificationExecutor
.
提供
我正在尝试创建一个简单的 Spring 项目,餐厅可以在其中将菜单项添加到共享数据库,用户可以使用 html 表单根据一系列标准搜索菜肴 - 特别是饮食要求
表格示例:
Restaurant Name: Chez Hans
Gluten Free: (X)
Egg Free: (X)
Vegan: ()
示例SQL命令
Select all FROM "dishes" Dish WHERE restaurant_name = "Chez Hans" AND egg_free = TRUE AND
gluten_Free = TRUE;
然后会将符合其标准的菜肴列表返回给用户。
表单中的任何字段都可以留空,并且不勾选一个框,例如,“素食主义者”并不意味着条件应设置为 'false',而是不包含在查询中。 因此,处理该问题的最佳方法似乎是使用 JpaSpecificationExecutor 创建动态 SQL 查询(类似于下面问题答案中的实现)
Filtering database rows with spring-data-jpa and spring-mvc
我根据我的研究和先验知识创建了一个解决方案。然而,当我实施我的解决方案时,没有返回任何菜品——即使数据库中有符合搜索条件的菜品。没有产生任何错误,只是一片空白 table,所以我不确定哪里出错了。
我研究了无数 articles/videos 关于 JpaSpecificationExecutor/dynamic 的查询,但我仍然不确定该理论的某些部分。这是我收集的有关 JpaSpecificationExecutor/dynamic 查询的信息(但我可能错了)
元模型不需要创建动态查询,而是验证数据库查询语句的正确性
要使用元模型创建查询 classes 需要包装器 class(在我的示例中 - DishSearch)
以下几行是将元模型 SingularAttribute class 转换回原始 class 值。
路径 dname = root.get(Dish_.dname); 路径 vegan= root.get(Dish_.vegan);
我是 Spring 的新手,但仍然觉得很难。非常感谢任何帮助或建议!
请看下面我的菜谱class:
package com.bron.demoJPA.specification;
public class DishSpecification implements Specification<Dish> {
private final DishSearch criteria;
public DishSpecification(DishSearch ds) {
criteria =ds;
}
@Override
public Predicate toPredicate(Root<Dish> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
Path<String> dname = root.get(Dish_.dname);
Path<Boolean> vegan= root.get(Dish_.vegan);
Path<Boolean> eggFree= root.get(Dish_.eggFree);
Path<Boolean> glutenFree= root.get(Dish_.glutenFree);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getDname()!=null) {
predicates.add(cb.equal(dname, criteria.getDname()));
}
if(criteria.isVegan()!=false) {
predicates.add(cb.equal(vegan, criteria.isVegan()));
}
if(criteria.isEggFree()!=false) {
predicates.add(cb.equal(eggFree, criteria.isEggFree()));
}
if(criteria.isGlutenFree()!=false) {
predicates.add(cb.equal(glutenFree, criteria.isGlutenFree()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
请看我的DishSearch Class:
package com.bron.demoJPA.specification;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class DishSearch {
private Long dishId;
private String dname;
private String description;
private double price;
private boolean vegan;
private boolean glutenFree;
private boolean eggFree;
private AppUser app;
}
请看我的SearchControllerClass:
@Controller
public class SearchController {
@Autowired
DishRepository drep;
@GetMapping("/showSearchForm")
public String showNewDishForm(Model model) {
// Create model attribute to bind form data
DishSearch dishSearch = new DishSearch();
model.addAttribute("dishSearch", dishSearch);
return "search_Dish";
}
@PostMapping("/showDishList")
public String saveUser(@ModelAttribute("dishSearch")DishSearch dishSearch) {
Specification<Dish> spec = new DishSpecification(dishSearch);
drep.findAll(spec);
return "show_dish_List";
}
}
请查看我的 DishRepository Class:
@Repository
public interface DishRepository extends JpaRepository<Dish, Long>, JpaSpecificationExecutor<Dish>{
@Transactional
@Modifying
List<Dish> findAll(Specification<Dish> spec);
}
请查看我的 search_Dish.html Thymeleaf 模板:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Dish Management System</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<br>
<div class="col-sm-10 offset-sm-1 text-center">
<div class="container">
<h2> Manage Dishes </h2>
<hr>
</div>
<form action="#" th:action="@{/showDishList}" th:object="${dishSearch}" method="POST">
<div class="col-sm-10 offset-sm-1 text-center">
<input type="text" th:field="*{dname}"
placeholder="Dish Name" class="form-control mb-4 col-10">
</div>
<div class="form-check form-check-inline">
<label class=" form-check-label" for="inlineCheckbox1 ">Vegan?</label>
<input type="checkbox" th:field="*{vegan}" />
<label class="form-check-label" for="inlineCheckbox1">Gluten Free?</label>
<input type="checkbox" th:field="*{glutenFree}" />
<label class="form-check-label" for="inlineCheckbox1">Egg Free?</label>
<input type="checkbox" th:field="*{EggFree}" />
</div>
<br>
<br>
<br>
<br>
<button type="submit" class="btn btn-info col-4"> Search Database</button>
</form>
</div>
<hr>
</body>
</html>
请查看我的 show_dish_List.html Thymeleaf 模板:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Search Results</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<br>
<div class="col-sm-10 offset-sm-1 text-center">
<h1>Dish List</h1>
</div>
<table border="1" class="table table-striped table-responsive-md">
<thead>
<tr>
<th>Dish Name</th>
<th>Dish description</th>
<th>Dish Price</th>
<th>Restaurant</th>
</tr>
</thead>
<tbody>
<tr th:each="dishSearch : ${listDishSearch}">
<td th:text="${dishSearch.dname}"></td>
<td th:text="${dishSearch.description}"></td>
<td th:text="${dishSearch.price}"></td>
</tr>
</tbody>
</table>
<div class="col-sm-10 offset-sm-1 text-center">
<a th:href="@{/showNewDishForm}"
class="btn btn-primary btn-sm mb-3"> Search Again</a>
</div>
----------------------------更新------------ ------------------
除了下面提供的答案外,在 Dish Specification Class 我更改了
if(criteria.getDname()!=null) {
predicates.add(cb.equal(dname, criteria.getDname()));
}
至
if(criteria.getDname()!="") {
predicates.add(cb.equal(dname, criteria.getDname()));
}
现在搜索工作正常!
我认为问题是您没有在用于呈现页面 show_dish_List.html
的 Model
中添加结果,因此 UI 中没有填充任何内容.您的 UI 期望数据在 listDishSearch
中,但该变量中没有任何内容。
将您的代码更新为:
@PostMapping("/showDishList")
public String saveUser(@ModelAttribute("dishSearch") DishSearch dishSearch, Model model) {
Specification<Dish> spec = new DishSpecification(dishSearch);
model.addAttribute("listDishSearch", drep.findAll(spec));
return "show_dish_List";
}
一切都应该工作正常。
从您的 DishRepository
存储库中删除方法 findAll
。它已经由接口 JpaSpecificationExecutor
.