Skip to content
Permalink
Browse files
Adds a custom method implementation that is provided together with al…
…l other Spring Data JPA methods.
  • Loading branch information
brunodrugowick committed Feb 20, 2020
1 parent 8b8a114 commit b7fb9718824cfe9fa74113ed2ad772a32690a46f
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 2 deletions.
@@ -0,0 +1,11 @@
package dev.drugowick.jpaqueriesblogpost.infrastructure.repository;

import dev.drugowick.jpaqueriesblogpost.domain.model.Restaurant;
import dev.drugowick.jpaqueriesblogpost.web.pages.dto.AdvancedSearch;

import java.util.List;

public interface CustomRestaurantRepository {

List<Restaurant> advancedSearch(AdvancedSearch advancedSearch);
}
@@ -0,0 +1,69 @@
package dev.drugowick.jpaqueriesblogpost.infrastructure.repository;

import dev.drugowick.jpaqueriesblogpost.domain.model.Restaurant;
import dev.drugowick.jpaqueriesblogpost.web.pages.dto.AdvancedSearch;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.HashMap;
import java.util.List;

/**
* This is a special class that Spring Data JPA (SDJ) detects and uses. This detection is based on the class name,
* which is the name of the repository interface plus the string "Impl".
*/
@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();
}
}
@@ -7,7 +7,7 @@
import java.math.BigDecimal;
import java.util.List;

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

List<Restaurant> findAllByNameContaining(String query);

@@ -1,8 +1,10 @@
package dev.drugowick.jpaqueriesblogpost.web.pages;

import dev.drugowick.jpaqueriesblogpost.infrastructure.repository.RestaurantRepository;
import dev.drugowick.jpaqueriesblogpost.web.pages.dto.AdvancedSearch;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@@ -47,4 +49,20 @@ public String indexWithQuery(@RequestParam("query") String query,
model.addAttribute("query", query);
return "index";
}

@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";
}
}
@@ -0,0 +1,19 @@
package dev.drugowick.jpaqueriesblogpost.web.pages.dto;

import lombok.Data;
import lombok.ToString;

import java.math.BigDecimal;

@Data
@ToString
public class AdvancedSearch {
private String name;
private String address;
private BigDecimal minDeliveryFee = new BigDecimal(0);
private BigDecimal maxDeliveryFee = new BigDecimal(100);
private String cuisine;
private String city;
private String grabngo;
private String active;
}
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<link href='/webjars/bootstrap/css/bootstrap.min.css' rel='stylesheet'>

<meta charset="UTF-8">
<title>Restaurants</title>
</head>
<body class="container">

<div th:replace="fragments/header :: header (title='Restaurants')"></div>

<br>
<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">
<label for="grabngo" class="col col-sm-4">Grab'n'Go</label>
<select class="form-control col-sm-8" id="grabngo" name="grabngo">
<option th:selected="${search.getGrabngo() == ''}" value="name">&lt;Empty&gt;</option>
<option th:selected="${search.getGrabngo() == 'Yes'}" value="cuisine">Yes</option>
<option th:selected="${search.getGrabngo() == 'No'}" value="delivery-fee">No</option>
</select>
</div>
<div class="form-group row">
<label for="active" class="col col-sm-4">Active</label>
<select class="form-control col-sm-8" id="active" name="active">
<option th:selected="${search.getActive() == ''}" value="name">&lt;Empty&gt;</option>
<option th:selected="${search.getActive() == 'Yes'}" value="cuisine">Yes</option>
<option th:selected="${search.getActive() == 'No'}" value="delivery-fee">No</option>
</select>
</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>

<br><br>

<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Delivery Fee</th>
<th>Cuisine</th>
<th>City</th>
<th>Grab'n'Go</th>
<th>Active</th>
</tr>
</thead>
<tr th:each="restaurant : ${restaurants}">
<td>
<button
class="btn btn-primary"
th:onclick="'window.location.href=\'/restaurants/' + ${restaurant.getId()} + '\''"
th:text="${restaurant.getName()}"></button>
</td>
<td th:text="${restaurant.getAddress()}"></td>
<td th:text="${restaurant.getDeliveryFee()}"></td>
<td th:text="${restaurant.getCuisine().getName()}"></td>
<td th:text="${restaurant.getCity()}"></td>
<td><input disabled="true" th:checked="${restaurant.isGrabngo()}" type="checkbox"></td>
<td><input disabled="true" th:checked="${restaurant.isActive()}" type="checkbox"></td>
</tr>
<tbody>

</tbody>
</table>

<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
@@ -17,13 +17,21 @@
<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="/advancedSearch">Advanced search</a>
</div>
</div>
<div class="form-group row">
<label for="field" class="col-sm-4 col-form-label">Search by</label>
<select class="form-control col-sm-8" id="field" name="field">
<option th:selected="${field == 'name'}" value="name">Name</option>
<option th:selected="${field == 'cuisine'}" value="cuisine">Cuisine</option>
<option th:selected="${field == 'delivery-fee'}" value="delivery-fee">Delivery Fee</option>
<option th:selected="${field == 'local-grabngo'}" value="local-grabngo">Active and Grab'n'Go by City</option>
<option th:selected="${field == 'local-grabngo'}" value="local-grabngo">Active and Grab'n'Go by
City
</option>
</select>
</div>
<div class="form-group row">

0 comments on commit b7fb971

Please sign in to comment.