【问题标题】:Dynamic queries and JpaSpecificationExecutor in SpringSpring中的动态查询和JpaSpecificationExecutor
【发布时间】: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/动态查询的信息(但我可能错了)

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

  2. 要使用元模型类创建查询,需要一个包装类(在我的示例中 - DishSearch)

  3. 以下几行是将元模型 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


    【解决方案1】:

    我认为问题在于您没有在用于呈现页面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已经提供了。

    【讨论】:

    • 感谢您的帮助!它现在部分工作! rep.findAll(spec) 方法正在查找菜肴名称,但是忽略真/假复选框,即选择“无蛋”不会返回任何内容,尽管数据库中存在无蛋菜肴。你有什么想法?再次感谢!
    猜你喜欢
    • 2017-01-03
    • 2014-12-05
    • 1970-01-01
    • 2015-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-16
    • 2018-07-06
    相关资源
    最近更新 更多