Back to Spring Boot Course

Spring Boot Course

Pagination

  • Pagination is a technique used to breakdown a large datasets into smaller, more manageable chunks.
  • In Spring Boot applications, Pagination helps in displaying large amounts of data across multiple pages, enhancing user experience and reducing derver load.

Purpose of Pagination

  • The primary purpose of pagination is to improve performance by fetching and displaying only a subset of data at a time.
  • This results in faster load times, reduced memory consumption, and smoother user interactions.

Sorting

  • Sorting involves arranging data in a specific order, such as alphabetical, numerical or chronological.
  • In Spring Boot applications, Sorting allows users to organize data based on their preferences, making it easier to find and analyze information.

Purpose of Sorting

  • The purpose of sorting is to provide users with the flexibility to arrange data according to their needs.
  • Whether its sorting by date, name or relevance, sorting functionality enhances usability and allows for better data comprehension.

Implementation of Pagination and Sorting

Let's create a restaurants and dishes management system with pagination and sorting using Spring MVC using thymeleaf and spring data jpa.

Lets start with spring boot app

pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.0</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.praveen.restaurant</groupId> <artifactId>praveen-restaurant-app</artifactId> <version>1.0.0</version> <name>Praveen Restaurant App</name> <description>Praveen Restaurant App</description> <url /> <licenses> <license /> </licenses> <developers> <developer /> </developers> <scm> <connection /> <developerConnection /> <tag /> <url /> </scm> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

Entities

package com.praveen.restaurant.entity; import java.util.List; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @Entity @Table(name = "restaurants") public class Restaurant { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "restaurant_id") private Long id; private String name; private String cuisine; @OneToMany(mappedBy = "restaurant") private List<Dish> dishes; public Restaurant() { } public Restaurant(Long id, String name, String cuisine, List<Dish> dishes) { this.id = id; this.name = name; this.cuisine = cuisine; this.dishes = dishes; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCuisine() { return cuisine; } public void setCuisine(String cuisine) { this.cuisine = cuisine; } public List<Dish> getDishes() { return dishes; } public void setDishes(List<Dish> dishes) { this.dishes = dishes; } }
package com.praveen.restaurant.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @Entity @Table(name = "dishes") public class Dish { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "dish_id") private Long id; private String name; private String description; private Double price; @ManyToOne @JoinColumn(name = "restaurant_id") private Restaurant restaurant; public Dish() { } public Dish(Long id, String name, String description, Double price, Restaurant restaurant) { this.id = id; this.name = name; this.description = description; this.price = price; this.restaurant = restaurant; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public Restaurant getRestaurant() { return restaurant; } public void setRestaurant(Restaurant restaurant) { this.restaurant = restaurant; } }

JPA Repository

package com.praveen.restaurant.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.praveen.restaurant.entity.Restaurant; @Repository public interface RestaurantRepository extends JpaRepository<Restaurant, Long> { Page<Restaurant> findAll(Pageable pageable); }
package com.praveen.restaurant.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.praveen.restaurant.entity.Dish; @Repository public interface DishRepository extends JpaRepository<Dish, Long> { Page<Dish> findByRestaurantId(Long restaurantId, Pageable pageable); }

Service

package com.praveen.restaurant.service; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import com.praveen.restaurant.entity.Restaurant; import com.praveen.restaurant.repository.RestaurantRepository; @Service public class RestaurantService { private RestaurantRepository restaurantRepository; public RestaurantService(RestaurantRepository restaurantRepository) { super(); this.restaurantRepository = restaurantRepository; } public Page<Restaurant> getRestaurants(int page, int size, String sortBy, String direction) { // Default sortBy to "name" and direction to "asc" if they are null or empty if (sortBy == null || sortBy.isEmpty()) { sortBy = "name"; } if (direction == null || direction.isEmpty()) { direction = "asc"; } Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(direction), sortBy)); return restaurantRepository.findAll(pageable); } }
package com.praveen.restaurant.service; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import com.praveen.restaurant.entity.Dish; import com.praveen.restaurant.repository.DishRepository; @Service public class DishService { private DishRepository dishRepository; public DishService(DishRepository dishRepository) { this.dishRepository = dishRepository; } public Page<Dish> getAllDishes(int page, int size, String sortBy, String direction, Long restaurantId) { Sort sort = direction.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending(); return dishRepository.findAll(PageRequest.of(page, size, sort).withSort(sort)); } }

Controller

package com.praveen.restaurant.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class HomeController { @GetMapping("/") public String home() { return "index"; } }
package com.praveen.restaurant.controller; import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import com.praveen.restaurant.entity.Restaurant; import com.praveen.restaurant.service.RestaurantService; @Controller public class RestaurantController { private RestaurantService restaurantService; public RestaurantController(RestaurantService restaurantService) { super(); this.restaurantService = restaurantService; } @GetMapping("/restaurants") public String getRestaurants(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "5") int size, @RequestParam(defaultValue = "name") String sort, @RequestParam(defaultValue = "asc") String direction, Model model) { Page<Restaurant> restaurantPage = restaurantService.getRestaurants(page, size, sort, direction); model.addAttribute("restaurantPage", restaurantPage); model.addAttribute("currentPage", page); model.addAttribute("size", size); model.addAttribute("sort", sort); model.addAttribute("direction", direction); model.addAttribute("totalPages", restaurantPage.getTotalPages()); return "restaurants"; } }
package com.praveen.restaurant.controller; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import com.praveen.restaurant.entity.Dish; import com.praveen.restaurant.repository.DishRepository; import com.praveen.restaurant.repository.RestaurantRepository; @Controller public class DishController { private DishRepository dishRepository; private RestaurantRepository restaurantRepository; public DishController(DishRepository dishRepository, RestaurantRepository restaurantRepository) { super(); this.dishRepository = dishRepository; this.restaurantRepository = restaurantRepository; } @GetMapping("/dishes") public String getDishes(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "5") int size, @RequestParam(defaultValue = "name") String sort, @RequestParam(defaultValue = "asc") String direction, @RequestParam(defaultValue = "0") Long restaurantId, // Default to 0 to show all Model model) { Pageable pageable = PageRequest.of(page, size, direction.equals("asc") ? Sort.by(sort).ascending() : Sort.by(sort).descending()); Page<Dish> dishPage; if (restaurantId == 0) { dishPage = dishRepository.findAll(pageable); } else { dishPage = dishRepository.findByRestaurantId(restaurantId, pageable); } model.addAttribute("dishPage", dishPage); model.addAttribute("currentPage", page); model.addAttribute("totalPages", dishPage.getTotalPages()); model.addAttribute("direction", direction); model.addAttribute("sort", sort); model.addAttribute("restaurantId", restaurantId); model.addAttribute("restaurants", restaurantRepository.findAll()); return "dishes"; } @GetMapping("/dishes/delete/{id}") public String deleteDish(@PathVariable Long id, @RequestParam(defaultValue = "0") Long restaurantId, RedirectAttributes redirectAttributes) { // Check if the dish exists if (dishRepository.existsById(id)) { dishRepository.deleteById(id); redirectAttributes.addFlashAttribute("message", "Dish deleted successfully!"); } else { redirectAttributes.addFlashAttribute("error", "Dish not found!"); } // Redirect back to the list of dishes for the restaurant return "redirect:/dishes?restaurantId=" + restaurantId; } }

H2 DB Setup

application.properties

spring.application.name=praveen-restaurant-app spring.datasource.url=jdbc:h2:file:./testdb;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:script.sql' spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.h2.console.enabled=true spring.h2.console.path=/h2-console spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML spring.thymeleaf.encoding=UTF-8

script.sql which is placed under src/main/resources folder to load the data into H2 DB at startup

-- Drop the tables if they already exist DROP TABLE IF EXISTS dishes; DROP TABLE IF EXISTS restaurants; -- Create the restaurants table with auto-incrementing ID CREATE TABLE restaurants ( restaurant_id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, cuisine VARCHAR(50) NOT NULL ); -- Insert data into restaurants table INSERT INTO restaurants (name, cuisine) VALUES ('The Gourmet Hub', 'Italian'), ('Spice Avenue', 'Indian'), ('Sushi Haven', 'Japanese'), ('Taco Palace', 'Mexican'), ('Pasta Paradise', 'Italian'), ('Curry House', 'Indian'), ('Ramen Junction', 'Japanese'), ('Burrito Bandits', 'Mexican'), ('Pizza World', 'Italian'), ('Chaat Corner', 'Indian'), ('Sashimi Spot', 'Japanese'), ('Fajita Fiesta', 'Mexican'), ('Lasagna Land', 'Italian'), ('Biryani Bistro', 'Indian'), ('Tempura Tavern', 'Japanese'), ('Enchilada Express', 'Mexican'), ('Gnocchi Grove', 'Italian'), ('Samosa Street', 'Indian'), ('Miso Master', 'Japanese'), ('Nacho Nirvana', 'Mexican'), ('Tiramisu Treat', 'Italian'), ('Curry Canteen', 'Indian'), ('Soba Station', 'Japanese'), ('Quesadilla Queen', 'Mexican'), ('Fettuccine Feast', 'Italian'), ('Masala Magic', 'Indian'), ('Sushi Samurai', 'Japanese'), ('Burrito Bliss', 'Mexican'), ('Risotto Royale', 'Italian'), ('Paneer Palace', 'Indian'), ('Katsu Kingdom', 'Japanese'), ('Chimichanga Chalet', 'Mexican'), ('Ravioli Rendezvous', 'Italian'), ('Dosa Delight', 'Indian'), ('Udon Universe', 'Japanese'), ('Tostada Town', 'Mexican'), ('Spaghetti Sphere', 'Italian'), ('Chicken Tikka Tavern', 'Indian'), ('Sushi Sushi', 'Japanese'), ('Baja Burrito', 'Mexican'), ('Pesto Place', 'Italian'), ('Vada Pav Village', 'Indian'), ('Sashimi Sushi', 'Japanese'), ('Taco Town', 'Mexican'), ('Carbonara Corner', 'Italian'), ('Chole Bhature Bistro', 'Indian'), ('Sukiyaki Stop', 'Japanese'), ('Mexican Magic', 'Mexican'), ('Calzone Corner', 'Italian'), ('Chana Chaat Cafe', 'Indian'); -- Create the dishes table with auto-incrementing ID and foreign key to restaurants CREATE TABLE dishes ( dish_id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, restaurant_id INT NOT NULL, FOREIGN KEY (restaurant_id) REFERENCES restaurants(restaurant_id) ); -- Insert data into dishes table INSERT INTO dishes (name, description, price, restaurant_id) VALUES -- The Gourmet Hub (Italian) ('Margherita Pizza', 'Classic pizza with tomato and mozzarella', 10.00, 1), ('Lasagna', 'Layers of pasta with meat and cheese', 13.00, 1), ('Gnocchi', 'Soft potato dumplings with marinara sauce', 11.00, 1), ('Risotto', 'Creamy rice dish with mushrooms', 14.00, 1), ('Tiramisu', 'Coffee-flavored dessert with mascarpone', 6.00, 1), -- Spice Avenue (Indian) ('Chicken Tikka', 'Spicy grilled chicken pieces', 8.00, 2), ('Butter Chicken', 'Rich and creamy chicken curry', 11.00, 2), ('Samosas', 'Fried pastries filled with spiced potatoes', 7.00, 2), ('Paneer Tikka', 'Grilled cheese cubes with spices', 9.00, 2), ('Chana Chaat', 'Spicy chickpea salad', 6.00, 2), -- Sushi Haven (Japanese) ('Sushi Roll', 'Fresh sushi roll with salmon and avocado', 15.00, 3), ('Miso Soup', 'Japanese soup with tofu and seaweed', 5.00, 3), ('Soba Noodles', 'Buckwheat noodles with soy sauce and vegetables', 10.00, 3), ('Tempura', 'Crispy battered seafood and vegetables', 12.00, 3), ('Sashimi', 'Fresh raw fish slices', 14.00, 3), -- Taco Palace (Mexican) ('Beef Tacos', 'Tacos with seasoned beef and fresh toppings', 9.00, 4), ('Quesadilla', 'Cheese-filled tortilla with salsa', 8.00, 4), ('Nachos', 'Tortilla chips with melted cheese and jalapeños', 8.00, 4), ('Chimichangas', 'Deep-fried burritos with various fillings', 11.00, 4), ('Taco Salad', 'Salad with taco ingredients', 10.00, 4), -- Pasta Paradise (Italian) ('Spaghetti Carbonara', 'Creamy pasta with pancetta and parmesan', 12.00, 5), ('Fettuccine Alfredo', 'Pasta in a creamy parmesan sauce', 12.00, 5), ('Pesto Pasta', 'Pasta with basil pesto sauce', 11.00, 5), ('Penne Arrabbiata', 'Spicy pasta with garlic and chili', 10.00, 5), ('Ravioli', 'Stuffed pasta with a choice of filling', 13.00, 5), -- Curry House (Indian) ('Butter Chicken', 'Rich and creamy chicken curry', 11.00, 6), ('Chicken Curry', 'Spicy chicken curry with rice', 11.00, 6), ('Palak Paneer', 'Spinach and cheese curry', 10.00, 6), ('Aloo Gobi', 'Potato and cauliflower curry', 9.00, 6), ('Chole Bhature', 'Spicy chickpea curry with deep-fried bread', 8.00, 6), -- Ramen Junction (Japanese) ('Ramen', 'Noodle soup with pork and vegetables', 12.00, 7), ('Udon Noodles', 'Thick wheat noodles in a savory broth', 11.00, 7), ('Gyoza', 'Japanese dumplings with pork and vegetables', 9.00, 7), ('Katsu Curry', 'Breaded pork cutlet with curry sauce', 13.00, 7), ('Okonomiyaki', 'Japanese savory pancake with various toppings', 12.00, 7), -- Burrito Bandits (Mexican) ('Burrito', 'Flour tortilla filled with meat, beans, and rice', 10.00, 8), ('Chimichangas', 'Deep-fried burritos with various fillings', 11.00, 8), ('Quesadilla', 'Cheese-filled tortilla with salsa', 8.00, 8), ('Burrito Bowl', 'Deconstructed burrito with rice and toppings', 10.00, 8), ('Enchiladas', 'Tortillas stuffed with meat and cheese', 10.00, 8), -- Pizza World (Italian) ('Margherita Pizza', 'Classic pizza with tomato and mozzarella', 10.00, 9), ('Pepperoni Pizza', 'Pizza topped with pepperoni slices', 12.00, 9), ('Vegetarian Pizza', 'Pizza with a variety of vegetables', 11.00, 9), ('BBQ Chicken Pizza', 'Pizza with barbecue chicken and onions', 13.00, 9), ('Four Cheese Pizza', 'Pizza with a blend of four cheeses', 14.00, 9), -- Chaat Corner (Indian) ('Pani Puri', 'Crispy puris filled with spicy water and tamarind', 7.00, 10), ('Bhel Puri', 'Puffed rice with vegetables and tamarind chutney', 6.00, 10), ('Dahi Puri', 'Crispy puris filled with yogurt and chutneys', 7.00, 10), ('Sev Puri', 'Puris topped with spiced potatoes and chutneys', 6.00, 10), ('Pav Bhaji', 'Spicy vegetable mash served with bread', 8.00, 10), -- Sashimi SPO(Japanese) ('Sashimi Platter', 'Assortment of fresh raw fish slices', 18.00, 11), ('Nigiri Sushi', 'Hand-formed sushi with raw fish', 15.00, 11), ('Miso Soup', 'Japanese soup with tofu and seaweed', 5.00, 11), ('Edamame', 'Steamed young soybeans with sea salt', 6.00, 11), ('Yakitori', 'Grilled chicken skewers with tare sauce', 9.00, 11), -- Fajita Fiesta (Mexican) ('Beef Fajitas', 'Sizzling beef strips with peppers and onions', 12.00, 12), ('Chicken Fajitas', 'Sizzling chicken strips with peppers and onions', 11.00, 12), ('Vegetarian Fajitas', 'Sizzling vegetables with peppers and onions', 10.00, 12), ('Fajita Nachos', 'Nachos topped with fajita meat and cheese', 11.00, 12), ('Fajita Quesadilla', 'Quesadilla filled with fajita meat and cheese', 12.00, 12), -- Lasagna Land (Italian) ('Lasagna', 'Layers of pasta with meat and cheese', 13.00, 13), ('Baked Ziti', 'Pasta baked with cheese and marinara sauce', 12.00, 13), ('Stuffed Shells', 'Pasta shells filled with ricotta and spinach', 11.00, 13), ('Eggplant Parmesan', 'Breaded eggplant slices baked with marinara sauce', 12.00, 13), ('Fettuccine Alfredo', 'Pasta in a creamy parmesan sauce', 12.00, 13), -- Biryani Bistro (Indian) ('Chicken Biryani', 'Spicy rice dish with chicken', 14.00, 14), ('Lamb Biryani', 'Spicy rice dish with lamb', 15.00, 14), ('Vegetable Biryani', 'Spicy rice dish with mixed vegetables', 13.00, 14), ('Hyderabadi Biryani', 'Traditional Hyderabadi style biryani', 16.00, 14), ('Kachori', 'Spicy stuffed pastries', 8.00, 14), -- Tempura Tavern (Japanese) ('Tempura Platter', 'Assorted tempura with dipping sauce', 14.00, 15), ('Shrimp Tempura', 'Battered and fried shrimp', 12.00, 15), ('Vegetable Tempura', 'Battered and fried vegetables', 11.00, 15), ('Tempura Udon', 'Udon noodles topped with tempura', 13.00, 15), ('Tempura Donburi', 'Rice bowl topped with tempura', 14.00, 15), -- Enchilada Express (Mexican) ('Beef Enchiladas', 'Corn tortillas stuffed with beef and cheese', 11.00, 16), ('Chicken Enchiladas', 'Corn tortillas stuffed with chicken and cheese', 11.00, 16), ('Vegetarian Enchiladas', 'Corn tortillas stuffed with vegetables and cheese', 10.00, 16), ('Enchilada Plate', 'Three enchiladas with rice and beans', 12.00, 16), ('Enchilada Burrito', 'Burrito topped with enchilada sauce and cheese', 12.00, 16), -- Gnocchi Grove (Italian) ('Gnocchi with Marinara', 'Potato gnocchi with marinara sauce', 11.00, 17), ('Gnocchi with Pesto', 'Potato gnocchi with basil pesto', 12.00, 17), ('Gnocchi al Forno', 'Baked gnocchi with cheese', 13.00, 17), ('Gnocchi with Sage Butter', 'Gnocchi sautéed with sage butter', 12.00, 17), ('Gnocchi Bolognese', 'Gnocchi with a rich meat sauce', 14.00, 17), -- Samosa Street (Indian) ('Vegetable Samosas', 'Fried pastries filled with spiced vegetables', 7.00, 18), ('Paneer Samosas', 'Fried pastries filled with spiced cheese', 8.00, 18), ('Lamb Samosas', 'Fried pastries filled with spiced lamb', 8.00, 18), ('Samosa Chaat', 'Samosas topped with yogurt and chutneys', 8.00, 18), ('Aloo Tikki', 'Spicy potato fritters', 6.00, 18), -- Miso Master (Japanese) ('Miso Soup', 'Japanese soup with tofu and seaweed', 5.00, 19), ('Miso Ramen', 'Ramen with miso broth', 12.00, 19), ('Miso Grilled Fish', 'Grilled fish marinated in miso', 15.00, 19), ('Miso Glazed Chicken', 'Chicken glazed with miso sauce', 13.00, 19), ('Miso Eggplant', 'Eggplant with miso sauce', 11.00, 19), -- Nacho Nirvana (Mexican) ('Classic Nachos', 'Tortilla chips with cheese and jalapeños', 8.00, 20), ('Loaded Nachos', 'Tortilla chips with beef, cheese, and sour cream', 10.00, 20), ('Vegetarian Nachos', 'Tortilla chips with cheese and vegetables', 8.00, 20), ('Nacho Supreme', 'Nachos with various toppings and sauces', 11.00, 20), ('Nacho Salad', 'Salad with nacho toppings', 9.00, 20), -- Tiramisu Treat (Italian) ('Classic Tiramisu', 'Coffee-flavored dessert with mascarpone', 6.00, 21), ('Tiramisu Sundae', 'Tiramisu layered with ice cream', 7.00, 21), ('Tiramisu Cheesecake', 'Cheesecake with tiramisu flavor', 8.00, 21), ('Tiramisu Cupcakes', 'Cupcakes with tiramisu flavor', 6.00, 21), ('Tiramisu Trifle', 'Layered tiramisu dessert', 7.00, 21), -- Curry Canteen (Indian) ('Chicken Curry', 'Spicy chicken curry with rice', 11.00, 22), ('Vegetable Curry', 'Mixed vegetables in a spiced curry sauce', 10.00, 22), ('Paneer Butter Masala', 'Paneer cubes in a creamy tomato sauce', 12.00, 22), ('Dal Tadka', 'Spiced lentil curry', 9.00, 22), ('Raita', 'Yogurt-based side dish with spices', 5.00, 22), -- Soba Station (Japanese) ('Soba Noodles', 'Buckwheat noodles with soy sauce and vegetables', 10.00, 23), ('Soba Salad', 'Cold soba noodles with vegetables', 9.00, 23), ('Soba Soup', 'Soba noodles in a savory broth', 11.00, 23), ('Tempura Soba', 'Soba noodles with tempura', 12.00, 23), ('Soba Stir-Fry', 'Stir-fried soba noodles with vegetables', 11.00, 23), -- Quesadilla Queen (Mexican) ('Cheese Quesadilla', 'Cheese-filled tortilla with salsa', 8.00, 24), ('Chicken Quesadilla', 'Chicken and cheese-filled tortilla', 9.00, 24), ('Beef Quesadilla', 'Beef and cheese-filled tortilla', 9.00, 24), ('Vegetable Quesadilla', 'Vegetable and cheese-filled tortilla', 8.00, 24), ('Quesadilla Platter', 'Assorted quesadillas with sides', 10.00, 24), -- Fettuccine Feast (Italian) ('Fettuccine Alfredo', 'Pasta in a creamy parmesan sauce', 12.00, 25), ('Fettuccine Carbonara', 'Pasta with pancetta and cream sauce', 13.00, 25), ('Fettuccine Bolognese', 'Pasta with a rich meat sauce', 14.00, 25), ('Fettuccine Pesto', 'Pasta with basil pesto', 12.00, 25), ('Fettuccine Primavera', 'Pasta with mixed vegetables in a light sauce', 11.00, 25), -- Masala Magic (Indian) ('Chicken Masala', 'Spicy chicken curry with rich gravy', 11.00, 26), ('Paneer Masala', 'Cheese cubes in a spicy tomato sauce', 10.00, 26), ('Aloo Masala', 'Spiced potatoes with curry sauce', 9.00, 26), ('Chana Masala', 'Spiced chickpeas with tomato gravy', 8.00, 26), ('Dal Makhani', 'Creamy lentil curry', 10.00, 26), -- Sushi Samurai (Japanese) ('Sushi Roll', 'Fresh sushi roll with salmon and avocado', 15.00, 27), ('Nigiri', 'Hand-formed sushi with raw fish', 14.00, 27), ('California Roll', 'Sushi roll with crab, avocado, and cucumber', 13.00, 27), ('Spicy Tuna Roll', 'Spicy tuna roll with cucumber', 15.00, 27), ('Tempura Roll', 'Sushi roll with tempura and avocado', 14.00, 27), -- Burrito Bliss (Mexican) ('Burrito', 'Flour tortilla filled with meat, beans, and rice', 10.00, 28), ('Chicken Burrito', 'Flour tortilla filled with chicken, beans, and rice', 10.00, 28), ('Beef Burrito', 'Flour tortilla filled with beef, beans, and rice', 11.00, 28), ('Vegetarian Burrito', 'Flour tortilla filled with vegetables, beans, and rice', 9.00, 28), ('Burrito Bowl', 'Deconstructed burrito with rice and toppings', 10.00, 28), -- Risotto Royale (Italian) ('Mushroom Risotto', 'Creamy risotto with mushrooms', 14.00, 29), ('Seafood Risotto', 'Creamy risotto with seafood', 16.00, 29), ('Pumpkin Risotto', 'Creamy risotto with pumpkin and sage', 14.00, 29), ('Chicken Risotto', 'Creamy risotto with chicken', 15.00, 29), ('Truffle Risotto', 'Creamy risotto with truffle oil', 18.00, 29), -- Paneer Palace (Indian) ('Paneer Tikka', 'Grilled cheese cubes with spices', 9.00, 30), ('Paneer Butter Masala', 'Paneer cubes in a creamy tomato sauce', 12.00, 30), ('Palak Paneer', 'Spinach and cheese curry', 10.00, 30), ('Paneer Pakora', 'Fried paneer cubes with spiced batter', 8.00, 30), ('Paneer Korma', 'Paneer in a rich, spiced curry', 11.00, 30), -- Katsu Kingdom (Japanese) ('Katsu Curry', 'Breaded pork cutlet with curry sauce', 13.00, 31), ('Chicken Katsu', 'Breaded and fried chicken cutlet', 12.00, 31), ('Pork Katsu', 'Breaded and fried pork cutlet', 13.00, 31), ('Katsu Don', 'Rice bowl with breaded pork cutlet', 14.00, 31), ('Katsu Sandwich', 'Breaded pork cutlet in a sandwich', 11.00, 31), -- Chimichanga Chalet (Mexican) ('Chimichangas', 'Deep-fried burritos with various fillings', 11.00, 32), ('Beef Chimichangas', 'Deep-fried burritos filled with beef', 12.00, 32), ('Chicken Chimichangas', 'Deep-fried burritos filled with chicken', 12.00, 32), ('Vegetarian Chimichangas', 'Deep-fried burritos filled with vegetables', 11.00, 32), ('Chimichanga Plate', 'Chimichangas served with rice and beans', 12.00, 32), -- Ravioli Rendezvous (Italian) ('Ravioli', 'Stuffed pasta with a choice of filling', 13.00, 33), ('Lobster Ravioli', 'Ravioli stuffed with lobster and ricotta', 15.00, 33), ('Spinach Ravioli', 'Ravioli filled with spinach and cheese', 12.00, 33), ('Mushroom Ravioli', 'Ravioli filled with mushrooms and cheese', 13.00, 33), ('Pumpkin Ravioli', 'Ravioli filled with pumpkin and spices', 14.00, 33), -- Dosa Delight (Indian) ('Masala Dosa', 'Crispy crepe filled with spiced potatoes', 7.00, 34), ('Plain Dosa', 'Crispy crepe served with chutneys', 6.00, 34), ('Onion Dosa', 'Crispy crepe with onions', 7.00, 34), ('Rawa Dosa', 'Crispy crepe made with semolina', 8.00, 34), ('Paper Dosa', 'Extra-thin and crispy dosa', 9.00, 34), -- Udon Universe (Japanese) ('Udon Noodles', 'Thick wheat noodles in a savory broth', 11.00, 35), ('Yaki Udon', 'Stir-fried udon noodles with vegetables', 12.00, 35), ('Udon Soup', 'Udon noodles in a rich broth', 11.00, 35), ('Tempura Udon', 'Udon noodles topped with tempura', 13.00, 35), ('Spicy Udon', 'Udon noodles with spicy broth', 12.00, 35), -- Spaghetti Sphere (Italian) ('Spaghetti Bolognese', 'Pasta with a rich meat sauce', 12.00, 36), ('Spaghetti Aglio e Olio', 'Pasta with garlic and olive oil', 10.00, 36), ('Spaghetti Carbonara', 'Creamy pasta with pancetta and parmesan', 12.00, 36), ('Spaghetti Marinara', 'Pasta with a seafood marinara sauce', 14.00, 36), ('Spaghetti Puttanesca', 'Pasta with tomatoes, olives, and capers', 11.00, 36), -- Chicken Tikka Tavern (Indian) ('Chicken Tikka', 'Spicy grilled chicken pieces', 8.00, 37), ('Chicken Tikka Masala', 'Grilled chicken in a spiced tomato sauce', 11.00, 37), ('Tandoori Chicken', 'Chicken marinated in yogurt and spices', 12.00, 37), ('Chicken Seekh Kebab', 'Ground chicken kebabs with spices', 9.00, 37), ('Chicken Korma', 'Chicken in a creamy, spiced curry', 10.00, 37), -- Sushi Sushi (Japanese) ('Sushi Roll', 'Fresh sushi roll with salmon and avocado', 15.00, 38), ('Nigiri Sushi', 'Hand-formed sushi with raw fish', 14.00, 38), ('California Roll', 'Sushi roll with crab, avocado, and cucumber', 13.00, 38), ('Spicy Tuna Roll', 'Spicy tuna roll with cucumber', 15.00, 38), ('Tempura Roll', 'Sushi roll with tempura and avocado', 14.00, 38), -- Baja Burrito (Mexican) ('Burrito', 'Flour tortilla filled with meat, beans, and rice', 10.00, 39), ('Beef Burrito', 'Flour tortilla filled with beef, beans, and rice', 11.00, 39), ('Chicken Burrito', 'Flour tortilla filled with chicken, beans, and rice', 10.00, 39), ('Vegetarian Burrito', 'Flour tortilla filled with vegetables, beans, and rice', 9.00, 39), ('Burrito Bowl', 'Deconstructed burrito with rice and toppings', 10.00, 39), -- Pesto Place (Italian) ('Pesto Pasta', 'Pasta with basil pesto sauce', 11.00, 40), ('Pesto Chicken', 'Chicken with basil pesto sauce', 13.00, 40), ('Pesto Pizza', 'Pizza with basil pesto and cheese', 12.00, 40), ('Pesto Risotto', 'Creamy risotto with basil pesto', 14.00, 40), ('Pesto Bruschetta', 'Toasted bread with pesto and tomatoes', 8.00, 40), -- Vada Pav Village (Indian) ('Vada Pav', 'Spicy potato fritter in a bun', 4.00, 41), ('Pav Bhaji', 'Spicy vegetable mash served with bread', 8.00, 41), ('Misal Pav', 'Spicy curry with bread', 7.00, 41), ('Bhaji Pav', 'Vegetable curry with bread', 6.00, 41), ('Batata Vada', 'Spiced potato fritters', 5.00, 41), -- Sashimi Sushi (Japanese) ('Sashimi Platter', 'Assortment of fresh raw fish slices', 18.00, 42), ('Nigiri', 'Hand-formed sushi with raw fish', 14.00, 42), ('Miso Soup', 'Japanese soup with tofu and seaweed', 5.00, 42), ('Edamame', 'Steamed young soybeans with sea salt', 6.00, 42), ('Yakitori', 'Grilled chicken skewers with tare sauce', 9.00, 42), -- Taco Town (Mexican) ('Beef Tacos', 'Tacos with seasoned beef and fresh toppings', 9.00, 43), ('Chicken Tacos', 'Tacos with seasoned chicken and fresh toppings', 9.00, 43), ('Vegetarian Tacos', 'Tacos with vegetables and cheese', 8.00, 43), ('Fish Tacos', 'Tacos with battered fish and slaw', 10.00, 43), ('Taco Salad', 'Salad with taco ingredients', 10.00, 43), -- Carbonara Corner (Italian) ('Carbonara Pasta', 'Pasta with a creamy egg and cheese sauce', 13.00, 44), ('Carbonara Pizza', 'Pizza with carbonara sauce and bacon', 12.00, 44), ('Carbonara Risotto', 'Creamy risotto with carbonara sauce', 14.00, 44), ('Carbonara Stuffed Mushrooms', 'Mushrooms stuffed with carbonara mix', 11.00, 44), ('Carbonara Frittata', 'Egg frittata with carbonara ingredients', 12.00, 44), -- Chole Bhature Bistro (Indian) ('Chole Bhature', 'Spicy chickpea curry with deep-fried bread', 8.00, 45), ('Bhature', 'Deep-fried bread', 4.00, 45), ('Chole', 'Spicy chickpea curry', 7.00, 45), ('Aloo Bhature', 'Deep-fried bread with spiced potatoes', 6.00, 45), ('Chole Bhature Plate', 'Chole and Bhature served together', 10.00, 45);

Now, lets create the UI pages using thymeleaf which is placed under src/main/resources/templates folder

index.html

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Restaurant and Dish Management</title> <style> /* General body styling */ body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; text-align: center; } /* Container styling */ .container { width: 80%; margin: 20px auto; padding: 20px; background-color: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); border-radius: 8px; } /* Header styling */ h1 { color: #333; margin-top: 0; } /* Navigation links styling */ .nav-links { list-style: none; padding: 0; } .nav-links li { display: inline; margin: 0 10px; } .nav-links a { text-decoration: none; color: #007bff; font-size: 18px; } .nav-links a:hover { text-decoration: underline; } /* Button styling */ .btn { display: inline-block; padding: 10px 20px; margin: 10px; font-size: 16px; color: #fff; border: none; border-radius: 5px; text-decoration: none; text-align: center; } .btn:hover { background-color: #f0f0f0; text-decoration: none; } </style> </head> <body> <div class="container"> <h1>Welcome to Restaurant and Dish Management</h1> <!-- Navigation Links --> <ul class="nav-links"> <li> <a class="btn" th:href="@{/restaurants}">View Restaurants</a> </li> <li> <a class="btn" th:href="@{/dishes}">View Dishes</a> </li> </ul> </div> </body> </html>

restaurants.html

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Restaurants</title> <style> /* General body styling */ body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; } /* Container styling */ .container { width: 80%; margin: 20px auto; padding: 20px; background-color: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); border-radius: 8px; } /* Header styling */ h1 { text-align: center; color: #333; margin-top: 0; } /* Table styling */ table { width: 100%; border-collapse: collapse; margin-bottom: 20px; } table, th, td { border: 1px solid #ddd; } th, td { padding: 10px; text-align: left; } th { background-color: #f4f4f4; } /* Set the first column to 70% and the second column to 30% */ th:nth-child(1), td:nth-child(1) { width: 70%; /* 70% width for the first column */ } th:nth-child(2), td:nth-child(2) { width: 30%; /* 30% width for the second column */ } /* Link styling */ a { text-decoration: none; color: #007bff; } a:hover { text-decoration: underline; } /* Pagination controls */ .pagination { text-align: center; margin-top: 20px; } .pagination a { margin: 0 5px; padding: 10px 15px; border: 1px solid #ddd; border-radius: 4px; background-color: #fff; color: #007bff; } .pagination a.active { background-color: #007bff; color: white; } .pagination a:hover { background-color: #f0f0f0; } .pagination span { margin: 0 5px; font-size: 16px; } /* Back to Home button styling */ .back-button { text-align: right; margin: 20px 0; } .back-button a { display: inline-block; padding: 10px 20px; border: 1px solid #ddd; border-radius: 4px; background-color: #007bff; color: #fff; text-decoration: none; font-size: 16px; } .back-button a:hover { background-color: #0056b3; } </style> </head> <body> <div class="container"> <!-- Back to Home button --> <div class="back-button"> <a th:href="@{/}">Back to Home</a> </div> <h1>Restaurants</h1> <!-- Table for Displaying Restaurants --> <table> <thead> <tr> <th> <a th:href="@{/restaurants(page=${currentPage}, size=${size}, sort=${sort == 'name' ? 'name' : 'name'}, direction=${direction == 'asc' ? 'desc' : 'asc'})}"> Name </a> </th> <th> <a th:href="@{/restaurants(page=${currentPage}, size=${size}, sort=${sort == 'cuisine' ? 'cuisine' : 'cuisine'}, direction=${direction == 'asc' ? 'desc' : 'asc'})}"> Cuisine </a> </th> </tr> </thead> <tbody> <tr th:each="restaurant : ${restaurantPage.content}"> <td> <a th:href="@{/dishes(restaurantId=${restaurant.id})}"> <span th:text="${restaurant.name}"></span> </a> </td> <td th:text="${restaurant.cuisine}"></td> </tr> </tbody> </table> <!-- Pagination Controls --> <div class="pagination"> <span th:if="${currentPage > 0}"> <a th:href="@{/restaurants(page=${currentPage - 1}, size=${size}, sort=${sort}, direction=${direction})}">Previous</a> </span> <span th:if="${currentPage < totalPages - 1}"> <a th:href="@{/restaurants(page=${currentPage + 1}, size=${size}, sort=${sort}, direction=${direction})}">Next</a> </span> <span>Page <span th:text="${currentPage + 1}"></span> of <span th:text="${totalPages}"></span></span> </div> </div> </body> </html>

dishes.html

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Dishes</title> <style> /* General body styling */ body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; } /* Container styling */ .container { width: 80%; margin: 20px auto; padding: 20px; background-color: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); border-radius: 8px; } /* Header styling */ h1 { text-align: center; color: #333; margin-top: 0; } /* Form styling */ form { margin-bottom: 20px; text-align: center; } select { padding: 10px; font-size: 16px; border: 1px solid #ddd; border-radius: 4px; margin: 0 5px; } /* Table styling */ table { width: 100%; border-collapse: collapse; margin-bottom: 20px; } table, th, td { border: 1px solid #ddd; } th, td { padding: 10px; text-align: left; } th { background-color: #f4f4f4; } /* Link styling */ a { text-decoration: none; color: #007bff; } a:hover { text-decoration: underline; } /* Pagination controls */ .pagination { text-align: center; margin-top: 20px; } .pagination a { margin: 0 5px; padding: 10px 15px; border: 1px solid #ddd; border-radius: 4px; background-color: #fff; color: #007bff; } .pagination a.active { background-color: #007bff; color: white; } .pagination a:hover { background-color: #f0f0f0; } .pagination span { margin: 0 5px; font-size: 16px; } /* Back to Home button styling */ .back-button { text-align: right; margin: 20px 0; } .back-button a { display: inline-block; padding: 10px 20px; border: 1px solid #ddd; border-radius: 4px; background-color: #007bff; color: #fff; text-decoration: none; font-size: 16px; } .back-button a:hover { background-color: #0056b3; } </style> </head> <body> <div class="container"> <!-- Back to Home button --> <div class="back-button"> <a th:href="@{/}">Back to Home</a> </div> <h1>Dishes</h1> <!-- Form for Filtering by Restaurant --> <form id="filterForm" method="get" action="/dishes"> <label for="restaurantId">Select Restaurant:</label> <select name="restaurantId" id="restaurantId"> <option value="0" th:selected="${restaurantId == 0}">All</option> <!-- Populate the restaurant options dynamically --> <option th:each="restaurant : ${restaurants}" th:value="${restaurant.id}" th:text="${restaurant.name}" th:selected="${restaurant.id == restaurantId}"> </option> </select> </form> <!-- Table for Displaying Dishes --> <table> <thead> <tr> <th> <a th:href="@{/dishes(page=${currentPage}, size=${size}, sort=${sort}, direction=${direction == 'asc' ? 'desc' : 'asc'}, restaurantId=${restaurantId})}"> Name </a> </th> <th> <a th:href="@{/dishes(page=${currentPage}, size=${size}, sort=${sort}, direction=${direction == 'asc' ? 'desc' : 'asc'}, restaurantId=${restaurantId})}"> Price </a> </th> <th>Actions</th> </tr> </thead> <tbody> <tr th:each="dish : ${dishPage.content}"> <td th:text="${dish.name}"></td> <td th:text="${dish.price}"></td> <td> <!-- Anchor tag to trigger deletion --> <a th:href="@{/dishes/delete/{id}(id=${dish.id}, restaurantId=${restaurantId})}" onclick="return confirm('Are you sure you want to delete this dish?');">Delete</a> </td> </tr> </tbody> </table> <!-- Pagination Controls --> <div class="pagination"> <span th:if="${currentPage > 0}"> <a th:href="@{/dishes(page=${currentPage - 1}, size=${size}, sort=${sort}, direction=${direction}, restaurantId=${restaurantId})}">Previous</a> </span> <span th:if="${currentPage < totalPages - 1}"> <a th:href="@{/dishes(page=${currentPage + 1}, size=${size}, sort=${sort}, direction=${direction}, restaurantId=${restaurantId})}">Next</a> </span> <span>Page <span th:text="${currentPage + 1}"></span> of <span th:text="${totalPages}"></span></span> </div> </div> <!-- JavaScript for automatic filtering --> <script> document.getElementById('restaurantId').addEventListener('change', function () { // Submit the form when the dropdown value changes document.getElementById('filterForm').submit(); }); </script> </body> </html>

Now lets open http://localhost:8080

Home Page

screenshot of the app

Restaurants Page

screenshot of the app

Dishes Page

screenshot of the app