Skip to content

Commit b7fb971

Browse files
Adds a custom method implementation that is provided together with all other Spring Data JPA methods.
1 parent 8b8a114 commit b7fb971

File tree

7 files changed

+244
-2
lines changed

7 files changed

+244
-2
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.drugowick.jpaqueriesblogpost.infrastructure.repository;
2+
3+
import dev.drugowick.jpaqueriesblogpost.domain.model.Restaurant;
4+
import dev.drugowick.jpaqueriesblogpost.web.pages.dto.AdvancedSearch;
5+
6+
import java.util.List;
7+
8+
public interface CustomRestaurantRepository {
9+
10+
List<Restaurant> advancedSearch(AdvancedSearch advancedSearch);
11+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package dev.drugowick.jpaqueriesblogpost.infrastructure.repository;
2+
3+
import dev.drugowick.jpaqueriesblogpost.domain.model.Restaurant;
4+
import dev.drugowick.jpaqueriesblogpost.web.pages.dto.AdvancedSearch;
5+
import org.springframework.stereotype.Repository;
6+
import org.springframework.util.StringUtils;
7+
8+
import javax.persistence.EntityManager;
9+
import javax.persistence.PersistenceContext;
10+
import javax.persistence.TypedQuery;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
14+
/**
15+
* This is a special class that Spring Data JPA (SDJ) detects and uses. This detection is based on the class name,
16+
* which is the name of the repository interface plus the string "Impl".
17+
*/
18+
@Repository
19+
public class CustomRestaurantRepositoryImpl implements CustomRestaurantRepository {
20+
21+
@PersistenceContext
22+
private EntityManager entityManager;
23+
24+
25+
@Override
26+
public List<Restaurant> advancedSearch(AdvancedSearch advancedSearch) {
27+
28+
var jpql = new StringBuilder();
29+
jpql.append("from Restaurant where 1=1 ");
30+
31+
var parameters = new HashMap<String, Object>();
32+
33+
if (StringUtils.hasLength(advancedSearch.getName())) {
34+
jpql.append("and name like :name ");
35+
parameters.put("name", "%" + advancedSearch.getName() + "%");
36+
}
37+
38+
if (StringUtils.hasLength(advancedSearch.getAddress())) {
39+
jpql.append("and address like :address ");
40+
parameters.put("address", "%" + advancedSearch.getAddress() + "%");
41+
}
42+
43+
if (advancedSearch.getMinDeliveryFee() != null) {
44+
jpql.append("and deliveryFee >= :startFee ");
45+
parameters.put("startFee", advancedSearch.getMinDeliveryFee());
46+
}
47+
48+
if (advancedSearch.getMaxDeliveryFee() != null) {
49+
jpql.append("and deliveryFee <= :endingFee ");
50+
parameters.put("endingFee", advancedSearch.getMaxDeliveryFee());
51+
}
52+
53+
if (StringUtils.hasLength(advancedSearch.getCuisine())) {
54+
jpql.append("and cuisine.name like :cuisine ");
55+
parameters.put("cuisine", "%" + advancedSearch.getCuisine() + "%");
56+
}
57+
58+
if (StringUtils.hasLength(advancedSearch.getCity())) {
59+
jpql.append("and city like :city ");
60+
parameters.put("city", "%" + advancedSearch.getCity() + "%");
61+
}
62+
63+
TypedQuery<Restaurant> query = entityManager.createQuery(jpql.toString(), Restaurant.class);
64+
65+
parameters.forEach((key, value) -> query.setParameter(key, value));
66+
67+
return query.getResultList();
68+
}
69+
}

src/main/java/dev/drugowick/jpaqueriesblogpost/infrastructure/repository/RestaurantRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import java.math.BigDecimal;
88
import java.util.List;
99

10-
public interface RestaurantRepository extends JpaRepository<Restaurant, Long> {
10+
public interface RestaurantRepository extends JpaRepository<Restaurant, Long>, CustomRestaurantRepository {
1111

1212
List<Restaurant> findAllByNameContaining(String query);
1313

src/main/java/dev/drugowick/jpaqueriesblogpost/web/pages/IndexPage.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package dev.drugowick.jpaqueriesblogpost.web.pages;
22

33
import dev.drugowick.jpaqueriesblogpost.infrastructure.repository.RestaurantRepository;
4+
import dev.drugowick.jpaqueriesblogpost.web.pages.dto.AdvancedSearch;
45
import org.springframework.stereotype.Controller;
56
import org.springframework.ui.Model;
7+
import org.springframework.web.bind.annotation.ModelAttribute;
68
import org.springframework.web.bind.annotation.RequestMapping;
79
import org.springframework.web.bind.annotation.RequestParam;
810

@@ -47,4 +49,20 @@ public String indexWithQuery(@RequestParam("query") String query,
4749
model.addAttribute("query", query);
4850
return "index";
4951
}
52+
53+
@RequestMapping("/advancedSearch")
54+
public String advancedSearch(Model model) {
55+
model.addAttribute("restaurants", restaurantRepository.findAll());
56+
model.addAttribute("search", new AdvancedSearch());
57+
return "advancedSearch";
58+
}
59+
60+
@RequestMapping("/advancedSearch/perform")
61+
public String advancedSearchWithQuery(@ModelAttribute AdvancedSearch advancedSearch,
62+
Model model) {
63+
model.addAttribute("restaurants", restaurantRepository.advancedSearch(advancedSearch));
64+
65+
model.addAttribute("search", advancedSearch);
66+
return "advancedSearch";
67+
}
5068
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dev.drugowick.jpaqueriesblogpost.web.pages.dto;
2+
3+
import lombok.Data;
4+
import lombok.ToString;
5+
6+
import java.math.BigDecimal;
7+
8+
@Data
9+
@ToString
10+
public class AdvancedSearch {
11+
private String name;
12+
private String address;
13+
private BigDecimal minDeliveryFee = new BigDecimal(0);
14+
private BigDecimal maxDeliveryFee = new BigDecimal(100);
15+
private String cuisine;
16+
private String city;
17+
private String grabngo;
18+
private String active;
19+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<!DOCTYPE html>
2+
<html lang="en" xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<link href='/webjars/bootstrap/css/bootstrap.min.css' rel='stylesheet'>
5+
6+
<meta charset="UTF-8">
7+
<title>Restaurants</title>
8+
</head>
9+
<body class="container">
10+
11+
<div th:replace="fragments/header :: header (title='Restaurants')"></div>
12+
13+
<br>
14+
<form th:action="@{/advancedSearch/perform}">
15+
<div class="card mb-auto">
16+
<div class="card-header">
17+
<h4>Filter Restaurants</h4>
18+
</div>
19+
<div class="card-body">
20+
<div class="form-group row small">
21+
<div class="col col-sm-4 float-left"></div>
22+
<div class="col col-sm-8 float-right">
23+
<a class="float-right" href="/">Simple search</a>
24+
</div>
25+
</div>
26+
<div class="form-group row">
27+
<label class="col col-sm-4" for="name">Name</label>
28+
<input class="form-control col-sm-8" id="name" placeholder="<empty>" th:name="name"
29+
th:value="${search.name}" type="text"/>
30+
</div>
31+
<div class="form-group row">
32+
<label class="col col-sm-4" for="address">Address</label>
33+
<input class="form-control col-sm-8" id="address" placeholder="<empty>" th:name="address"
34+
th:value="${search.address}" type="text"/>
35+
</div>
36+
<div class="form-group row">
37+
<label class="col col-sm-4" for="minDeliveryFee">Delivery Fee</label>
38+
<label class="col col-sm-1" for="minDeliveryFee">Min</label>
39+
<input class="form-control col-sm-3" id="minDeliveryFee" placeholder="<min>" th:name="minDeliveryFee"
40+
th:value="${search.minDeliveryFee}" type="text"/>
41+
<label class="col col-sm-1" for="maxDeliveryFee">Max</label>
42+
<input class="form-control col-sm-3" id="maxDeliveryFee" placeholder="<max>" th:name="maxDeliveryFee"
43+
th:value="${search.maxDeliveryFee}" type="text"/>
44+
</div>
45+
<div class="form-group row">
46+
<label class="col col-sm-4" for="cuisine">Cuisine</label>
47+
<input class="form-control col-sm-8" id="cuisine" placeholder="<empty>" th:name="cuisine"
48+
th:value="${search.cuisine}" type="text"/>
49+
</div>
50+
<div class="form-group row">
51+
<label class="col col-sm-4" for="city">City</label>
52+
<input class="form-control col-sm-8" id="city" placeholder="<empty>" th:name="city"
53+
th:value="${search.city}" type="text"/>
54+
</div>
55+
<!--
56+
<div class="form-group row">
57+
<label for="grabngo" class="col col-sm-4">Grab'n'Go</label>
58+
<select class="form-control col-sm-8" id="grabngo" name="grabngo">
59+
<option th:selected="${search.getGrabngo() == ''}" value="name">&lt;Empty&gt;</option>
60+
<option th:selected="${search.getGrabngo() == 'Yes'}" value="cuisine">Yes</option>
61+
<option th:selected="${search.getGrabngo() == 'No'}" value="delivery-fee">No</option>
62+
</select>
63+
</div>
64+
<div class="form-group row">
65+
<label for="active" class="col col-sm-4">Active</label>
66+
<select class="form-control col-sm-8" id="active" name="active">
67+
<option th:selected="${search.getActive() == ''}" value="name">&lt;Empty&gt;</option>
68+
<option th:selected="${search.getActive() == 'Yes'}" value="cuisine">Yes</option>
69+
<option th:selected="${search.getActive() == 'No'}" value="delivery-fee">No</option>
70+
</select>
71+
</div>
72+
-->
73+
<div class="form-group row">
74+
<div class="col col-sm-4"></div>
75+
<input class="btn btn-primary col col-sm-8" type="submit" value="Submit">
76+
</div>
77+
</div>
78+
</div>
79+
</form>
80+
81+
<br><br>
82+
83+
<table class="table table-striped table-bordered">
84+
<thead>
85+
<tr>
86+
<th>Name</th>
87+
<th>Address</th>
88+
<th>Delivery Fee</th>
89+
<th>Cuisine</th>
90+
<th>City</th>
91+
<th>Grab'n'Go</th>
92+
<th>Active</th>
93+
</tr>
94+
</thead>
95+
<tr th:each="restaurant : ${restaurants}">
96+
<td>
97+
<button
98+
class="btn btn-primary"
99+
th:onclick="'window.location.href=\'/restaurants/' + ${restaurant.getId()} + '\''"
100+
th:text="${restaurant.getName()}"></button>
101+
</td>
102+
<td th:text="${restaurant.getAddress()}"></td>
103+
<td th:text="${restaurant.getDeliveryFee()}"></td>
104+
<td th:text="${restaurant.getCuisine().getName()}"></td>
105+
<td th:text="${restaurant.getCity()}"></td>
106+
<td><input disabled="true" th:checked="${restaurant.isGrabngo()}" type="checkbox"></td>
107+
<td><input disabled="true" th:checked="${restaurant.isActive()}" type="checkbox"></td>
108+
</tr>
109+
<tbody>
110+
111+
</tbody>
112+
</table>
113+
114+
<script src="/webjars/jquery/jquery.min.js"></script>
115+
<script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
116+
</body>
117+
</html>

src/main/resources/templates/index.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@
1717
<h4>Filter Restaurants</h4>
1818
</div>
1919
<div class="card-body">
20+
<div class="form-group row small">
21+
<div class="col col-sm-4 float-left"></div>
22+
<div class="col col-sm-8 float-right">
23+
<a class="float-right" href="/advancedSearch">Advanced search</a>
24+
</div>
25+
</div>
2026
<div class="form-group row">
2127
<label for="field" class="col-sm-4 col-form-label">Search by</label>
2228
<select class="form-control col-sm-8" id="field" name="field">
2329
<option th:selected="${field == 'name'}" value="name">Name</option>
2430
<option th:selected="${field == 'cuisine'}" value="cuisine">Cuisine</option>
2531
<option th:selected="${field == 'delivery-fee'}" value="delivery-fee">Delivery Fee</option>
26-
<option th:selected="${field == 'local-grabngo'}" value="local-grabngo">Active and Grab'n'Go by City</option>
32+
<option th:selected="${field == 'local-grabngo'}" value="local-grabngo">Active and Grab'n'Go by
33+
City
34+
</option>
2735
</select>
2836
</div>
2937
<div class="form-group row">

0 commit comments

Comments
 (0)