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 查询的信息(但我可能错了)

  1. 元模型不需要创建动态查询,而是验证数据库查询语句的正确性

  2. 要使用元模型创建查询 classes 需要包装器 class(在我的示例中 - DishSearch)

  3. 以下几行是将元模型 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.htmlModel 中添加结果,因此 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.

提供