Skip to content

Ryuma-sudo/Web_lab_7

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Product Management System

Student Information

  • Name: Nguyễn Quang Trực
  • Student ID: ITCSIU23041
  • Class: Group 2

Technologies Used

  • Spring Boot 3.3.x
  • Spring Data JPA
  • MySQL 8.0
  • Thymeleaf
  • Maven

Setup Instructions

  1. Import project into VS Code
  2. Create database: product_management
  3. Update application.properties with your MySQL credentials
  4. Run: mvn spring-boot:run
  5. Open browser: http://localhost:8080/products

Completed Features

  • CRUD operations
  • Search functionality
  • Advanced search with filters
  • Validation
  • Sorting
  • Pagination
  • Statistics Dashboard
  • REST API (Bonus)

Project Structure

product-management/
│
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/product_management/
│   │   │       ├── ProductManagementApplication.java (Main)
│   │   │       ├── entity/
│   │   │       │   └── Product.java
│   │   │       ├── repository/
│   │   │       │   └── ProductRepository.java
│   │   │       ├── service/
│   │   │       │   ├── ProductService.java
│   │   │       │   └── ProductServiceImpl.java
│   │   │       └── controller/
│   │   │           ├── ProductController.java
│   │   │           └── DashboardController.java 
│   │   │
│   │   └── resources/
│   │       ├── application.properties
│   │       ├── static/
│   │       │   └── css/
│   │       └── templates/
│   │           ├── product-list.html
│   │           ├── product-form.html
│   │           └── dashboard.html        
│   │
│   └── test/
│       └── java/
│
├── pom.xml (Maven dependencies)
└── README.md

Code Flow

1. List Functionality (Read Flow)

Objective: Retrieve and display the complete list of products.

  1. Request Initiation: The flow begins when a GET request is made to the root path /products.

  2. Controller Layer (ProductController.java): The listProducts method intercepts the request. It calls the service layer to fetch data and adds it to the Model for rendering.

    // Web_lab_7/src/main/java/com/example/product_management/controller/ProductController.java
    @GetMapping
    public String listProducts(Model model) {
        // Retrieve all products from the service layer
        List<Product> products = productService.getAllProducts(); 
        // Bind the list to the model with key "products"
        model.addAttribute("products", products);
        // Return the view name "product-list.html"
        return "product-list";
    }
  3. Service Layer (ProductServiceImpl.java): The getAllProducts method delegates the data retrieval to the repository.

    // Web_lab_7/src/main/java/com/example/product_management/service/ProductServiceImpl.java
    @Override
    public List<Product> getAllProducts() {
        // Execute SELECT * FROM products via JPA
        return productRepository.findAll();
    }
  4. View Layer (product-list.html): Thymeleaf iterates over the products list to render the table rows.

    <tr th:each="product : ${products}">
        <td th:text="${product.id}">...</td>
        </tr>
image

2. Create Functionality (Create Flow)

Objective: Instantiate and persist a new Product entity.

Phase 1: Form Display (GET)

  1. Controller: The showNewForm method maps to /products/new. It creates an empty Product instance to bind form data.
    // Web_lab_7/src/main/java/com/example/product_management/controller/ProductController.java
    @GetMapping("/new")
    public String showNewForm(Model model) {
        // Create empty entity for form binding
        Product product = new Product();
        model.addAttribute("product", product);
        return "product-form";
    }

Phase 2: Data Submission (POST)

  1. Controller: The saveProduct method handles the POST request to /products/save.
  2. Service & Repository: Since the id of the new product is null, JpaRepository.save() identifies this as an INSERT operation.
    // Web_lab_7/src/main/java/com/example/product_management/service/ProductServiceImpl.java
    @Override
    public Product saveProduct(Product product) {
        // Persist the entity (INSERT if ID is null)
        return productRepository.save(product);
    }
image image

3. Update Functionality (Update Flow)

Objective: Modify an existing Product entity. This reuses logic from the Create flow but with a populated ID.

Phase 1: Form Pre-filling (GET)

  1. Controller: The showEditForm method accepts the id via the URL path /products/edit/{id}.
  2. Service: It attempts to find the existing product.
    // Web_lab_7/src/main/java/com/example/product_management/controller/ProductController.java
    @GetMapping("/edit/{id}")
    public String showEditForm(@PathVariable Long id, Model model, ...) {
        return productService.getProductById(id)
                .map(product -> {
                    // Add existing product to model to pre-fill inputs
                    model.addAttribute("product", product);
                    return "product-form";
                })
                .orElseGet(() -> {
                    // Handle non-existent ID
                    return "redirect:/products";
                });
    }

Phase 2: Data Submission (POST)

  1. View: The form includes a hidden input for the ID.

    <input type="hidden" th:field="*{id}" />
  2. Controller & Service: The form submits to the same /products/save endpoint. Because the product object now contains a non-null id, the save() method in the repository executes an UPDATE SQL statement instead of an INSERT.

image image

4. Delete Functionality (Delete Flow)

Objective: Remove a specific product from the database.

  1. Request Initiation: The user clicks "Delete," triggering a GET request to /products/delete/{id}.

  2. Controller: The deleteProduct method extracts the id and delegates to the service.

    // Web_lab_7/src/main/java/com/example/product_management/controller/ProductController.java
    @GetMapping("/delete/{id}")
    public String deleteProduct(@PathVariable Long id, RedirectAttributes redirectAttributes) {
        try {
            // Initiate delete operation
            productService.deleteProduct(id);
            // ... (flash message logic)
        } catch (Exception e) {
            // ... (error handling)
        }
        return "redirect:/products";
    }
  3. Service Layer: The service method calls the repository's void delete method.

    // Web_lab_7/src/main/java/com/example/product_management/service/ProductServiceImpl.java
    @Override
    public void deleteProduct(Long id) {
        // Execute DELETE FROM products WHERE id = ?
        productRepository.deleteById(id);
    }
image image image

5. Advanced Search Functionality (Search Flow)

Objective: Filter products based on multiple criteria (Name, Category, Price Range) combined.

  1. Request Initiation: The user submits the filter form, sending a GET request to /products/advanced-search with query parameters.

  2. Controller: The advancedSearch method captures optional parameters and sanitizes empty strings to null to ensure the repository query logic works correctly.

    // Web_lab_7/src/main/java/com/example/product_management/controller/ProductController.java
    @GetMapping("/advanced-search")
    public String advancedSearch(@RequestParam(required = false) String name, ... ) {
        // Convert empty strings to null for JPA query compatibility
        if (name != null && name.trim().isEmpty()) name = null;
        // ... (call service)
        List<Product> products = productService.advancedSearch(name, category, minPrice, maxPrice);
        model.addAttribute("products", products);
        return "product-list";
    }
  3. Repository Layer (ProductRepository.java): A custom JPQL @Query handles the multi-criteria logic using check-for-null idioms (:param IS NULL OR field = :param).

    @Query("SELECT p FROM Product p WHERE " +
           "(:name IS NULL OR p.name LIKE %:name%) AND " +
           "(:category IS NULL OR p.category = :category) AND " +
           "(:minPrice IS NULL OR p.price >= :minPrice) AND " +
           "(:maxPrice IS NULL OR p.price <= :maxPrice)")
    List<Product> searchProducts(@Param("name") String name, ...);
image

6. Validation Functionality

Objective: Ensure data integrity by validating user input before saving to the database.

  1. Entity Layer (Product.java): Jakarta Validation annotations are applied to entity fields.

    @NotBlank(message = "Product name is required")
    private String name;
    
    @DecimalMin(value = "0.01", message = "Price must be greater than 0")
    private BigDecimal price;
  2. Controller: The saveProduct method uses @Valid to trigger validation and BindingResult to capture errors.

    @PostMapping("/save")
    public String saveProduct(@Valid @ModelAttribute("product") Product product, 
                              BindingResult result, ...) {
        // If validation fails, return to form with error details
        if (result.hasErrors()) {
            return "product-form";
        }
        // Proceed to save
    }
  3. View Layer (product-form.html): Thymeleaf displays error messages next to invalid fields.

    <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="error-message">Error</span>
image

7. Sorting Functionality

Objective: Allow users to sort the product list by any column (ID, Name, Price, etc.) in Ascending or Descending order.

  1. Request Initiation: Clicking a table header sends a request with sortBy and sortDir parameters (e.g., ?sortBy=price&sortDir=desc).

  2. Controller: The listProducts method converts these strings into a Spring Data Sort object.

    // Web_lab_7/src/main/java/com/example/product_management/controller/ProductController.java
    Sort sort = sortDir.equals("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending();
    // Pass sort object to service
    List<Product> products = productService.getAllProducts(sort);
  3. Service Layer: Passes the Sort object directly to the repository.

    @Override
    public List<Product> getAllProducts(Sort sort) {
        return productRepository.findAll(sort);
    }
image

8. Statistics Dashboard Functionality

Objective: Display high-level metrics (Total Value, Average Price) and alerts (Low Stock).

  1. Request Initiation: A GET request is made to /dashboard.

  2. Controller (DashboardController.java): Calls specialized service methods to aggregate data.

    @GetMapping
    public String showDashboard(Model model) {
        model.addAttribute("totalValue", productService.getTotalValue());
        model.addAttribute("averagePrice", productService.getAveragePrice());
        model.addAttribute("lowStockProducts", productService.getLowStockProducts(10));
        return "dashboard";
    }
  3. Repository Layer: Uses JPQL Aggregate functions (SUM, AVG, COUNT).

    @Query("SELECT COALESCE(SUM(p.price * p.quantity), 0) FROM Product p")
    BigDecimal calculateTotalValue();
    
    @Query("SELECT p FROM Product p WHERE p.quantity < :threshold")
    List<Product> findLowStockProducts(@Param("threshold") int threshold);
image

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors