使用您自己的代码扩展Spring Data JPA存储库的三个步骤

这是“查询您的Spring Data JPA存储库”系列文章的第六篇。

What about an advanced search?

如果您想通过餐厅中可用的任何字段进行搜索然后合并而不必选择特定搜索,该怎么办。

Alt Text

好吧,它不是Google,但它的功能足以应付许多用例。

Let's prepare the app

好吧,你知道演习。 让我们来构建应用程序使用此新的高级搜索选项所必需的内容。

搜索表单如下所示:

<form th:action="@{/advancedSearch/perform}">
    <div class="card mb-auto">
        <div class="card-header">
            <h4>Filter Restaurants</h4>
        </div>
        <div class="card-body">
            <div class="form-group row small">
                <div class="col col-sm-4 float-left"></div>
                <div class="col col-sm-8 float-right">
                    <a class="float-right" href="/">Simple search</a>
                </div>
            </div>
            <div class="form-group row">
                <label class="col col-sm-4" for="name">Name</label>
                <input class="form-control col-sm-8" id="name" placeholder="<empty>" th:name="name"
                       th:value="${search.name}" type="text"/>
            </div>
            <div class="form-group row">
                <label class="col col-sm-4" for="address">Address</label>
                <input class="form-control col-sm-8" id="address" placeholder="<empty>" th:name="address"
                       th:value="${search.address}" type="text"/>
            </div>
            <div class="form-group row">
                <label class="col col-sm-4" for="minDeliveryFee">Delivery Fee</label>
                <label class="col col-sm-1" for="minDeliveryFee">Min</label>
                <input class="form-control col-sm-3" id="minDeliveryFee" placeholder="<min>" th:name="minDeliveryFee"
                       th:value="${search.minDeliveryFee}" type="text"/>
                <label class="col col-sm-1" for="maxDeliveryFee">Max</label>
                <input class="form-control col-sm-3" id="maxDeliveryFee" placeholder="<max>" th:name="maxDeliveryFee"
                       th:value="${search.maxDeliveryFee}" type="text"/>
            </div>
            <div class="form-group row">
                <label class="col col-sm-4" for="cuisine">Cuisine</label>
                <input class="form-control col-sm-8" id="cuisine" placeholder="<empty>" th:name="cuisine"
                       th:value="${search.cuisine}" type="text"/>
            </div>
            <div class="form-group row">
                <label class="col col-sm-4" for="city">City</label>
                <input class="form-control col-sm-8" id="city" placeholder="<empty>" th:name="city"
                       th:value="${search.city}" type="text"/>
            </div>
            <div class="form-group row">
                <div class="col col-sm-4"></div>
                <input class="btn btn-primary col col-sm-8" type="submit" value="Submit">
            </div>
        </div>
    </div>
</form>

我们需要新的控制器方法来处理新页面和搜索操作:

@RequestMapping("/advancedSearch")
public String advancedSearch(Model model) {
    model.addAttribute("restaurants", restaurantRepository.findAll());
    model.addAttribute("search", new AdvancedSearch());
    return "advancedSearch";
}

@RequestMapping("/advancedSearch/perform")
public String advancedSearchWithQuery(@ModelAttribute AdvancedSearch advancedSearch, Model model) {
    model.addAttribute("restaurants", restaurantRepository.advancedSearch(advancedSearch));

    model.addAttribute("search", advancedSearch);
    return "advancedSearch";
}

Alt Text

注意:

  • The @ModelAttribute annotation: it maps the input to a new class called...
  • AdvancedSearch. This is a simple bean with fields to hold data coming from the form. With the help of Project Lombok this class is quite simple.
    • We use this class to pass data between the form and the app on both directions. This is how we can show the query inputs to the user even after the page is refreshed to show the search results (remember this is not your typical SPA, ok?).

Custom Repository Methods

但您可能也已经注意到,我们在restaurantRepository叫高级搜索通过同音异义词高级搜索对象通过参数。 不,这不是Spring Data JPA的默认方法(会很好,是吗?),但是创建自己的自定义方法的能力是我们在此处学习的强大功能!

让我们分三步看一下。

Step 1: Create a new interface to hold the method declarations

public interface CustomRestaurantRepository {
    List<Restaurant> advancedSearch(AdvancedSearch advancedSearch);
}

请注意高级搜索方法。 这就是我们现在所需要的。

Step 2: Make your Spring Data JPA repository extend your new interface

public interface RestaurantRepository extends JpaRepository<Restaurant, Long>, CustomRestaurantRepository {

现在注意,我们的存储库扩展了JPAR存储库(来自Spring Data JPA项目)和CustomRestaurant存储库(我们自己定义存储库方法的类)。 现在我们可以调用新方法了,但是它的代码呢?

Step 3: Implement the custom method

现在只需要实现所需的代码即可。 我们将创建一个CustomRestaurant存储库Impl实现我们新创建的类CustomRestaurant存储库接口。

@Repository
public class CustomRestaurantRepositoryImpl implements CustomRestaurantRepository {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<Restaurant> advancedSearch(AdvancedSearch advancedSearch) {

        var jpql = new StringBuilder();
        jpql.append("from Restaurant where 1=1 ");

        var parameters = new HashMap<String, Object>();

        if (StringUtils.hasLength(advancedSearch.getName())) {
            jpql.append("and name like :name ");
            parameters.put("name", "%" + advancedSearch.getName() + "%");
        }

        if (StringUtils.hasLength(advancedSearch.getAddress())) {
            jpql.append("and address like :address ");
            parameters.put("address", "%" + advancedSearch.getAddress() + "%");
        }

        if (advancedSearch.getMinDeliveryFee() != null) {
            jpql.append("and deliveryFee >= :startFee ");
            parameters.put("startFee", advancedSearch.getMinDeliveryFee());
        }

        if (advancedSearch.getMaxDeliveryFee() != null) {
            jpql.append("and deliveryFee <= :endingFee ");
            parameters.put("endingFee", advancedSearch.getMaxDeliveryFee());
        }

        if (StringUtils.hasLength(advancedSearch.getCuisine())) {
            jpql.append("and cuisine.name like :cuisine ");
            parameters.put("cuisine", "%" + advancedSearch.getCuisine() + "%");
        }

        if (StringUtils.hasLength(advancedSearch.getCity())) {
            jpql.append("and city like :city ");
            parameters.put("city", "%" + advancedSearch.getCity() + "%");
        }

        TypedQuery<Restaurant> query = entityManager.createQuery(jpql.toString(), Restaurant.class);

        parameters.forEach((key, value) -> query.setParameter(key, value));

        return query.getResultList();
    }
}

这里有很多要解压的东西:

  • 首先,我们了解实体管理器通过注入@PersistenceContext。 这样,我们可以通过JPA执行操作。然后我们覆盖高级搜索 method to:检查每个属性高级搜索对象,如果不为null,则将其添加到自定义JPQL查询中。匹配适当的参数。 首先,在临时地图上,然后在查询上实际进行映射。执行查询以返回结果。最后但并非最不重要的后缀Impl实际是什么告诉Spring Data JPA这是现有产品的自定义实现餐厅资料库。 添加我们的接口并扩展Spring Data JPA接口只是为了使代码可读。 你应该做这个!

Additional challenge

Notice that the example app has also an option to select the logical operator to use when performing the advanced search, AñD or OR. You may want to try to implement it yourself, but if you don't want to, here's the implementation for you.

那是最终结果:

Alt Text

The example app

The working app is here (wait for Heroku to load the app, it takes a few seconds on the free tier).

Commits related to this post

The preparation and core code is here.
The logical operator addition is here.
And there's a UI improvement I did here

GitHub logo brunodrugowick / jpa-queries-blog-post

A demo project for a blog post about (Spring Data) JPA.

from: https://dev.to//brunodrugowick/four-steps-to-extend-a-spring-data-jpa-repository-with-your-own-code-53b0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值