【发布时间】:2022-01-21 17:10:03
【问题描述】:
我正在尝试创建一个简单的 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;
然后将符合其标准的菜肴列表返回给用户。
表单中的任何字段都可以留空,并且不选中框,例如“vegan”并不意味着条件应该设置为“false”,而是不包含在查询中。 因此,处理该问题的最佳方法似乎是使用 JpaSpecificationExecutor 创建动态 SQL 查询(类似于下面问题答案中的实现)
Filtering database rows with spring-data-jpa and spring-mvc
我根据我的研究和先验知识创建了一个解决方案。但是,当我实施我的解决方案时,不会返回任何菜肴——即使数据库中有符合搜索条件的菜肴。没有产生错误,只是一个空白表,所以我不确定我哪里出错了。
我研究了无数关于 JpaSpecificationExecutor/动态查询的文章/视频,但该理论的某些部分我仍然不确定。这是我收集的关于 JpaSpecificationExecutor/动态查询的信息(但我可能错了)
-
元模型不需要创建动态查询,而是验证数据库查询语句的正确性
-
要使用元模型类创建查询,需要一个包装类(在我的示例中 - DishSearch)
-
以下几行是将元模型 SingularAttribute 类转换回原始类值。
路径 dname = root.get(Dish_.dname); 路径 vegan= root.get(Dish_.vegan);
我对 Spring 还很陌生,但仍然觉得它很困难。任何帮助或建议将不胜感激!
请看下面我的 DishSpecification 类:
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 课程:
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;
}
请查看我的 SearchController 类:
@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 类:
@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>
更新 我尝试在 Searchcontroller 类和两种 Html 表单中从“DishSearch”更改为“Dish”,但结果是一样的。
【问题讨论】:
标签: spring-data-jpa thymeleaf specifications dynamicquery metamodel