diff --git a/.gitignore b/.gitignore index 1de5659..794c788 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -target \ No newline at end of file +target +HELP.md \ No newline at end of file diff --git a/Week 02/Lecture 03/Assignment 05/ListToMap.java b/Week 02/Lecture 03/Assignment 05/ListToMap.java index 686f587..de47030 100644 --- a/Week 02/Lecture 03/Assignment 05/ListToMap.java +++ b/Week 02/Lecture 03/Assignment 05/ListToMap.java @@ -1,7 +1,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.TreeMap; // Assume Employee class with fields: int employeeID, String name, String department class Employee { @@ -46,11 +46,15 @@ public static void main(String[] args) { new Employee(3, "Charlie", "Finance") ); - // Convert List to Map using employeeID as key - Map employeeMap = employees.stream() - .collect(Collectors.toMap(Employee::getEmployeeID, emp -> emp)); + // Creating employeeMap using TreeMap + Map employeesMap = new TreeMap<>(); + + // Convert List to Map + for (Employee emp : employees){ + employeesMap.put(emp.getEmployeeID(), emp.getName()); + } // Print the resulting Map - employeeMap.forEach((id, emp) -> System.out.println("Employee ID: " + id + ", Employee: " + emp)); + employeesMap.forEach((id, emp) -> System.out.println("Employee ID: " + id + ", Employee: " + emp)); } } \ No newline at end of file diff --git a/Week 02/Lecture 03/Assignment 05/README.md b/Week 02/Lecture 03/Assignment 05/README.md index 6769ca7..55b8de1 100644 --- a/Week 02/Lecture 03/Assignment 05/README.md +++ b/Week 02/Lecture 03/Assignment 05/README.md @@ -80,7 +80,7 @@ To remove duplicate lines from a file: 4. **Writing Unique Lines**: Write only those lines to a new file that haven't been seen before (not in the `HashSet`). #### πŸ“‹ Case CSV Content -In this program, i use CSV file [`input.csv`](/Week%2002%20-%20Jun%2017-21/Lecture%2003/Assignment%205/data/input.csv) with content like this. +In this program, i use CSV file [`input.csv`](/Week%2002/Lecture%2003/Assignment%2005/RemoveDuplicates.java) with content like this. ```csv employeeID,name,department 1,Alice,HR @@ -106,6 +106,16 @@ Detail implementation is written on [this code](/Week%2002%20-%20Jun%2017-21/Lec The output of the program shows on this [`output.csv`](/Week%2002%20-%20Jun%2017-21/Lecture%2003/Assignment%205/data/output.csv) +Here is how to run the updated code of program +```bash +$ java RemoveDuplicates +``` + +for example. +```bash +java RemoveDuplicates data/input.csv data/output.csv 0 +``` +
### πŸ–¨οΈ Task 4 - Get a Shallow Copy of a `HashMap` @@ -166,7 +176,7 @@ Here i implement class `BankAccount` and `BankAccountDemo`. 3. **Use Java Streams for Transformation**: Utilize Java Streams API to transform the `List` into a `Map`. 4. **Collect into Map**: Use the `Collectors.toMap()` method to collect elements of the `List` into a `Map` using the specified key and value mappings. -Detail implementation is written on [this code](/Week%2002%20-%20Jun%2017-21/Lecture%2003/Assignment%205/ListToMap.java), and the output of the program shows like this. +Detail implementation is written on [this code](/Week%2002/Lecture%2003/Assignment%2005/ListToMap.java), and the output of the program shows like this (updated based on comment). ![Screenshot](img/Task5.png) diff --git a/Week 02/Lecture 03/Assignment 05/RemoveDuplicates.java b/Week 02/Lecture 03/Assignment 05/RemoveDuplicates.java index f364dd0..19287f7 100644 --- a/Week 02/Lecture 03/Assignment 05/RemoveDuplicates.java +++ b/Week 02/Lecture 03/Assignment 05/RemoveDuplicates.java @@ -3,29 +3,42 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; public class RemoveDuplicates { public static void main(String[] args) { - String inputFileName = "data/input.csv"; - String outputFileName = "data/output.csv"; - String delimiter = ","; // Parse CSV format - - // Set to store unique keys (employeeID in this case) + if (args.length < 3) { + System.out.println("Usage: java RemoveDuplicates "); + return; + } + + String inputFileName = args[0]; + String outputFileName = args[1]; + int keyFieldIndex; + try { + keyFieldIndex = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + System.out.println("Invalid keyFieldIndex. It must be an integer."); + return; + } + + // Set to store unique keys Set seenKeys = new HashSet<>(); - + try (BufferedReader reader = new BufferedReader(new FileReader(inputFileName)); PrintWriter writer = new PrintWriter(new FileWriter(outputFileName))) { String line; while ((line = reader.readLine()) != null) { - // Split the line into fields - String[] fields = line.split(delimiter); - - // Ensure there are enough fields and key field is valid - if (fields.length > 1) { - String key = fields[0]; // Assuming employeeID is the first field + // Properly split the line respecting quoted commas + String[] fields = parseCsvLine(line); + + // Ensure key field index is valid + if (fields.length > keyFieldIndex) { + String key = fields[keyFieldIndex]; if (!seenKeys.contains(key)) { seenKeys.add(key); // Add the key to set (marks as seen) writer.println(line); // Write the line to output @@ -36,7 +49,36 @@ public static void main(String[] args) { System.out.println("Duplicates removed successfully. Output written to " + outputFileName); } catch (IOException e) { - System.out.println("I/O Error occured:" + e); + System.out.println("I/O Error occurred: " + e); } } -} + + // Function to parse CSV line while handling commas within quotes + private static String[] parseCsvLine(String line) { + boolean inQuotes = false; + StringBuilder field = new StringBuilder(); + List fields = new ArrayList<>(); + + for (char c : line.toCharArray()) { + switch (c) { + case '"': + inQuotes = !inQuotes; // Toggle the inQuotes flag + break; + case ',': + if (inQuotes) { + field.append(c); // Inside quotes, include comma + } else { + fields.add(field.toString()); + field.setLength(0); // Reset the field buffer + } + break; + default: + field.append(c); // Add character to field buffer + break; + } + } + fields.add(field.toString()); // Add last field + + return fields.toArray(new String[0]); + } +} \ No newline at end of file diff --git a/Week 02/Lecture 03/Assignment 05/img/Task5.png b/Week 02/Lecture 03/Assignment 05/img/Task5.png index ec754bd..620f659 100644 Binary files a/Week 02/Lecture 03/Assignment 05/img/Task5.png and b/Week 02/Lecture 03/Assignment 05/img/Task5.png differ diff --git a/Week 02/Lecture 04/Assignment 06/README.md b/Week 02/Lecture 04/Assignment 06/README.md index 7a36c3c..26cad3a 100644 --- a/Week 02/Lecture 04/Assignment 06/README.md +++ b/Week 02/Lecture 04/Assignment 06/README.md @@ -119,11 +119,22 @@ Here’s a detailed process on how to remove duplicate lines from files based on 4. **Write the Output**: Write the processed, duplicate-free data to a new file. #### πŸ‘¨πŸ»β€πŸ’» Implementation -Detail implementation is written on [this code](/Week%2002%20-%20Jun%2017-21/Lecture%2004/Assignment%206/RemoveDuplicatesCSV.java). Here’s what the program actually done. -1. **Read All Lines**: `Files.readAllLines(Paths.get(inputFilePath))` reads the CSV file into a list of strings. -2. **Extract Header**: The first line is treated as the header to determine the key field's index. -3. **Stream Processing**: The stream skips the header, then collects lines into a map using the key field (`id`). If a duplicate key is found, the first occurrence is retained. -4. **Write Results**: The header is re-added, and the list is written to the new file. +Detail implementation is written on [this code](/Week%2002/Lecture%2004/Assignment%2006/RemoveDuplicatesCSV.java). Here’s what the program actually done (updated based on comment). +1. **Initialize Readers and Writers** + - `BufferedReader` is used to read the input CSV file line by line. + - `BufferedWriter` is used to write the unique lines to the output CSV file. +2. **Extract and Write Header** + - The first line, which is the header, is read using `reader.readLine()`. + - This header is written immediately to the output file using `writer.write(header)`. +3. **Determine Key Field Index** + - The header is split to determine the index of the key field (`id`). + - This is done by iterating through the headers to find the matching field. +4. **Stream Processing and Duplicate Removal** + - A `Set` is used to keep track of the keys that have already been processed. + - For each subsequent line, the key field's value is checked against the `Set`. If the key is unique, the line is written to the output file. +5. **Read and Write Lines** + - The program continues to read each line from the input file, splits it to get the key field, and checks the key against the `Set`. + - If the key is not in the `Set`, the line is written to the output file and the key is added to the `Set`. **Best Practices Highlighted** 1. **`BufferedReader` for Large Files**: Using `BufferedReader` with `lines()` streams data efficiently. diff --git a/Week 02/Lecture 04/Assignment 06/RemoveDuplicatesCSV.java b/Week 02/Lecture 04/Assignment 06/RemoveDuplicatesCSV.java index 77d29de..01e728e 100644 --- a/Week 02/Lecture 04/Assignment 06/RemoveDuplicatesCSV.java +++ b/Week 02/Lecture 04/Assignment 06/RemoveDuplicatesCSV.java @@ -1,10 +1,10 @@ import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import java.util.HashSet; +import java.util.Set; public class RemoveDuplicatesCSV { public static void main(String[] args) { @@ -12,35 +12,44 @@ public static void main(String[] args) { String outputFilePath = "data/unique.csv"; String keyFieldName = "id"; - try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputFilePath))) { - List lines = reader.lines().collect(Collectors.toList()); - if (lines.isEmpty()) return; + try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputFilePath)); + BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputFilePath))) { + + String header = reader.readLine(); + if (header == null) return; - // Extract header and determine the key field index - String header = lines.get(0); - List headers = Arrays.asList(header.split(",")); - int keyIndex = headers.indexOf(keyFieldName); - if (keyIndex == -1) throw new IllegalArgumentException("Invalid key field name"); + // Write header to the output file + writer.write(header); + writer.newLine(); - // Process lines and remove duplicates based on the key field - List uniqueLines = lines.stream() - .skip(1) // Skip header - .collect(Collectors.toMap( - line -> line.split(",")[keyIndex], // Use the key field - line -> line, // Use the line as value - (existing, replacement) -> existing // Keep the first occurrence - )) - .values() - .stream() - .collect(Collectors.toList()); + // Determine the key field index + String[] headers = header.split(","); + int keyIndex = -1; + for (int i = 0; i < headers.length; i++) { + if (headers[i].trim().equals(keyFieldName)) { + keyIndex = i; + break; + } + } + if (keyIndex == -1) throw new IllegalArgumentException("Invalid key field name"); - // Add header back to the list - uniqueLines.add(0, header); + // Use a Set to track unique keys + Set seenKeys = new HashSet<>(); - // Write the results to a new file - Files.write(Paths.get(outputFilePath), uniqueLines); + // Read and process each line + String line; + while ((line = reader.readLine()) != null) { + String[] fields = line.split(","); + if (fields.length > keyIndex) { + String key = fields[keyIndex]; + if (seenKeys.add(key)) { // Add returns false if the key was already present + writer.write(line); + writer.newLine(); + } + } + } } catch (IOException e) { - System.out.println("I/O Error occured:" + e); + System.out.println("I/O Error occurred: " + e); } } -} +} \ No newline at end of file diff --git a/Week 06/Lecture 11/Assignment 01/Lecture 11 - Assignment 01.postman_collection.json b/Week 06/Lecture 11/Assignment 01/Lecture 11 - Assignment 01.postman_collection.json new file mode 100644 index 0000000..0075f54 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/Lecture 11 - Assignment 01.postman_collection.json @@ -0,0 +1,366 @@ +{ + "info": { + "_postman_id": "3093d6b8-a742-4d7c-b565-95715055e5d3", + "name": "Lecture 11 - Assignment 01", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "34693283" + }, + "item": [ + { + "name": "Employees", + "item": [ + { + "name": "All Employees", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employees" + }, + "response": [] + }, + { + "name": "All Employees Configurable Pages", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees?page=1&size=5", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "size", + "value": "5" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee By EmpNo", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employees/3" + }, + "response": [] + }, + { + "name": "New Employee", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"birthDate\": \"2003-08-12\",\r\n \"firstName\": \"Michael\",\r\n \"lastName\": \"Leon\",\r\n \"gender\": \"M\",\r\n \"hireDate\": \"2024-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employees" + }, + "response": [] + }, + { + "name": "Edit Employee", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"birthDate\": \"2003-08-12\",\r\n \"firstName\": \"Leon\",\r\n \"lastName\": \"Michael\",\r\n \"gender\": \"M\",\r\n \"hireDate\": \"2024-07-18\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employees/11" + }, + "response": [] + }, + { + "name": "Delete Employee", + "request": { + "method": "DELETE", + "header": [], + "url": "localhost:8080/api/v1/employees/11" + }, + "response": [] + } + ] + }, + { + "name": "Departments", + "item": [ + { + "name": "All Departments", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/departments" + }, + "response": [] + }, + { + "name": "All Departments Configurable Pages", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/departments?page=0&size=2", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "departments" + ], + "query": [ + { + "key": "page", + "value": "0" + }, + { + "key": "size", + "value": "2" + } + ] + } + }, + "response": [] + }, + { + "name": "Department By DeptNo", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/departments/d004" + }, + "response": [] + }, + { + "name": "New Department", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"deptNo\": \"d006\",\r\n \"deptName\": \"Research\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/departments" + }, + "response": [] + }, + { + "name": "Edit Employee", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"deptNo\": \"d006\",\r\n \"deptName\": \"New Research\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/departments/d006" + }, + "response": [] + }, + { + "name": "Delete Employee", + "request": { + "method": "DELETE", + "header": [], + "url": "localhost:8080/api/v1/departments/d006" + }, + "response": [] + } + ] + }, + { + "name": "Salaries", + "item": [ + { + "name": "Salary by ID", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"fromDate\": \"2000-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + }, + { + "name": "New Salary", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n },\r\n \"salary\": 60000,\r\n \"toDate\": \"2025-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + }, + { + "name": "Edit Salary", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n },\r\n \"salary\": 65000,\r\n \"toDate\": \"2025-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + }, + { + "name": "Delete Salary", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + } + ] + }, + { + "name": "Titles", + "item": [ + { + "name": "Title by ID", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"title\": \"Manager\",\r\n \"fromDate\": \"2000-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + }, + { + "name": "New Title", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 8,\r\n \"title\": \"Engineer\",\r\n \"fromDate\": \"2000-01-01\"\r\n },\r\n \"toDate\": \"2002-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + }, + { + "name": "Edit Title", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 8,\r\n \"title\": \"Engineer\",\r\n \"fromDate\": \"2000-01-01\"\r\n },\r\n \"toDate\": \"2020-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + }, + { + "name": "Delete Title", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/Week 06/Lecture 11/Assignment 01/README.md b/Week 06/Lecture 11/Assignment 01/README.md new file mode 100644 index 0000000..ba34cd6 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/README.md @@ -0,0 +1,375 @@ +# πŸ‘¨πŸ»β€πŸ« Lecture 11 - Spring Data JPA +> This repository is created as a part of assignment for Lecture 11 - Spring Data JPA + +## πŸ“ Assignment 01 - Implementation of Model, JPA, Repositories, Services, and REST APIs + +### πŸ”Ž [Research] Composite Key in JPA + +Implementing a composite key in JPA (Java Persistence API) involves using an `@Embeddable` class to represent the composite key and embedding it into the entity class. Here’s a short explanation and steps to implement it: + +#### Steps to Implement Composite Key in JPA + +1. **Create the Embeddable Key Class**: + - Define a class to represent the composite key. + - Annotate the class with `@Embeddable`. + - Implement `Serializable` interface. + - Override `equals()` and `hashCode()` methods. In this case i'm using using `@Data` and `@EqualsAndHashCode` from Lombok to automatically generate it. + +2. **Embed the Key in the Entity Class**: + - Use `@EmbeddedId` annotation in the entity class to include the composite key. + - Annotate the entity class with `@Entity` and other necessary JPA annotations. + +3. **Map the Composite Key Columns**: + - Map the fields of the embeddable key class to the corresponding columns in the database. + +#### Example + +##### Embeddable Key Class +For this example i will use [SalaryId Class](/Week%2006/Lecture%2011/Assignment%2001/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/SalaryId.java). + +```java +import java.io.Serializable; +import java.time.LocalDate; +import jakarta.persistence.Embeddable; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@Embeddable +@EqualsAndHashCode +public class SalaryId implements Serializable { + private Integer empNo; + private LocalDate fromDate; +} +``` + +##### Entity Class +For this example i will use [Salary Class](/Week%2006/Lecture%2011/Assignment%2001/lecture_11/src/main/java/com/example/lecture_11/data/model/Salary.java). +```java +import java.time.LocalDate; +import com.example.lecture_11.data.model.composite.SalaryId; +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "salaries") +@NoArgsConstructor +@AllArgsConstructor +public class Salary { + + @EmbeddedId + private SalaryId id; + + @Column(nullable = false) + private Integer salary; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} +``` + +#### Explanation + +1. **SalaryId Class**: + - Annotated with `@Embeddable`, indicating it is a composite key. + - Implements `Serializable`. + - Includes necessary fields (`empNo`, `fromDate`) that form the composite key. + - Uses Lombok's `@EqualsAndHashCode` to automatically generate `equals()` and `hashCode()` methods based on the fields of the class. + +2. **Salary Class**: + - Annotated with `@Entity` to indicate it is a JPA entity. + - Uses `@EmbeddedId` to include `SalaryId` as the primary key. + - Defines other entity attributes (`salary`, `toDate`). + +By following these steps, i successfully implement and use composite keys in the JPA entities. + +Using Lombok's `@EqualsAndHashCode` simplifies the code and ensures that the `equals()` and `hashCode()` methods are correctly implemented based on the fields of the composite key class. This approach reduces boilerplate code and makes the implementation cleaner and easier to maintain. + +### 🌳 Project Structure +```bash +lecture_11 +β”œβ”€β”€ .mvn/wrapper/ +β”‚ └── maven-wrapper.properties +β”œβ”€β”€ src/main/ +β”‚ β”œβ”€β”€ java/com/example/lecture_11/ +β”‚ β”‚ β”œβ”€β”€ controller/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentController.java +β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeController.java +β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryController.java +β”‚ β”‚ β”‚ └── TitleController.java +β”‚ β”‚ β”œβ”€β”€ data/ +β”‚ β”‚ β”‚ β”œβ”€β”€ model/ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ composite/ +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptEmpId.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptManagerId.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryId.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── TitleId.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Department.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptEmp.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptManager.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Employee.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Salary.java +β”‚ β”‚ β”‚ β”‚ └── Title.java +β”‚ β”‚ β”‚ └── repository/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ DeptEmpRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ DeptManagerRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ Salary.Repositoryjava +β”‚ β”‚ β”‚ └── TitleRepository.java +β”‚ β”‚ β”œβ”€β”€ service/ +β”‚ β”‚ β”‚ β”œβ”€β”€ impl/ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentServiceImpl.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeServiceImpl.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryServiceImpl.java +β”‚ β”‚ β”‚ β”‚ └── TitleServiceImpl.java +β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentService.java +β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeService.java +β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryService.java +β”‚ β”‚ β”‚ └── TitleService.java +β”‚ β”‚ └── Lecture11Application.java +β”‚ └── resources/ +β”‚ └── application.properties +β”œβ”€β”€ .gitignore +β”œβ”€β”€ mvnw +β”œβ”€β”€ mvnw.cmd +β”œβ”€β”€ pom.xml +β”œβ”€β”€ run.bat +└── run.sh +``` + +### 🧩 SQL Query Data +Here is the SQL query to create the database, table, and instantiate some data. +```sql +-- Create the database +CREATE DATABASE week6_lecture11; + +-- Use the database +USE week6_lecture11; + +-- Create employees table +CREATE TABLE employees ( + emp_no INT AUTO_INCREMENT PRIMARY KEY, + birth_date DATE NOT NULL, + first_name VARCHAR(14) NOT NULL, + last_name VARCHAR(16) NOT NULL, + gender ENUM('M', 'F') NOT NULL, + hire_date DATE NOT NULL +); + +-- Create departments table +CREATE TABLE departments ( + dept_no CHAR(4) PRIMARY KEY, + dept_name VARCHAR(40) NOT NULL UNIQUE +); + +-- Create dept_emp table +CREATE TABLE dept_emp ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create dept_manager table +CREATE TABLE dept_manager ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create salaries table +CREATE TABLE salaries ( + emp_no INT NOT NULL, + from_date DATE NOT NULL, + salary INT NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); + +-- Create titles table +CREATE TABLE titles ( + emp_no INT NOT NULL, + title VARCHAR(50) NOT NULL, + from_date DATE NOT NULL, + to_date DATE, + PRIMARY KEY (emp_no, title, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); +``` + +Here is the query to insert some generated dummy data +```sql +-- Insert employees +INSERT INTO employees (birth_date, first_name, last_name, gender, hire_date) VALUES +('1980-01-01', 'John', 'Doe', 'M', '2000-01-01'), +('1985-05-23', 'Jane', 'Smith', 'F', '2005-05-01'), +('1990-07-11', 'Alice', 'Johnson', 'F', '2010-06-01'), +('1975-02-14', 'Bob', 'Brown', 'M', '1995-03-01'), +('1988-12-25', 'Charlie', 'Davis', 'M', '2008-12-01'), +('1981-04-10', 'David', 'Evans', 'M', '2001-04-10'), +('1986-08-15', 'Laura', 'Wilson', 'F', '2006-08-15'), +('1991-03-22', 'Karen', 'Garcia', 'F', '2011-03-22'), +('1976-06-12', 'Paul', 'Martinez', 'M', '1996-06-12'), +('1989-11-30', 'Nancy', 'Rodriguez', 'F', '2009-11-30'); + +-- Insert departments +INSERT INTO departments (dept_no, dept_name) VALUES +('d001', 'Marketing'), +('d002', 'Finance'), +('d003', 'Human Resources'), +('d004', 'Engineering'), +('d005', 'Sales'); + +-- Insert dept_emp +INSERT INTO dept_emp (emp_no, dept_no, from_date, to_date) VALUES +(1, 'd001', '2000-01-01', '2002-01-01'), +(1, 'd002', '2002-01-01', '9999-01-01'), +(2, 'd002', '2005-05-01', '2010-05-01'), +(2, 'd003', '2010-05-01', '9999-01-01'), +(3, 'd003', '2010-06-01', '9999-01-01'), +(3, 'd004', '2011-01-01', '9999-01-01'), +(4, 'd004', '1995-03-01', '9999-01-01'), +(4, 'd005', '2000-01-01', '9999-01-01'), +(5, 'd001', '2008-12-01', '9999-01-01'), +(5, 'd005', '2010-01-01', '9999-01-01'), +(6, 'd002', '2001-04-10', '2003-04-10'), +(6, 'd003', '2003-04-10', '9999-01-01'), +(7, 'd003', '2006-08-15', '2011-08-15'), +(7, 'd004', '2011-08-15', '9999-01-01'), +(8, 'd001', '2011-03-22', '9999-01-01'), +(9, 'd004', '1996-06-12', '2006-06-12'), +(9, 'd005', '2006-06-12', '9999-01-01'), +(10, 'd005', '2009-11-30', '9999-01-01'); + +-- Insert dept_manager +INSERT INTO dept_manager (emp_no, dept_no, from_date, to_date) VALUES +(1, 'd001', '2000-01-01', '2002-01-01'), +(2, 'd002', '2005-05-01', '2010-05-01'), +(3, 'd003', '2010-06-01', '2011-01-01'); + +-- Insert salaries +INSERT INTO salaries (emp_no, salary, from_date, to_date) VALUES +(1, 60000, '2000-01-01', '2002-01-01'), +(1, 65000, '2002-01-01', '9999-01-01'), +(2, 75000, '2005-05-01', '2010-05-01'), +(2, 80000, '2010-05-01', '9999-01-01'), +(3, 80000, '2010-06-01', '2011-01-01'), +(3, 85000, '2011-01-01', '9999-01-01'), +(4, 90000, '1995-03-01', '2000-01-01'), +(4, 95000, '2000-01-01', '9999-01-01'), +(5, 85000, '2008-12-01', '2010-01-01'), +(5, 90000, '2010-01-01', '9999-01-01'), +(6, 65000, '2001-04-10', '2003-04-10'), +(6, 70000, '2003-04-10', '9999-01-01'), +(7, 70000, '2006-08-15', '2011-08-15'), +(7, 75000, '2011-08-15', '9999-01-01'), +(8, 72000, '2011-03-22', '9999-01-01'), +(9, 95000, '1996-06-12', '2006-06-12'), +(9, 100000, '2006-06-12', '9999-01-01'), +(10, 86000, '2009-11-30', '9999-01-01'); + +-- Insert titles +INSERT INTO titles (emp_no, title, from_date, to_date) VALUES +(1, 'Manager', '2000-01-01', '2002-01-01'), +(1, 'Senior Manager', '2002-01-01', '9999-01-01'), +(2, 'Analyst', '2005-05-01', '2010-05-01'), +(2, 'Senior Analyst', '2010-05-01', '9999-01-01'), +(3, 'HR Specialist', '2010-06-01', '2011-01-01'), +(3, 'HR Manager', '2011-01-01', '9999-01-01'), +(4, 'Engineer', '1995-03-01', '2000-01-01'), +(4, 'Senior Engineer', '2000-01-01', '9999-01-01'), +(5, 'Sales Representative', '2008-12-01', '2010-01-01'), +(5, 'Senior Sales Representative', '2010-01-01', '9999-01-01'), +(6, 'Finance Specialist', '2001-04-10', '2003-04-10'), +(6, 'Senior Finance Specialist', '2003-04-10', '9999-01-01'), +(7, 'HR Manager', '2006-08-15', '2011-08-15'), +(7, 'Senior HR Manager', '2011-08-15', '9999-01-01'), +(8, 'Marketing Specialist', '2011-03-22', '9999-01-01'), +(9, 'Senior Engineer', '1996-06-12', '2006-06-12'), +(9, 'Chief Engineer', '2006-06-12', '9999-01-01'), +(10, 'Senior Sales Representative', '2009-11-30', '9999-01-01'); +``` + +All the MySQL queries is available on [this file](/Week%2006/Lecture%2011/lecture_11/src/main/resources/data.sql). Here is the query to drop the database +```sql +-- Drop the database +DROP DATABASE IF EXISTS week6_lecture11; +``` + +Also don't forget to configure [application properties](/Week%2006/Lecture%2011/lecture_11/src/main/resources/application.propertiess) with this format +```java +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/ +spring.datasource.username= +spring.datasource.password= +``` + +and don't forget to add this +```java +spring.jpa.hibernate.ddl-auto=update +``` +to do database seeding using JPA Hibernate. + +### βš™οΈ How to run the program +1. Go to the `lecture_11` directory by using this command + ```bash + $ cd lecture_11 + ``` +2. Make sure you have maven installed on your computer, use `mvn -v` to check the version. +3. If you are using windows, you can run the program by using this command. + ```bash + $ ./run.bat + ``` + And if you are using Linux, you can run the program by using this command. + ```bash + $ chmod +x run.sh + $ ./run.sh + ``` + +If all the instruction is well executed, Open [localhost:8080](http://localhost:8080) to see that the REST APIs is now works. + +### πŸ”‘ List of Endpoints +| Endpoint | Method | Description | +|-----------------------------------------|:--------: |---------------------------------------------------------------------------------------------| +| /api/v1/employees | GET | Retrieve all employees with default pagination (page 0 with size 20 elements/page). | +| /api/v1/employees?page=1&size=5 | GET | Retrieve employees with pagination (page 1 with size 5 elements/page). | +| /api/v1/employees/{empNo} | GET | Retrieve a specific employee by employee number. | +| /api/v1/employees | POST | Create a new employee. | +| /api/v1/employees/{empNo} | PUT | Update an existing employee by employee number. | +| /api/v1/employees/{empNo} | DELETE | Delete an employee by employee number. | +| /api/v1/departments | GET | Retrieve all departments with default pagination (page 0 with size 20 elements/page). | +| /api/v1/departments?page=0&size=2 | GET | Retrieve departments with pagination (page 0 with size 2 elements/page). | +| /api/v1/departments/{deptNo} | GET | Retrieve a specific department by department number. | +| /api/v1/departments | POST | Create a new department. | +| /api/v1/departments/{deptNo} | PUT | Update an existing department by department number. | +| /api/v1/departments/{deptNo} | DELETE | Delete a department by department number. | +| /api/v1/salaries | GET | Retrieve salary by ID. | +| /api/v1/salaries | POST | Create a new salary record. | +| /api/v1/salaries | PUT | Update an existing salary record. | +| /api/v1/salaries | DELETE | Delete a salary record by ID. | +| /api/v1/titles | GET | Retrieve title by ID. | +| /api/v1/titles | POST | Create a new title. | +| /api/v1/titles | PUT | Update an existing title. | +| /api/v1/titles | DELETE | Delete a title record by ID. | + +### πŸ“¬ Postman Collection + +Here is the [postman collection](/Week%2006/Lecture%2011/Assignment%2001/Lecture%2011%20-%20Assignment%2001.postman_collection.json) you can use to demo the API functionality. \ No newline at end of file diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/.gitignore b/Week 06/Lecture 11/Assignment 01/lecture_11/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/.mvn/wrapper/maven-wrapper.properties b/Week 06/Lecture 11/Assignment 01/lecture_11/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/mvnw b/Week 06/Lecture 11/Assignment 01/lecture_11/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/mvnw.cmd b/Week 06/Lecture 11/Assignment 01/lecture_11/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/pom.xml b/Week 06/Lecture 11/Assignment 01/lecture_11/pom.xml new file mode 100644 index 0000000..ed71858 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_11 + 1.0-SNAPSHOT + lecture_11 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web-services + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + mysql + mysql-connector-java + 8.0.33 + runtime + + + + + org.projectlombok + lombok + + + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + 8.0.0.Final + + + javax.validation + validation-api + 2.0.1.Final + + + + + org.mapstruct + mapstruct + 1.5.3.Final + + + org.mapstruct + mapstruct-processor + 1.5.3.Final + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/run.bat b/Week 06/Lecture 11/Assignment 01/lecture_11/run.bat new file mode 100644 index 0000000..1dee2cc --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/run.bat @@ -0,0 +1,3 @@ +@echo off +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_11-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/run.sh b/Week 06/Lecture 11/Assignment 01/lecture_11/run.sh new file mode 100644 index 0000000..7b72a65 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_11-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/Lecture11Application.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/Lecture11Application.java new file mode 100644 index 0000000..389e390 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/Lecture11Application.java @@ -0,0 +1,13 @@ +package com.example.lecture_11; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture11Application { + + public static void main(String[] args) { + SpringApplication.run(Lecture11Application.class, args); + } + +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/DepartmentController.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/DepartmentController.java new file mode 100644 index 0000000..7b1a847 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/DepartmentController.java @@ -0,0 +1,122 @@ +package com.example.lecture_11.controllers; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_11.data.model.Department; +import com.example.lecture_11.services.DepartmentService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/departments") +@AllArgsConstructor +public class DepartmentController { + + private final DepartmentService departmentService; + + /** + * This method retrieves {@link Page} of {@link Department} from the database. + * + * @param page The page number to retrieve (0-based index). + * @param size The number of elements per page. + * @return ResponseEntity> - A response entity containing a pages of {@link Department}. + * If the pages is empty, it returns a HTTP status code 204 (No Content). + * If the operation is successful, it returns a HTTP status code 200 (OK) with the pages of {@link Department}. + */ + @GetMapping + public ResponseEntity> findAll(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + Page departments = departmentService.findAll(pageable); + + if (departments.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(departments); + } + + /** + * This method retrieves an {@link Department} from the database by its deptNo. + * + * @param deptNo The unique identifier of the {@link Department}. + * @return ResponseEntity - A response entity containing the {@link Department} if found, or a 404 Not Found status code if not found. + */ + @GetMapping(value = "/{deptNo}") + public ResponseEntity findDepartmentById(@PathVariable("deptNo") String deptNo) { + Optional departmentOpt= departmentService.findById(deptNo); + + if(departmentOpt.isPresent()) { + return ResponseEntity.ok(departmentOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + /** + * This method saves a {@link Department} to the database. + * + * @param department The department object to be saved. + * @return ResponseEntity - A response entity containing the saved {@link Department}. + * If the {@link Department} already exists in the database, it returns a HTTP status code 400 (Bad Request). + */ + @PostMapping + public ResponseEntity save(@RequestBody Department department) { + Optional departmentOpt = departmentService.findById(department.getDeptNo()); + + if (departmentOpt.isPresent()) { + return ResponseEntity.badRequest().build(); + } + + return ResponseEntity.ok(departmentService.save(department)); + } + + /** + * This method updates an existing {@link Department} in the database. + * + * @param department The department object to be updated. + * @return ResponseEntity - A response entity containing the updated {@link Department}. + * If the {@link Department} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping(value = "/{deptNo}") + public ResponseEntity update(@PathVariable("deptNo") String deptNo, @RequestBody Department department) { + Optional departmentOpt = departmentService.findById(deptNo); + + if (departmentOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(departmentService.save(department)); + } + + /** + * This method deletes an {@link Department} from the database by its deptNo. + * + * @param deptNo The unique identifier of the {@link Department} to be deleted. + * @return ResponseEntity - A response entity containing the deleted {@link Department} if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping(value = "/{deptNo}") + public ResponseEntity deleteDepartment(@PathVariable(value = "deptNo") String deptNo) { + Optional departmentOpt = departmentService.findById(deptNo); + + if(departmentOpt.isPresent()) { + departmentService.deleteById(deptNo); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/EmployeeController.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/EmployeeController.java new file mode 100644 index 0000000..8d1f9a8 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/EmployeeController.java @@ -0,0 +1,117 @@ +package com.example.lecture_11.controllers; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_11.data.model.Employee; +import com.example.lecture_11.services.EmployeeService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/employees") +@AllArgsConstructor +public class EmployeeController { + + private final EmployeeService employeeService; + + /** + * This method retrieves {@link Page} of {@link Employee} from the database. + * + * @param page The page number to retrieve (0-based index). + * @param size The number of elements per page. + * @return ResponseEntity> - A response entity containing a page of {@link Employee}. + * If the page is empty, it returns a HTTP status code 204 (No Content). + * If the operation is successful, it returns a HTTP status code 200 (OK) with the page of {@link Employee}. + */ + @GetMapping + public ResponseEntity> findAll(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + Page employees = employeeService.findAll(pageable); + + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(employees); + } + + /** + * This method retrieves an {@link Employee} from the database by its empNo. + * + * @param empNo The unique identifier of the {@link Employee}. + * @return ResponseEntity - A response entity containing the {@link Employee} if found, or a 404 Not Found status code if not found. + */ + @GetMapping(value = "/{empNo}") + public ResponseEntity findEmployeeById(@PathVariable("empNo") Integer empNo) { + Optional employeeOpt= employeeService.findById(empNo); + + if(employeeOpt.isPresent()) { + return ResponseEntity.ok(employeeOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + /** + * This method saves an {@link Employee} to the database. + * + * @param employee The employee object to be saved. + * @return ResponseEntity - A response entity containing the saved {@link Employee}. + */ + @PostMapping + public ResponseEntity save(@RequestBody Employee employee) { + return ResponseEntity.ok(employeeService.save(employee)); + } + + /** + * This method updates an existing {@link Employee} in the database. + * + * @param empNo The unique identifier of the {@link Employee} to be updated. + * @param employee The employee object to be updated. + * @return ResponseEntity - A response entity containing the updated {@link Employee}. + * If the {@link Employee} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping(value = "/{empNo}") + public ResponseEntity update(@PathVariable Integer empNo, @RequestBody Employee employee) { + Optional employeeOpt = employeeService.findById(empNo); + + if (employeeOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + employee.setEmpNo(empNo); + return ResponseEntity.ok(employeeService.save(employee)); + } + + /** + * This method deletes an {@link Employee} from the database by its empNo. + * + * @param empNo The unique identifier of the {@link Employee} to be deleted. + * @return ResponseEntity - A response entity containing the deleted {@link Employee} if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping(value = "/{empNo}") + public ResponseEntity deleteEmployee(@PathVariable(value = "empNo") Integer empNo) { + Optional employeeOpt = employeeService.findById(empNo); + + if(employeeOpt.isPresent()) { + employeeService.deleteById(empNo); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/SalaryController.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/SalaryController.java new file mode 100644 index 0000000..577d41d --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/SalaryController.java @@ -0,0 +1,90 @@ +package com.example.lecture_11.controllers; + +import java.util.Optional; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_11.data.model.Salary; +import com.example.lecture_11.data.model.composite.SalaryId; +import com.example.lecture_11.services.SalaryService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/salaries") +@AllArgsConstructor +public class SalaryController { + + private final SalaryService salaryService; + + /** + * This method retrieves a {@link Salary} from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Salary} to be retrieved. + * @return ResponseEntity - A response entity containing the {@link Salary} if found, or a 404 Not Found status code if not found. + */ + @GetMapping + public ResponseEntity findSalaryById(@RequestBody SalaryId id) { + Optional salaryOpt= salaryService.findById(id); + + if(salaryOpt.isPresent()) { + return ResponseEntity.ok(salaryOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + /** + * This method saves a {@link Salary} to the database. + * + * @param salary The salary object to be saved. + * @return ResponseEntity - A response entity containing the saved {@link Salary}. + */ + @PostMapping + public ResponseEntity save(@RequestBody Salary salary) { + return ResponseEntity.ok(salaryService.save(salary)); + } + + /** + * This method updates an existing {@link Salary} in the database. + * + * @param salary The salary object to be updated. + * @return ResponseEntity - A response entity containing the updated {@link Salary}. + * If the {@link Salary} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping + public ResponseEntity update(@RequestBody Salary salary) { + Optional salaryOpt = salaryService.findById(salary.getId()); + + if (salaryOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(salaryService.save(salary)); + } + + /** + * This method deletes an {@link Salary} from the database by its id. + * + * @param id The unique identifier of the {@link Salary} to be deleted. + * @return ResponseEntity - A response entity containing the deleted {@link Salary} if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping + public ResponseEntity deleteSalary(@RequestBody SalaryId id) { + Optional salaryOpt = salaryService.findById(id); + + if(salaryOpt.isPresent()) { + salaryService.deleteById(id); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/TitleController.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/TitleController.java new file mode 100644 index 0000000..e2d7409 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/controllers/TitleController.java @@ -0,0 +1,92 @@ +package com.example.lecture_11.controllers; + +import java.util.Optional; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_11.data.model.Title; +import com.example.lecture_11.data.model.composite.TitleId; +import com.example.lecture_11.services.TitleService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/titles") +@AllArgsConstructor +public class TitleController { + + private final TitleService titleService; + + /** + * This method retrieves a {@link Title} from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Title} to be retrieved. + * @return ResponseEntity - A response entity containing the {@link Title} if found, or a 404 Not Found status code if not found. + */ + @GetMapping + public ResponseEntity<Title> findTitleById(@RequestBody TitleId id) { + Optional<Title> titleOpt= titleService.findById(id); + + if(titleOpt.isPresent()) { + return ResponseEntity.ok(titleOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + + /** + * This method saves a {@link Title} to the database. + * + * @param title The title object to be saved. + * @return ResponseEntity<Title> - A response entity containing the saved {@link Title}. + * If the {@link Title} already exists in the database, it returns a HTTP status code 400 (Bad Request). + */ + @PostMapping + public ResponseEntity<Title> save(@RequestBody Title title) { + return ResponseEntity.ok(titleService.save(title)); + } + + /** + * This method updates an existing {@link Title} in the database. + * + * @param title The title object to be updated. + * @return ResponseEntity<Title> - A response entity containing the updated {@link Title}. + * If the {@link Title} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping + public ResponseEntity<Title> update(@RequestBody Title title) { + Optional<Title> titleOpt = titleService.findById(title.getId()); + + if (titleOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(titleService.save(title)); + } + + /** + * This method deletes a {@link Title} from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Title} to be deleted. + * @return ResponseEntity<Title> - A response entity containing the deleted {@link Title} if found and successfully deleted, or a 404 Not Found status code if not found. + */ + @DeleteMapping + public ResponseEntity<Title> deleteTitle(@RequestBody TitleId id) { + Optional<Title> titleOpt = titleService.findById(id); + + if (titleOpt.isPresent()) { + titleService.deleteById(id); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Department.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Department.java new file mode 100644 index 0000000..030191f --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Department.java @@ -0,0 +1,24 @@ +package com.example.lecture_11.data.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "departments") +@NoArgsConstructor +@AllArgsConstructor +public class Department { + + @Id + @Column(length = 4) + private String deptNo; + + @Column(length = 40, nullable = false, unique = true) + private String deptName; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/DeptEmp.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/DeptEmp.java new file mode 100644 index 0000000..6ec4c7e --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/DeptEmp.java @@ -0,0 +1,34 @@ +package com.example.lecture_11.data.model; + +import java.time.LocalDate; + +import com.example.lecture_11.data.model.composite.DeptEmpId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "dept_emp") +@NoArgsConstructor +@AllArgsConstructor +public class DeptEmp { + + @EmbeddedId + private DeptEmpId id; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate fromDate; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/DeptManager.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/DeptManager.java new file mode 100644 index 0000000..d088624 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/DeptManager.java @@ -0,0 +1,34 @@ +package com.example.lecture_11.data.model; + +import java.time.LocalDate; + +import com.example.lecture_11.data.model.composite.DeptManagerId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "dept_manager") +@NoArgsConstructor +@AllArgsConstructor +public class DeptManager { + + @EmbeddedId + private DeptManagerId id; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate fromDate; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Employee.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Employee.java new file mode 100644 index 0000000..5367cf0 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Employee.java @@ -0,0 +1,44 @@ +package com.example.lecture_11.data.model; + +import java.time.LocalDate; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "employees") +@NoArgsConstructor +@AllArgsConstructor +public class Employee { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer empNo; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate birthDate; + + @Column(length = 14, nullable = false) + private String firstName; + + @Column(length = 16, nullable = false) + private String lastName; + + @Column(columnDefinition = "enum('M','F')", nullable = false) + private String gender; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate hireDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Salary.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Salary.java new file mode 100644 index 0000000..86cd5bb --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Salary.java @@ -0,0 +1,33 @@ +package com.example.lecture_11.data.model; + +import java.time.LocalDate; + +import com.example.lecture_11.data.model.composite.SalaryId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "salaries") +@NoArgsConstructor +@AllArgsConstructor +public class Salary { + + @EmbeddedId + private SalaryId id; + + @Column(nullable = false) + private Integer salary; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Title.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Title.java new file mode 100644 index 0000000..8724490 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/Title.java @@ -0,0 +1,30 @@ +package com.example.lecture_11.data.model; + +import java.time.LocalDate; + +import com.example.lecture_11.data.model.composite.TitleId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "titles") +@NoArgsConstructor +@AllArgsConstructor +public class Title { + + @EmbeddedId + private TitleId id; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/DeptEmpId.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/DeptEmpId.java new file mode 100644 index 0000000..d064349 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/DeptEmpId.java @@ -0,0 +1,15 @@ +package com.example.lecture_11.data.model.composite; + +import java.io.Serializable; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class DeptEmpId implements Serializable { + private Integer empNo; + private String deptNo; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/DeptManagerId.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/DeptManagerId.java new file mode 100644 index 0000000..f8a82c6 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/DeptManagerId.java @@ -0,0 +1,15 @@ +package com.example.lecture_11.data.model.composite; + +import java.io.Serializable; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class DeptManagerId implements Serializable { + private Integer empNo; + private String deptNo; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/SalaryId.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/SalaryId.java new file mode 100644 index 0000000..17a6a6b --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/SalaryId.java @@ -0,0 +1,16 @@ +package com.example.lecture_11.data.model.composite; + +import java.io.Serializable; +import java.time.LocalDate; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class SalaryId implements Serializable { + private Integer empNo; + private LocalDate fromDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/TitleId.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/TitleId.java new file mode 100644 index 0000000..496ace7 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/model/composite/TitleId.java @@ -0,0 +1,17 @@ +package com.example.lecture_11.data.model.composite; + +import java.io.Serializable; +import java.time.LocalDate; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class TitleId implements Serializable { + private Integer empNo; + private String title; + private LocalDate fromDate; +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DepartmentRepository.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DepartmentRepository.java new file mode 100644 index 0000000..72bf62c --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DepartmentRepository.java @@ -0,0 +1,11 @@ +package com.example.lecture_11.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.lecture_11.data.model.Department; + +@Repository +public interface DepartmentRepository extends JpaRepository<Department, String> { +} + diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DeptEmpRepository.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DeptEmpRepository.java new file mode 100644 index 0000000..c285503 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DeptEmpRepository.java @@ -0,0 +1,10 @@ +package com.example.lecture_11.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_11.data.model.DeptEmp; +import com.example.lecture_11.data.model.composite.DeptEmpId; + +public interface DeptEmpRepository extends JpaRepository<DeptEmp, DeptEmpId> { +} + diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DeptManagerRepository.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DeptManagerRepository.java new file mode 100644 index 0000000..664b2e2 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/DeptManagerRepository.java @@ -0,0 +1,10 @@ +package com.example.lecture_11.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_11.data.model.DeptManager; +import com.example.lecture_11.data.model.composite.DeptManagerId; + +public interface DeptManagerRepository extends JpaRepository<DeptManager, DeptManagerId> { +} + diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/EmployeeRepository.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/EmployeeRepository.java new file mode 100644 index 0000000..ba85bc3 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/EmployeeRepository.java @@ -0,0 +1,10 @@ +package com.example.lecture_11.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.lecture_11.data.model.Employee; + +@Repository +public interface EmployeeRepository extends JpaRepository<Employee, Integer> { +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/SalaryRepository.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/SalaryRepository.java new file mode 100644 index 0000000..535efc7 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/SalaryRepository.java @@ -0,0 +1,9 @@ +package com.example.lecture_11.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_11.data.model.Salary; +import com.example.lecture_11.data.model.composite.SalaryId; + +public interface SalaryRepository extends JpaRepository<Salary, SalaryId> { +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/TitleRepository.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/TitleRepository.java new file mode 100644 index 0000000..2857d84 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/data/repository/TitleRepository.java @@ -0,0 +1,9 @@ +package com.example.lecture_11.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_11.data.model.Title; +import com.example.lecture_11.data.model.composite.TitleId; + +public interface TitleRepository extends JpaRepository<Title, TitleId> { +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/DepartmentService.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/DepartmentService.java new file mode 100644 index 0000000..fccfc7e --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/DepartmentService.java @@ -0,0 +1,22 @@ +package com.example.lecture_11.services; + +import com.example.lecture_11.data.model.Department; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; + +public interface DepartmentService { + // Retrieves a paginated list of {@link Department} entities. + Page<Department> findAll(Pageable pageable); + + // Retrieves an {@link Department} entity by its unique identifier. + Optional<Department> findById(String deptNo); + + // Saves or updates an {@link Department} entity in the database. + Department save(Department department); + + // Deletes an {@link Department} entity from the database by its unique identifier. + void deleteById(String deptNo); +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/EmployeeService.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/EmployeeService.java new file mode 100644 index 0000000..6635aa4 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/EmployeeService.java @@ -0,0 +1,22 @@ +package com.example.lecture_11.services; + +import com.example.lecture_11.data.model.Employee; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; + +public interface EmployeeService { + // Retrieves a paginated list of {@link Employee} entities. + Page<Employee> findAll(Pageable pageable); + + // Retrieves an {@link Employee} entity by its unique identifier. + Optional<Employee> findById(Integer empNo); + + // Saves or updates an {@link Employee} entity in the database. + Employee save(Employee employee); + + // Deletes an {@link Employee} entity from the database by its unique identifier. + void deleteById(Integer empNo); +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/SalaryService.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/SalaryService.java new file mode 100644 index 0000000..d9464b3 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/SalaryService.java @@ -0,0 +1,17 @@ +package com.example.lecture_11.services; + +import java.util.Optional; + +import com.example.lecture_11.data.model.Salary; +import com.example.lecture_11.data.model.composite.SalaryId; + +public interface SalaryService { + // Retrieves an {@link Salary} entity by its unique identifier. + Optional<Salary> findById(SalaryId id); + + // Saves or updates an {@link Salary} entity in the database. + Salary save(Salary salary); + + // Deletes an {@link Salary} entity from the database by its unique identifier. + void deleteById(SalaryId id); +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/TitleService.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/TitleService.java new file mode 100644 index 0000000..32ef32e --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/TitleService.java @@ -0,0 +1,17 @@ +package com.example.lecture_11.services; + +import java.util.Optional; + +import com.example.lecture_11.data.model.Title; +import com.example.lecture_11.data.model.composite.TitleId; + +public interface TitleService { + // Retrieves an {@link Title} entity by its unique identifier. + Optional<Title> findById(TitleId id); + + // Saves or updates an {@link Title} entity in the database. + Title save(Title title); + + // Deletes an {@link Title} entity from the database by its unique identifier. + void deleteById(TitleId id); +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/DepartmentServiceImpl.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/DepartmentServiceImpl.java new file mode 100644 index 0000000..d86da54 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/DepartmentServiceImpl.java @@ -0,0 +1,64 @@ +package com.example.lecture_11.services.impl; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import com.example.lecture_11.data.model.Department; +import com.example.lecture_11.data.repository.DepartmentRepository; +import com.example.lecture_11.services.DepartmentService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class DepartmentServiceImpl implements DepartmentService { + + private final DepartmentRepository departmentRepository; + + /** + * Retrieves a paginated list of {@link Department} entities. + * + * @param pageable The pagination and sorting parameters. + * @return A {@link Page} of {@link Department} entities. + */ + @Override + public Page<Department> findAll(Pageable pageable) { + return departmentRepository.findAll(pageable); + } + + /** + * Retrieves an {@link Department} entity by its unique identifier. + * + * @param deptNo The unique identifier of the {@link Department} entity to retrieve. + * @return An {@link Optional} containing the {@link Department} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Department> findById(String deptNo) { + return departmentRepository.findById(deptNo); + } + + /** + * Saves or updates an {@link Department} entity in the database. + * + * @param department The {@link Department} entity to be saved or updated. + * @return The saved or updated {@link Department} entity. + */ + @Override + public Department save(Department department) { + return departmentRepository.save(department); + } + + /** + * Deletes an {@link Department} entity from the database by its unique identifier. + * + * @param deptNo The unique identifier of the {@link Department} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(String deptNo) { + departmentRepository.deleteById(deptNo); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/EmployeeServiceImpl.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/EmployeeServiceImpl.java new file mode 100644 index 0000000..dba7659 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/EmployeeServiceImpl.java @@ -0,0 +1,64 @@ +package com.example.lecture_11.services.impl; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import com.example.lecture_11.data.model.Employee; +import com.example.lecture_11.data.repository.EmployeeRepository; +import com.example.lecture_11.services.EmployeeService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + + /** + * Retrieves a paginated list of {@link Employee} entities. + * + * @param pageable The pagination and sorting parameters. + * @return A {@link Page} of {@link Employee} entities. + */ + @Override + public Page<Employee> findAll(Pageable pageable) { + return employeeRepository.findAll(pageable); + } + + /** + * Retrieves an {@link Employee} entity by its unique identifier. + * + * @param empNo The unique identifier of the {@link Employee} entity to retrieve. + * @return An {@link Optional} containing the {@link Employee} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Employee> findById(Integer empNo) { + return employeeRepository.findById(empNo); + } + + /** + * Saves or updates an {@link Employee} entity in the database. + * + * @param employee The {@link Employee} entity to be saved or updated. + * @return The saved or updated {@link Employee} entity. + */ + @Override + public Employee save(Employee employee) { + return employeeRepository.save(employee); + } + + /** + * Deletes an {@link Employee} entity from the database by its unique identifier. + * + * @param empNo The unique identifier of the {@link Employee} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(Integer empNo) { + employeeRepository.deleteById(empNo); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/SalaryServiceImpl.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/SalaryServiceImpl.java new file mode 100644 index 0000000..ce91410 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/SalaryServiceImpl.java @@ -0,0 +1,52 @@ +package com.example.lecture_11.services.impl; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.example.lecture_11.data.model.Salary; +import com.example.lecture_11.data.model.composite.SalaryId; +import com.example.lecture_11.data.repository.SalaryRepository; +import com.example.lecture_11.services.SalaryService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class SalaryServiceImpl implements SalaryService { + + private final SalaryRepository salaryRepository; + + /** + * Retrieves an {@link Salary} entity by its unique identifier. + * + * @param id The unique identifier of the {@link Salary} entity to retrieve. + * @return An {@link Optional} containing the {@link Salary} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Salary> findById(SalaryId id) { + return salaryRepository.findById(id); + } + + /** + * Saves or updates an {@link Salary} entity in the database. + * + * @param salary The {@link Salary} entity to be saved or updated. + * @return The saved or updated {@link Salary} entity. + */ + @Override + public Salary save(Salary salary) { + return salaryRepository.save(salary); + } + + /** + * Deletes an {@link Salary} entity from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Salary} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(SalaryId id) { + salaryRepository.deleteById(id); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/TitleServiceImpl.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/TitleServiceImpl.java new file mode 100644 index 0000000..fc733c1 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/java/com/example/lecture_11/services/impl/TitleServiceImpl.java @@ -0,0 +1,52 @@ +package com.example.lecture_11.services.impl; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.example.lecture_11.data.model.Title; +import com.example.lecture_11.data.model.composite.TitleId; +import com.example.lecture_11.data.repository.TitleRepository; +import com.example.lecture_11.services.TitleService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class TitleServiceImpl implements TitleService { + + private final TitleRepository titleRepository; + + /** + * Retrieves an {@link Title} entity by its unique identifier. + * + * @param id The unique identifier of the {@link Title} entity to retrieve. + * @return An {@link Optional} containing the {@link Title} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Title> findById(TitleId id) { + return titleRepository.findById(id); + } + + /** + * Saves or updates an {@link Title} entity in the database. + * + * @param title The {@link Title} entity to be saved or updated. + * @return The saved or updated {@link Title} entity. + */ + @Override + public Title save(Title title) { + return titleRepository.save(title); + } + + /** + * Deletes an {@link Title} entity from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Title} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(TitleId id) { + titleRepository.deleteById(id); + } +} diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/resources/application.properties b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/resources/application.properties new file mode 100644 index 0000000..7d70328 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.application.name=lecture_11 + +spring.datasource.url=jdbc:mysql://localhost:3308/week6_lecture11?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource.username=root +spring.datasource.password=Michaeleon16606_ +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.jpa.hibernate.ddl-auto=update \ No newline at end of file diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/resources/data.sql b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/resources/data.sql new file mode 100644 index 0000000..7631c07 --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/main/resources/data.sql @@ -0,0 +1,149 @@ +-- Database schema initializer +-- Create employees table +CREATE TABLE employees ( + emp_no INT AUTO_INCREMENT PRIMARY KEY, + birth_date DATE NOT NULL, + first_name VARCHAR(14) NOT NULL, + last_name VARCHAR(16) NOT NULL, + gender ENUM('M', 'F') NOT NULL, + hire_date DATE NOT NULL +); + +-- Create departments table +CREATE TABLE departments ( + dept_no CHAR(4) PRIMARY KEY, + dept_name VARCHAR(40) NOT NULL UNIQUE +); + +-- Create dept_emp table +CREATE TABLE dept_emp ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create dept_manager table +CREATE TABLE dept_manager ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create salaries table +CREATE TABLE salaries ( + emp_no INT NOT NULL, + from_date DATE NOT NULL, + salary INT NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); + +-- Create titles table +CREATE TABLE titles ( + emp_no INT NOT NULL, + title VARCHAR(50) NOT NULL, + from_date DATE NOT NULL, + to_date DATE, + PRIMARY KEY (emp_no, title, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); + +-- Database Initial Seeding +-- Insert employees +INSERT INTO employees (birth_date, first_name, last_name, gender, hire_date) VALUES +('1980-01-01', 'John', 'Doe', 'M', '2000-01-01'), +('1985-05-23', 'Jane', 'Smith', 'F', '2005-05-01'), +('1990-07-11', 'Alice', 'Johnson', 'F', '2010-06-01'), +('1975-02-14', 'Bob', 'Brown', 'M', '1995-03-01'), +('1988-12-25', 'Charlie', 'Davis', 'M', '2008-12-01'), +('1981-04-10', 'David', 'Evans', 'M', '2001-04-10'), +('1986-08-15', 'Laura', 'Wilson', 'F', '2006-08-15'), +('1991-03-22', 'Karen', 'Garcia', 'F', '2011-03-22'), +('1976-06-12', 'Paul', 'Martinez', 'M', '1996-06-12'), +('1989-11-30', 'Nancy', 'Rodriguez', 'F', '2009-11-30'); + +-- Insert departments +INSERT INTO departments (dept_no, dept_name) VALUES +('d001', 'Marketing'), +('d002', 'Finance'), +('d003', 'Human Resources'), +('d004', 'Engineering'), +('d005', 'Sales'); + +-- Insert dept_emp +INSERT INTO dept_emp (emp_no, dept_no, from_date, to_date) VALUES +(1, 'd001', '2000-01-01', '2002-01-01'), +(1, 'd002', '2002-01-01', '9999-01-01'), +(2, 'd002', '2005-05-01', '2010-05-01'), +(2, 'd003', '2010-05-01', '9999-01-01'), +(3, 'd003', '2010-06-01', '9999-01-01'), +(3, 'd004', '2011-01-01', '9999-01-01'), +(4, 'd004', '1995-03-01', '9999-01-01'), +(4, 'd005', '2000-01-01', '9999-01-01'), +(5, 'd001', '2008-12-01', '9999-01-01'), +(5, 'd005', '2010-01-01', '9999-01-01'), +(6, 'd002', '2001-04-10', '2003-04-10'), +(6, 'd003', '2003-04-10', '9999-01-01'), +(7, 'd003', '2006-08-15', '2011-08-15'), +(7, 'd004', '2011-08-15', '9999-01-01'), +(8, 'd001', '2011-03-22', '9999-01-01'), +(9, 'd004', '1996-06-12', '2006-06-12'), +(9, 'd005', '2006-06-12', '9999-01-01'), +(10, 'd005', '2009-11-30', '9999-01-01'); + +-- Insert dept_manager +INSERT INTO dept_manager (emp_no, dept_no, from_date, to_date) VALUES +(1, 'd001', '2000-01-01', '2002-01-01'), +(2, 'd002', '2005-05-01', '2010-05-01'), +(3, 'd003', '2010-06-01', '2011-01-01'); + +-- Insert salaries +INSERT INTO salaries (emp_no, salary, from_date, to_date) VALUES +(1, 60000, '2000-01-01', '2002-01-01'), +(1, 65000, '2002-01-01', '9999-01-01'), +(2, 75000, '2005-05-01', '2010-05-01'), +(2, 80000, '2010-05-01', '9999-01-01'), +(3, 80000, '2010-06-01', '2011-01-01'), +(3, 85000, '2011-01-01', '9999-01-01'), +(4, 90000, '1995-03-01', '2000-01-01'), +(4, 95000, '2000-01-01', '9999-01-01'), +(5, 85000, '2008-12-01', '2010-01-01'), +(5, 90000, '2010-01-01', '9999-01-01'), +(6, 65000, '2001-04-10', '2003-04-10'), +(6, 70000, '2003-04-10', '9999-01-01'), +(7, 70000, '2006-08-15', '2011-08-15'), +(7, 75000, '2011-08-15', '9999-01-01'), +(8, 72000, '2011-03-22', '9999-01-01'), +(9, 95000, '1996-06-12', '2006-06-12'), +(9, 100000, '2006-06-12', '9999-01-01'), +(10, 86000, '2009-11-30', '9999-01-01'); + +-- Insert titles +INSERT INTO titles (emp_no, title, from_date, to_date) VALUES +(1, 'Manager', '2000-01-01', '2002-01-01'), +(1, 'Senior Manager', '2002-01-01', '9999-01-01'), +(2, 'Analyst', '2005-05-01', '2010-05-01'), +(2, 'Senior Analyst', '2010-05-01', '9999-01-01'), +(3, 'HR Specialist', '2010-06-01', '2011-01-01'), +(3, 'HR Manager', '2011-01-01', '9999-01-01'), +(4, 'Engineer', '1995-03-01', '2000-01-01'), +(4, 'Senior Engineer', '2000-01-01', '9999-01-01'), +(5, 'Sales Representative', '2008-12-01', '2010-01-01'), +(5, 'Senior Sales Representative', '2010-01-01', '9999-01-01'), +(6, 'Finance Specialist', '2001-04-10', '2003-04-10'), +(6, 'Senior Finance Specialist', '2003-04-10', '9999-01-01'), +(7, 'HR Manager', '2006-08-15', '2011-08-15'), +(7, 'Senior HR Manager', '2011-08-15', '9999-01-01'), +(8, 'Marketing Specialist', '2011-03-22', '9999-01-01'), +(9, 'Senior Engineer', '1996-06-12', '2006-06-12'), +(9, 'Chief Engineer', '2006-06-12', '9999-01-01'), +(10, 'Senior Sales Representative', '2009-11-30', '9999-01-01'); diff --git a/Week 06/Lecture 11/Assignment 01/lecture_11/src/test/java/com/example/lecture_11/Lecture11ApplicationTests.java b/Week 06/Lecture 11/Assignment 01/lecture_11/src/test/java/com/example/lecture_11/Lecture11ApplicationTests.java new file mode 100644 index 0000000..12ec4ae --- /dev/null +++ b/Week 06/Lecture 11/Assignment 01/lecture_11/src/test/java/com/example/lecture_11/Lecture11ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.lecture_11; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Lecture11ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/Week 06/Lecture 12/Assignment 01/Lecture 12 - Assignment 01.postman_collection.json b/Week 06/Lecture 12/Assignment 01/Lecture 12 - Assignment 01.postman_collection.json new file mode 100644 index 0000000..3da82dd --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/Lecture 12 - Assignment 01.postman_collection.json @@ -0,0 +1,835 @@ +{ + "info": { + "_postman_id": "3093d6b8-a742-4d7c-b565-95715055e5d3", + "name": "Lecture 12 - Assignment 01", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "34693283" + }, + "item": [ + { + "name": "Employees", + "item": [ + { + "name": "All Employees", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employees" + }, + "response": [] + }, + { + "name": "All Employees Configurable Pages", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees?page=1&size=5", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "size", + "value": "5" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee By EmpNo", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employees/3" + }, + "response": [] + }, + { + "name": "New Employee", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"birthDate\": \"2003-08-12\",\r\n \"firstName\": \"Michael\",\r\n \"lastName\": \"Leon\",\r\n \"gender\": \"M\",\r\n \"hireDate\": \"2024-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employees" + }, + "response": [] + }, + { + "name": "Edit Employee", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"birthDate\": \"2003-08-12\",\r\n \"firstName\": \"Leon\",\r\n \"lastName\": \"Michael\",\r\n \"gender\": \"M\",\r\n \"hireDate\": \"2024-07-18\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employees/12" + }, + "response": [] + }, + { + "name": "Delete Employee", + "request": { + "method": "DELETE", + "header": [], + "url": "localhost:8080/api/v1/employees/12" + }, + "response": [] + } + ] + }, + { + "name": "Departments", + "item": [ + { + "name": "All Departments", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/departments" + }, + "response": [] + }, + { + "name": "All Departments Configurable Pages", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/departments?page=0&size=2", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "departments" + ], + "query": [ + { + "key": "page", + "value": "0" + }, + { + "key": "size", + "value": "2" + } + ] + } + }, + "response": [] + }, + { + "name": "Department By DeptNo", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/departments/d004" + }, + "response": [] + }, + { + "name": "New Department", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"deptNo\": \"d006\",\r\n \"deptName\": \"Research\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/departments" + }, + "response": [] + }, + { + "name": "Edit Employee", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"deptNo\": \"d006\",\r\n \"deptName\": \"New Research\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/departments/d006" + }, + "response": [] + }, + { + "name": "Delete Employee", + "request": { + "method": "DELETE", + "header": [], + "url": "localhost:8080/api/v1/departments/d006" + }, + "response": [] + } + ] + }, + { + "name": "Salaries", + "item": [ + { + "name": "Salary by ID", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"fromDate\": \"2000-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + }, + { + "name": "New Salary", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n },\r\n \"salary\": 60000,\r\n \"toDate\": \"2025-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + }, + { + "name": "Edit Salary", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n },\r\n \"salary\": 65000,\r\n \"toDate\": \"2025-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + }, + { + "name": "Delete Salary", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"fromDate\": \"2024-07-17\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/salaries" + }, + "response": [] + } + ] + }, + { + "name": "Titles", + "item": [ + { + "name": "Title by ID", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 1,\r\n \"title\": \"Manager\",\r\n \"fromDate\": \"2000-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + }, + { + "name": "New Title", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 8,\r\n \"title\": \"Engineer\",\r\n \"fromDate\": \"2000-01-01\"\r\n },\r\n \"toDate\": \"2002-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + }, + { + "name": "Edit Title", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": {\r\n \"empNo\": 8,\r\n \"title\": \"Engineer\",\r\n \"fromDate\": \"2000-01-01\"\r\n },\r\n \"toDate\": \"2020-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + }, + { + "name": "Delete Title", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"empNo\": 8,\r\n \"title\": \"Engineer\",\r\n \"fromDate\": \"2000-01-01\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/titles" + }, + "response": [] + } + ] + }, + { + "name": "Advance Employees Search", + "item": [ + { + "name": "Employee Dynamic Search (1)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?firstName=Paul&gender=M", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "firstName", + "value": "Paul" + }, + { + "key": "gender", + "value": "M" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Dynamic Search (2)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?birthDate=1991-03-22&birthDateOperation=eq&lastName=Garcia", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "birthDate", + "value": "1991-03-22" + }, + { + "key": "birthDateOperation", + "value": "eq" + }, + { + "key": "lastName", + "value": "Garcia" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Dynamic Search (3)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?gender=F&page=1&size=3", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "gender", + "value": "F" + }, + { + "key": "page", + "value": "1" + }, + { + "key": "size", + "value": "3" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Dynamic Search (4)", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employees/search" + }, + "response": [] + }, + { + "name": "Employee Advance Search (1)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?firstName=John&firstNameOperation=like&sortBy=lastName&sortOrder=asc", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "firstName", + "value": "John" + }, + { + "key": "firstNameOperation", + "value": "like" + }, + { + "key": "sortBy", + "value": "lastName" + }, + { + "key": "sortOrder", + "value": "asc" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (2)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?hireDate=2020-06-01&hireDateOperation=lt&size=40&page=2", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "hireDate", + "value": "2020-06-01" + }, + { + "key": "hireDateOperation", + "value": "lt" + }, + { + "key": "size", + "value": "40" + }, + { + "key": "page", + "value": "2" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (3)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?birthDate=1990-01-01&birthDateOperation=gt&page=1", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "birthDate", + "value": "1990-01-01" + }, + { + "key": "birthDateOperation", + "value": "gt" + }, + { + "key": "page", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (4)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?lastName=Smith&hireYear=2015", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "lastName", + "value": "Smith" + }, + { + "key": "hireYear", + "value": "2015" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (5)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?birthMonth=2", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "birthMonth", + "value": "2" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (6)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?firstName=Albert&lastName=B&lastNameOperation=like&gender=M", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "firstName", + "value": "Albert" + }, + { + "key": "lastName", + "value": "B" + }, + { + "key": "lastNameOperation", + "value": "like" + }, + { + "key": "gender", + "value": "M" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (7)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?firstName=Bobby&birthDate=1985-01-01&birthDateOperation=geq", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "firstName", + "value": "Bobby" + }, + { + "key": "birthDate", + "value": "1985-01-01" + }, + { + "key": "birthDateOperation", + "value": "geq" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (8)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?hireDate=2010-12-31&hireDateOperation=leq&sortBy=hireDate&sortOrder=desc&page=4", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "hireDate", + "value": "2010-12-31" + }, + { + "key": "hireDateOperation", + "value": "leq" + }, + { + "key": "sortBy", + "value": "hireDate" + }, + { + "key": "sortOrder", + "value": "desc" + }, + { + "key": "page", + "value": "4" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (9)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?birthMonth=8&hireYear=2011", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "birthMonth", + "value": "8" + }, + { + "key": "hireYear", + "value": "2011" + } + ] + } + }, + "response": [] + }, + { + "name": "Employee Advance Search (10)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employees/search?firstName=Michael&birthDate=1977-09-05&birthDateOperation=eq", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employees", + "search" + ], + "query": [ + { + "key": "firstName", + "value": "Michael" + }, + { + "key": "birthDate", + "value": "1977-09-05" + }, + { + "key": "birthDateOperation", + "value": "eq" + } + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/Week 06/Lecture 12/Assignment 01/README.md b/Week 06/Lecture 12/Assignment 01/README.md new file mode 100644 index 0000000..b3b1153 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/README.md @@ -0,0 +1,503 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 12 - Spring Data JPA +> This repository is created as a part of assignment for Lecture 12 - Spring Data JPA + +## ⚑ Assignment 01 - Adding Dynamic Criteria for Employee Search + +### πŸ”Ž Dynamic Search Criteria 😡😡 + +To implement dynamic criteria search APIs for every attribute on my `Employee` model, i'll need to enhance my existing codebase to support filtering based on various attributes. Here's a detailed approach: + +### πŸ‘£ Step-by-Step Explanation + +1. **Define Search Criteria**: I decided how i want to pass search criteria to my API. Common approaches include query parameters (`/api/v1/employees?firstName=John&gender=M`) or a JSON object in the request body (`POST` request with a JSON body containing search criteria). In this implementation, i choose the query parameters. + +2. **DTO (Data Transfer Object)**: I used DTOs to transfer data between layers (controller, service, repository). This helps in decoupling my API contract from my entity structure and provides flexibility in handling incoming requests. + +3. **Service Layer Modification**: I enhanced my service layer to handle dynamic filtering using specifications or query methods. Specifications are particularly useful for complex queries involving multiple criteria. + +4. **Controller Layer Modification**: I also modified my controller to accept dynamic search criteria and delegate the search to the service layer. + +5. **Implementation Considerations**: I also not forget to handle various scenarios such as no search criteria provided, pagination, sorting, and proper error handling for invalid queries. + +### πŸ‘¨πŸ»β€πŸ’» Implementation: + +#### 1. Create a DTO for Search Criteria ([EmployeeSearchCriteriaDTO.java](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/java/com/example/lecture_12/dto/EmployeeSearchCriteriaDTO.java)) + +```java +package com.example.lecture_12.dto; + +import java.time.LocalDate; +import lombok.Data; + +@Data +public class EmployeeSearchCriteriaDTO { + private LocalDate birthDate; + private String firstName; + private String lastName; + private String gender; + private LocalDate hireDate; +} +``` + +#### 2. Update Employee Repository ([EmployeeRepository.java](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/java/com/example/lecture_12/data/repository/EmployeeRepository.java)) + +```java +package com.example.lecture_12.data.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.lecture_12.data.model.Employee; + +@Repository +public interface EmployeeRepository extends JpaRepository<Employee, Integer> { + // Define a custom query method using Specification and Pageable + Page<Employee> findAll(Specification<Employee> spec, Pageable pageable); +} +``` + +#### 3. Modify Employee Service Interface ([EmployeeService.java](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/java/com/example/lecture_12/services/EmployeeService.java)) + +```java +package com.example.lecture_12.services; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.example.lecture_12.data.model.Employee; +import com.example.lecture_12.dto.EmployeeSearchCriteriaDTO; + +public interface EmployeeService { + .... + + // Retrieves a paginated list of {@link Employee} entities based on the provided search criteria. + Page<Employee> findByCriteria(EmployeeSearchCriteriaDTO criteria, Pageable pageable); + + .... +} +``` + +#### 4. Implement Employee Service ([EmployeeServiceImpl.java](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/java/com/example/lecture_12/services/impl/EmployeeServiceImpl.java)) + +```java +package com.example.lecture_12.services.impl; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import com.example.lecture_12.data.model.Employee; +import com.example.lecture_12.data.repository.EmployeeRepository; +import com.example.lecture_12.dto.EmployeeSearchCriteriaDTO; +import com.example.lecture_12.services.EmployeeService; +import jakarta.persistence.criteria.Predicate; +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + + .... + + /** + * Retrieves a paginated list of {@link Employee} entities based on the provided search criteria. + * + * @param criteria The criteria object containing fields to filter the search. + * @param pageable Pagination and sorting parameters. + * @return A {@link Page} of {@link Employee} entities that match the specified criteria. + */ + @Override + public Page<Employee> findByCriteria(EmployeeSearchCriteriaDTO criteria, Pageable pageable) { + return employeeRepository.findAll((Specification<Employee>) (root, query, cb) -> { + List<Predicate> predicates = new ArrayList<>(); + + if (criteria.getBirthDate() != null) { predicates.add(cb.equal(root.get("birthDate"), criteria.getBirthDate())); } + if (criteria.getFirstName() != null) { predicates.add(cb.equal(root.get("firstName"), criteria.getFirstName())); } + if (criteria.getLastName() != null) { predicates.add(cb.equal(root.get("lastName"), criteria.getLastName())); } + if (criteria.getGender() != null) { predicates.add(cb.equal(root.get("gender"), criteria.getGender())); } + if (criteria.getHireDate() != null) { predicates.add(cb.equal(root.get("hireDate"), criteria.getHireDate())); } + + return cb.and(predicates.toArray(Predicate[]::new)); + }, pageable); + } + + .... +} +``` + +#### 5. Update Employee Controller ([EmployeeController.java](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/java/com/example/lecture_12/controllers/EmployeeController.java)) + +```java +package com.example.lecture_12.controllers; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import com.example.lecture_12.data.model.Employee; +import com.example.lecture_12.dto.EmployeeSearchCriteriaDTO; +import com.example.lecture_12.services.EmployeeService; +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/employees") +@AllArgsConstructor +public class EmployeeController { + + private final EmployeeService employeeService; + + .... + + /** + * Endpoint to search for {@link Employee} entities based on the provided search criteria. + * Supports pagination and sorting. + * + * @param criteria The criteria object of {@link EmployeeSearchCriteriaDTO} containing fields to filter the search. + * @param page The page number to retrieve (default is 0). + * @param size The number of elements per page (default is 20). + * @return ResponseEntity containing a {@link Page} of {@link Employee} entities that match the criteria, + */ + @GetMapping("/search") + public ResponseEntity<Page<Employee>> searchEmployees(EmployeeSearchCriteriaDTO criteria, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + Page<Employee> employees = employeeService.findByCriteria(criteria, pageable); + + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(employees); + } + + .... +} +``` + +### πŸ“’ Explanation of Code +Here is the detail explanation on what i already done throughout the code. + +- **DTO**: `EmployeeSearchCriteriaDTO` is a simple class to hold search criteria. Each attribute corresponds to a field in the `Employee` entity. +- **Database Layer (JPA)**: adding `findAll` with Specification and Pageable on `EmployeeRepository` to handle spesific search query criteria dynamically from the database also to implement pagination easily. +- **Service Layer**: `EmployeeServiceImpl` implements `findByCriteria` method using JPA Specifications to dynamically build predicates based on provided criteria. +- **Controller Layer**: `EmployeeController` exposes a `GET` endpoint `/api/v1/employees/search` to accept search criteria as query parameters and returns a list of matching `Employee` entities. + +### πŸ“ Some Notable Mentions + +- **Security**: I'm ensuring to validate and sanitize input to prevent injection attacks. +- **Performance**: Instead of just showing all the filtered criteria, i also use pagination (`Pageable`) to handle large result sets efficiently. +- **Flexibility**: In the program i implemented, i expand the approach by handling more complex queries using JPA `Specifications` which makes the execution more smooth and dynamic. + +This approach ensures my API to be flexible, maintainable, and follows best practices for handling dynamic search criteria in a Spring Boot application using JPA. + +--- + +### 🌳 Project Structure +```bash +lecture_12 +β”œβ”€β”€ .mvn/wrapper/ +β”‚ └── maven-wrapper.properties +β”œβ”€β”€ src/main/ +β”‚ β”œβ”€β”€ java/com/example/lecture_12/ +β”‚ β”‚ β”œβ”€β”€ config/ +β”‚ β”‚ β”‚ └── WebConfig.java +β”‚ β”‚ β”œβ”€β”€ controller/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentController.java +β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeController.java +β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryController.java +β”‚ β”‚ β”‚ └── TitleController.java +β”‚ β”‚ β”œβ”€β”€ data/ +β”‚ β”‚ β”‚ β”œβ”€β”€ model/ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ composite/ +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptEmpId.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptManagerId.java +β”‚ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryId.java +β”‚ β”‚ β”‚ β”‚ β”‚ └── TitleId.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Department.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptEmp.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DeptManager.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Employee.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ Salary.java +β”‚ β”‚ β”‚ β”‚ └── Title.java +β”‚ β”‚ β”‚ └── repository/ +β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ DeptEmpRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ DeptManagerRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeRepository.java +β”‚ β”‚ β”‚ β”œβ”€β”€ Salary.Repositoryjava +β”‚ β”‚ β”‚ └── TitleRepository.java +β”‚ β”‚ β”œβ”€β”€ dto/ +β”‚ β”‚ β”‚ └── EmployeeSearchCriteriaDTO.java +β”‚ β”‚ β”œβ”€β”€ service/ +β”‚ β”‚ β”‚ β”œβ”€β”€ impl/ +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentServiceImpl.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeServiceImpl.java +β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryServiceImpl.java +β”‚ β”‚ β”‚ β”‚ └── TitleServiceImpl.java +β”‚ β”‚ β”‚ β”œβ”€β”€ DepartmentService.java +β”‚ β”‚ β”‚ β”œβ”€β”€ EmployeeService.java +β”‚ β”‚ β”‚ β”œβ”€β”€ SalaryService.java +β”‚ β”‚ β”‚ └── TitleService.java +β”‚ β”‚ └── Lecture12Application.java +β”‚ └── resources/ +β”‚ └── application.properties +β”œβ”€β”€ .gitignore +β”œβ”€β”€ mvnw +β”œβ”€β”€ mvnw.cmd +β”œβ”€β”€ pom.xml +β”œβ”€β”€ run.bat +└── run.sh +``` + +### 🧩 SQL Query Data +Here is the SQL query to create the database, table, and instantiate some data. +```sql +-- Create the database +CREATE DATABASE week6_lecture12; + +-- Use the database +USE week6_lecture12; + +-- Create employees table +CREATE TABLE employees ( + emp_no INT AUTO_INCREMENT PRIMARY KEY, + birth_date DATE NOT NULL, + first_name VARCHAR(14) NOT NULL, + last_name VARCHAR(16) NOT NULL, + gender ENUM('M', 'F') NOT NULL, + hire_date DATE NOT NULL +); + +-- Create departments table +CREATE TABLE departments ( + dept_no CHAR(4) PRIMARY KEY, + dept_name VARCHAR(40) NOT NULL UNIQUE +); + +-- Create dept_emp table +CREATE TABLE dept_emp ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create dept_manager table +CREATE TABLE dept_manager ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create salaries table +CREATE TABLE salaries ( + emp_no INT NOT NULL, + from_date DATE NOT NULL, + salary INT NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); + +-- Create titles table +CREATE TABLE titles ( + emp_no INT NOT NULL, + title VARCHAR(50) NOT NULL, + from_date DATE NOT NULL, + to_date DATE, + PRIMARY KEY (emp_no, title, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); +``` + +There are also query to insert some generated dummy data. All the MySQL queries is available on [this file](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/resources/data.sql). Here is the query to drop the database. +```sql +-- Drop the database +DROP DATABASE IF EXISTS week6_lecture12; +``` + +Also don't forget to configure [application properties](/Week%2006/Lecture%2012/Assignment%2001/lecture_12/src/main/resources/application.properties) with this format. +```java +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/<my_database> +spring.datasource.username=<my_user_name> +spring.datasource.password=<my_password> +``` + +and don't forget to add this to re-update the SQL DDL queries. +```java +spring.jpa.hibernate.ddl-auto=update +``` + +finally, don't forget to add this for hibernate SQL logging. +```java +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE +``` + + +### βš™οΈ How to run the program +1. Go to the `lecture_12` directory by using this command + ```bash + $ cd lecture_12 + ``` +2. Make sure you have maven installed on my computer, use `mvn -v` to check the version. +3. If you are using windows, you can run the program by using this command. + ```bash + $ ./run.bat + ``` + And if you are using Linux, you can run the program by using this command. + ```bash + $ chmod +x run.sh + $ ./run.sh + ``` + +If all the instruction is well executed, Open [localhost:8080](http://localhost:8080) to see that the REST APIs is now works. + +### πŸ”‘ List of Endpoints +| Endpoint | Method | Description | +|-----------------------------------------|:--------: |---------------------------------------------------------------------------------------------| +| /api/v1/employees | GET | Retrieve all employees with default pagination (page 1 with size 20 elements/page). | +| /api/v1/employees?page=1&size=5 | GET | Retrieve employees with pagination (page 2 with size 5 elements/page). | +| /api/v1/employees/{empNo} | GET | Retrieve a specific employee by employee number. | +| /api/v1/employees | POST | Create a new employee. | +| /api/v1/employees/{empNo} | PUT | Update an existing employee by employee number. | +| /api/v1/employees/{empNo} | DELETE | Delete an employee by employee number. | +| /api/v1/departments | GET | Retrieve all departments with default pagination (page 1 with size 20 elements/page). | +| /api/v1/departments?page=0&size=2 | GET | Retrieve departments with pagination (page 1 with size 2 elements/page). | +| /api/v1/departments/{deptNo} | GET | Retrieve a specific department by department number. | +| /api/v1/departments | POST | Create a new department. | +| /api/v1/departments/{deptNo} | PUT | Update an existing department by department number. | +| /api/v1/departments/{deptNo} | DELETE | Delete a department by department number. | +| /api/v1/salaries | GET | Retrieve salary by ID. | +| /api/v1/salaries | POST | Create a new salary record. | +| /api/v1/salaries | PUT | Update an existing salary record. | +| /api/v1/salaries | DELETE | Delete a salary record by ID. | +| /api/v1/titles | GET | Retrieve title by ID. | +| /api/v1/titles | POST | Create a new title. | +| /api/v1/titles | PUT | Update an existing title. | +| /api/v1/titles | DELETE | Delete a title record by ID. | + +#### Additional Dynamic Search Queries +Here is some demo on how to search employees based on dynamic search queries. All the method used are `GET`. + +| Endpoint | Description | +|-----------------------------------------|---------------------------------------------------------------------------------------------| +| /api/v1/employees/search?firstName=Paul&gender=M | Retrieve all male employees who the first name is Paul. | +| /api/v1/employees/search?birthDate=1991-03-22&lastName=Garcia | Retrieve all employees who the birth date is March 22nd, 1991 and the last name is Garcia. | +| /api/v1/employees/search?gender=F&page=1&size=3 | Retrieve all the female employees with pagination (page 2 with size 3 elements/page). | +| /api/v1/employees/search | Retrieve all the employees data with default pagination. | + +--- + +### πŸ”₯ Bonus - Advanced Query Functionality +#### Overview + +This part of assignment implements advanced query functionality for the `Employee` entity. The main motivation was to allow users to perform flexible and complex searches based on various criteria, including sorting and advanced operations like greater than, less than, and querying by specific date parts (e.g., month, year). The implementation uses Spring Data JPA's Specification and Criteria API to dynamically construct queries based on the provided search criteria (basically just modifying what i already made previously). + +#### Features +1. **Dynamic Filtering**: Allows filtering based on multiple criteria. +2. **Advanced Operations**: Supports operations like equals, greater than, less than, greater than or equal to, and less than or equal to. +3. **Date Part Queries**: Enables querying by specific parts of dates, such as month and year. +4. **Sorting**: Supports sorting by any field in ascending or descending order. +5. **Pagination**: Handles large datasets efficiently by providing pagination. + +#### Functionalities + +- Filter by first name, last name, gender, birth date, and hire date. +- Perform advanced operations (`eq`, `gt`, `lt`, `geq`, `leq`) on dates. +- Query by specific date parts (month, year). +- Sort results by any field. +- Paginate results to handle large datasets. + +The updated endpoint for searching employees with advanced criteria will look like this: + +##### Endpoint: `GET /api/v1/employees/search` + +##### Parameters + +- **page** (optional): The page number to retrieve (default is 0). +- **size** (optional): The number of elements per page (default is 20). +- **sortBy** (optional): The field to sort by (e.g., "firstName", "hireDate"). +- **sortOrder** (optional): The sort order ("asc" or "desc"). +- **firstName** (optional): The first name to filter by. +- **firstNameOperation** (optional): The operation for first name ("eq" for equals, "like" for like). +- **lastName** (optional): The last name to filter by. +- **lastNameOperation** (optional): The operation for last name ("eq" for equals, "like" for like). +- **gender** (optional): The gender to filter by. +- **genderOperation** (optional): The operation for gender ("eq" for equals). +- **birthDate** (optional): The birth date to filter by (format: YYYY-MM-DD). +- **birthDateOperation** (optional): The operation for birth date ("eq", "gt", "lt", "geq", "leq"). +- **hireDate** (optional): The hire date to filter by (format: YYYY-MM-DD). +- **hireDateOperation** (optional): The operation for hire date ("eq", "gt", "lt", "geq", "leq"). +- **birthMonth** (optional): The birth month to filter by (1-12). +- **birthYear** (optional): The birth year to filter by (e.g., 1970). +- **hireMonth** (optional): The hire month to filter by (1-12). +- **hireYear** (optional): The hire year to filter by (e.g., 2020). + +##### Example Request +Here is the URL `GET` request query to do this: + +Find all employees with the first name containing "John", hired after January 1st, 2020, sorted by last name in ascending order. The result will be paginated with custom pagination where maximum 10 employees/page and show the employees data on page 1 (0-based index). +```http +GET /api/v1/employees/search?page=0&size=10&sortBy=lastName&sortOrder=asc&firstName=John&firstNameOperation=like&hireDate=2020-01-01&hireDateOperation=gt +``` + +and here's the query used through hibernate logging: + +![Screenshot](img/hibernate.png) + +#### Advanced Query Examples + +Here are 10 examples of advanced queries you can perform with this implementation. All the method used are `GET`. + +| Endpoint | Description | +|-----------------------------------------|---------------------------------------------------------------------------------------------| +| /api/v1/employees/search?firstName=John&firstNameOperation=like&sortBy=lastName&sortOrder=asc | Find employees with first name containing "John" and sort by last name ascending. | +| /api/v1/employees/search?hireDate=2020-06-01&hireDateOperation=lt&size=40&page=2 | Find employees hired before June 1st, 2020 with custom pagination (page 3 with size 40 elements/page). | +| /api/v1/employees/search?birthDate=1990-01-01&birthDateOperation=gt&page=1 | Find employees born after January 1st, 1990 with default pagination, show employees on page 2. | +| /api/v1/employees/search?lastName=Smith&hireYear=2015 | Find employees with last name "Smith" and hired in the year 2015. | +| /api/v1/employees/search?birthMonth=2 | Find employees born in February. | +| /api/v1/employees/search?firstName=Albert&lastName=B&lastNameOperation=like&gender=M | Find male employees with first name "Albert" and last name starting with "B". | +| /api/v1/employees/search?firstName=Bobby&birthDate=1985-01-01&birthDateOperation=geq | Find employees with first name "Bobby" and birth date greater than or equal to January 1st, 1985. | +| /api/v1/employees/search?hireDate=2010-12-31&hireDateOperation=leq&sortBy=hireDate&sortOrder=desc&page=4 | Find employees hired on or before December 31st, 2010, and sort by hire date descending with default pagination, show employees on page 5. | +| /api/v1/employees/search?birthMonth=8&hireYear=2011 | Find employees born in August and hired in the year 2011. | +| /api/v1/employees/search?firstName=Michael&birthDate=1977-09-05&birthDateOperation=eq | Find employees with first name "Michael" and birth date equal to September 5th, 1977. | + +#### Remarks and Conclusion + +In the industry, building flexible and advanced search functionality for REST APIs is a common practice, especially for applications that handle complex data retrieval requirements. The approach i made is in line with how such functionality is typically implemented. Here are a few points that highlight common practices in the industry: + +1. **Specification and Criteria API**: Using the JPA Specification and Criteria API is a standard approach to build dynamic queries in a type-safe way. This allows for complex query construction based on various criteria, which is a common requirement in many applications. + +2. **Pagination and Sorting**: Providing support for pagination and sorting in API endpoints is essential for handling large datasets. This is typically done using Spring Data's `Pageable` and `Sort` interfaces, which i've included in the `findByCriteria` method. + +3. **DTOs for Search Criteria**: Using Data Transfer Objects (DTOs) to encapsulate search criteria is a common practice. This helps in structuring the input parameters and makes the API more maintainable and understandable. + +4. **Combining Filters and Operations**: Allowing different operations (e.g., equality, greater than, less than) and combining them with logical operators (AND, OR) is a typical requirement for advanced search functionality. The use of predicates in the Specification API facilitates this. + +5. **Documentation and Consistency**: Documenting the API endpoints and ensuring consistent parameter naming conventions is crucial. This helps other developers understand and use the API correctly. + +This advanced query functionality significantly enhances the flexibility and usability of the `Employee` API. By supporting complex query operations, sorting, and pagination, it caters to a wide range of search requirements, making it a robust solution for applications that need sophisticated data retrieval capabilities. + +### πŸ“¬ Postman Collection + +Here is the [postman collection](/Week%2006/Lecture%2012/Assignment%2001/Lecture%2012%20-%20Assignment%2001.postman_collection.json) you can use to demo the API functionality, including the bonus part i already made on the separate APIs folder. \ No newline at end of file diff --git a/Week 06/Lecture 12/Assignment 01/img/hibernate.png b/Week 06/Lecture 12/Assignment 01/img/hibernate.png new file mode 100644 index 0000000..75081df Binary files /dev/null and b/Week 06/Lecture 12/Assignment 01/img/hibernate.png differ diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/.gitignore b/Week 06/Lecture 12/Assignment 01/lecture_12/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/.mvn/wrapper/maven-wrapper.properties b/Week 06/Lecture 12/Assignment 01/lecture_12/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/mvnw b/Week 06/Lecture 12/Assignment 01/lecture_12/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/mvnw.cmd b/Week 06/Lecture 12/Assignment 01/lecture_12/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash> +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/pom.xml b/Week 06/Lecture 12/Assignment 01/lecture_12/pom.xml new file mode 100644 index 0000000..4d64216 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/pom.xml @@ -0,0 +1,105 @@ +<?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.3.1</version> + <relativePath/> <!-- lookup parent from repository --> + </parent> + <groupId>com.example</groupId> + <artifactId>lecture_12</artifactId> + <version>1.0-SNAPSHOT</version> + <name>lecture_12</name> + <description>Demo project for Spring Boot</description> + <url/> + <licenses> + <license/> + </licenses> + <developers> + <developer/> + </developers> + <scm> + <connection/> + <developerConnection/> + <tag/> + <url/> + </scm> + <properties> + <java.version>21</java.version> + </properties> + <dependencies> + <!-- SpringBoot Starter --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web-services</artifactId> + </dependency> + + <!-- JPA and MySQL Starter --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-jpa</artifactId> + </dependency> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + <version>8.0.33</version> + <scope>runtime</scope> + </dependency> + + <!-- Lombok Annotation --> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + + <!-- Validator and Validation --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + <dependency> + <groupId>org.hibernate.validator</groupId> + <artifactId>hibernate-validator</artifactId> + <version>8.0.0.Final</version> + </dependency> + <dependency> + <groupId>javax.validation</groupId> + <artifactId>validation-api</artifactId> + <version>2.0.1.Final</version> + </dependency> + + <!-- Mapper and struct --> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct</artifactId> + <version>1.5.3.Final</version> + </dependency> + <dependency> + <groupId>org.mapstruct</groupId> + <artifactId>mapstruct-processor</artifactId> + <version>1.5.3.Final</version> + <scope>provided</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/run.bat b/Week 06/Lecture 12/Assignment 01/lecture_12/run.bat new file mode 100644 index 0000000..d95af0e --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/run.bat @@ -0,0 +1,3 @@ +@echo off +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_12-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/run.sh b/Week 06/Lecture 12/Assignment 01/lecture_12/run.sh new file mode 100644 index 0000000..1fec79d --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_12-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/Lecture12Application.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/Lecture12Application.java new file mode 100644 index 0000000..3f704ca --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/Lecture12Application.java @@ -0,0 +1,13 @@ +package com.example.lecture_12; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture12Application { + + public static void main(String[] args) { + SpringApplication.run(Lecture12Application.class, args); + } + +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/config/WebConfig.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/config/WebConfig.java new file mode 100644 index 0000000..606e495 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/config/WebConfig.java @@ -0,0 +1,9 @@ +package com.example.lecture_12.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.web.config.EnableSpringDataWebSupport; + +@Configuration +@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) +public class WebConfig { +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/DepartmentController.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/DepartmentController.java new file mode 100644 index 0000000..b24de1a --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/DepartmentController.java @@ -0,0 +1,122 @@ +package com.example.lecture_12.controllers; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_12.data.model.Department; +import com.example.lecture_12.services.DepartmentService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/departments") +@AllArgsConstructor +public class DepartmentController { + + private final DepartmentService departmentService; + + /** + * This method retrieves {@link Page} of {@link Department} from the database. + * + * @param page The page number to retrieve (0-based index). + * @param size The number of elements per page. + * @return ResponseEntity<List<Department>> - A response entity containing a pages of {@link Department}. + * If the pages is empty, it returns a HTTP status code 204 (No Content). + * If the operation is successful, it returns a HTTP status code 200 (OK) with the pages of {@link Department}. + */ + @GetMapping + public ResponseEntity<Page<Department>> findAll(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + Page<Department> departments = departmentService.findAll(pageable); + + if (departments.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(departments); + } + + /** + * This method retrieves an {@link Department} from the database by its deptNo. + * + * @param deptNo The unique identifier of the {@link Department}. + * @return ResponseEntity<Department> - A response entity containing the {@link Department} if found, or a 404 Not Found status code if not found. + */ + @GetMapping(value = "/{deptNo}") + public ResponseEntity<Department> findDepartmentById(@PathVariable("deptNo") String deptNo) { + Optional<Department> departmentOpt= departmentService.findById(deptNo); + + if(departmentOpt.isPresent()) { + return ResponseEntity.ok(departmentOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + /** + * This method saves a {@link Department} to the database. + * + * @param department The department object to be saved. + * @return ResponseEntity<Department> - A response entity containing the saved {@link Department}. + * If the {@link Department} already exists in the database, it returns a HTTP status code 400 (Bad Request). + */ + @PostMapping + public ResponseEntity<Department> save(@RequestBody Department department) { + Optional<Department> departmentOpt = departmentService.findById(department.getDeptNo()); + + if (departmentOpt.isPresent()) { + return ResponseEntity.badRequest().build(); + } + + return ResponseEntity.ok(departmentService.save(department)); + } + + /** + * This method updates an existing {@link Department} in the database. + * + * @param department The department object to be updated. + * @return ResponseEntity<Department> - A response entity containing the updated {@link Department}. + * If the {@link Department} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping(value = "/{deptNo}") + public ResponseEntity<Department> update(@PathVariable("deptNo") String deptNo, @RequestBody Department department) { + Optional<Department> departmentOpt = departmentService.findById(deptNo); + + if (departmentOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(departmentService.save(department)); + } + + /** + * This method deletes an {@link Department} from the database by its deptNo. + * + * @param deptNo The unique identifier of the {@link Department} to be deleted. + * @return ResponseEntity<Department> - A response entity containing the deleted {@link Department} if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping(value = "/{deptNo}") + public ResponseEntity<Department> deleteDepartment(@PathVariable(value = "deptNo") String deptNo) { + Optional<Department> departmentOpt = departmentService.findById(deptNo); + + if(departmentOpt.isPresent()) { + departmentService.deleteById(deptNo); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/EmployeeController.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/EmployeeController.java new file mode 100644 index 0000000..f7abdde --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/EmployeeController.java @@ -0,0 +1,140 @@ +package com.example.lecture_12.controllers; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_12.data.model.Employee; +import com.example.lecture_12.dto.EmployeeSearchCriteriaDTO; +import com.example.lecture_12.services.EmployeeService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/employees") +@AllArgsConstructor +public class EmployeeController { + + private final EmployeeService employeeService; + + /** + * This method retrieves {@link Page} of {@link Employee} from the database. + * + * @param page The page number to retrieve (0-based index). + * @param size The number of elements per page. + * @return ResponseEntity<Page<Employee>> - A response entity containing a page of {@link Employee}. + * If the page is empty, it returns a HTTP status code 204 (No Content). + * If the operation is successful, it returns a HTTP status code 200 (OK) with the page of {@link Employee}. + */ + @GetMapping + public ResponseEntity<Page<Employee>> findAll(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + Page<Employee> employees = employeeService.findAll(pageable); + + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(employees); + } + + /** + * Endpoint to search for {@link Employee} entities based on the provided search criteria. + * Supports pagination, sorting, and various operations. + * + * @param criteria The criteria object containing fields to filter the search. + * @param page The page number to retrieve (default is 0). + * @param size The number of elements per page (default is 20). + * @return ResponseEntity containing a {@link Page} of {@link Employee} entities that match the criteria, + * or HTTP status code 204 (No Content) if no employees match the criteria. + */ + @GetMapping("/search") + public ResponseEntity<Page<Employee>> searchEmployees(EmployeeSearchCriteriaDTO criteria, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { + Pageable pageable = PageRequest.of(page, size); + Page<Employee> employees = employeeService.findByCriteria(criteria, pageable); + + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(employees); + } + + /** + * This method retrieves an {@link Employee} from the database by its empNo. + * + * @param empNo The unique identifier of the {@link Employee}. + * @return ResponseEntity<Employee> - A response entity containing the {@link Employee} if found, or a 404 Not Found status code if not found. + */ + @GetMapping(value = "/{empNo}") + public ResponseEntity<Employee> findEmployeeById(@PathVariable("empNo") Integer empNo) { + Optional<Employee> employeeOpt= employeeService.findById(empNo); + + if(employeeOpt.isPresent()) { + return ResponseEntity.ok(employeeOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + /** + * This method saves an {@link Employee} to the database. + * + * @param employee The employee object to be saved. + * @return ResponseEntity<Employee> - A response entity containing the saved {@link Employee}. + */ + @PostMapping + public ResponseEntity<Employee> save(@RequestBody Employee employee) { + return ResponseEntity.ok(employeeService.save(employee)); + } + + /** + * This method updates an existing {@link Employee} in the database. + * + * @param empNo The unique identifier of the {@link Employee} to be updated. + * @param employee The employee object to be updated. + * @return ResponseEntity<Employee> - A response entity containing the updated {@link Employee}. + * If the {@link Employee} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping(value = "/{empNo}") + public ResponseEntity<Employee> update(@PathVariable Integer empNo, @RequestBody Employee employee) { + Optional<Employee> employeeOpt = employeeService.findById(empNo); + + if (employeeOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + employee.setEmpNo(empNo); + return ResponseEntity.ok(employeeService.save(employee)); + } + + /** + * This method deletes an {@link Employee} from the database by its empNo. + * + * @param empNo The unique identifier of the {@link Employee} to be deleted. + * @return ResponseEntity<Employee> - A response entity containing the deleted {@link Employee} if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping(value = "/{empNo}") + public ResponseEntity<Employee> deleteEmployee(@PathVariable(value = "empNo") Integer empNo) { + Optional<Employee> employeeOpt = employeeService.findById(empNo); + + if(employeeOpt.isPresent()) { + employeeService.deleteById(empNo); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/SalaryController.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/SalaryController.java new file mode 100644 index 0000000..b61880d --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/SalaryController.java @@ -0,0 +1,90 @@ +package com.example.lecture_12.controllers; + +import java.util.Optional; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_12.data.model.Salary; +import com.example.lecture_12.data.model.composite.SalaryId; +import com.example.lecture_12.services.SalaryService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/salaries") +@AllArgsConstructor +public class SalaryController { + + private final SalaryService salaryService; + + /** + * This method retrieves a {@link Salary} from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Salary} to be retrieved. + * @return ResponseEntity<Salary> - A response entity containing the {@link Salary} if found, or a 404 Not Found status code if not found. + */ + @GetMapping + public ResponseEntity<Salary> findSalaryById(@RequestBody SalaryId id) { + Optional<Salary> salaryOpt= salaryService.findById(id); + + if(salaryOpt.isPresent()) { + return ResponseEntity.ok(salaryOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + /** + * This method saves a {@link Salary} to the database. + * + * @param salary The salary object to be saved. + * @return ResponseEntity<Salary> - A response entity containing the saved {@link Salary}. + */ + @PostMapping + public ResponseEntity<Salary> save(@RequestBody Salary salary) { + return ResponseEntity.ok(salaryService.save(salary)); + } + + /** + * This method updates an existing {@link Salary} in the database. + * + * @param salary The salary object to be updated. + * @return ResponseEntity<Salary> - A response entity containing the updated {@link Salary}. + * If the {@link Salary} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping + public ResponseEntity<Salary> update(@RequestBody Salary salary) { + Optional<Salary> salaryOpt = salaryService.findById(salary.getId()); + + if (salaryOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(salaryService.save(salary)); + } + + /** + * This method deletes an {@link Salary} from the database by its id. + * + * @param id The unique identifier of the {@link Salary} to be deleted. + * @return ResponseEntity<Salary> - A response entity containing the deleted {@link Salary} if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping + public ResponseEntity<Salary> deleteSalary(@RequestBody SalaryId id) { + Optional<Salary> salaryOpt = salaryService.findById(id); + + if(salaryOpt.isPresent()) { + salaryService.deleteById(id); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/TitleController.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/TitleController.java new file mode 100644 index 0000000..59c8bfe --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/controllers/TitleController.java @@ -0,0 +1,92 @@ +package com.example.lecture_12.controllers; + +import java.util.Optional; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_12.data.model.Title; +import com.example.lecture_12.data.model.composite.TitleId; +import com.example.lecture_12.services.TitleService; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/titles") +@AllArgsConstructor +public class TitleController { + + private final TitleService titleService; + + /** + * This method retrieves a {@link Title} from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Title} to be retrieved. + * @return ResponseEntity<Title> - A response entity containing the {@link Title} if found, or a 404 Not Found status code if not found. + */ + @GetMapping + public ResponseEntity<Title> findTitleById(@RequestBody TitleId id) { + Optional<Title> titleOpt= titleService.findById(id); + + if(titleOpt.isPresent()) { + return ResponseEntity.ok(titleOpt.get()); + } + + return ResponseEntity.notFound().build(); + } + + + /** + * This method saves a {@link Title} to the database. + * + * @param title The title object to be saved. + * @return ResponseEntity<Title> - A response entity containing the saved {@link Title}. + * If the {@link Title} already exists in the database, it returns a HTTP status code 400 (Bad Request). + */ + @PostMapping + public ResponseEntity<Title> save(@RequestBody Title title) { + return ResponseEntity.ok(titleService.save(title)); + } + + /** + * This method updates an existing {@link Title} in the database. + * + * @param title The title object to be updated. + * @return ResponseEntity<Title> - A response entity containing the updated {@link Title}. + * If the {@link Title} does not exist in the database, it returns a HTTP status code 404 (Not Found). + */ + @PutMapping + public ResponseEntity<Title> update(@RequestBody Title title) { + Optional<Title> titleOpt = titleService.findById(title.getId()); + + if (titleOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(titleService.save(title)); + } + + /** + * This method deletes a {@link Title} from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Title} to be deleted. + * @return ResponseEntity<Title> - A response entity containing the deleted {@link Title} if found and successfully deleted, or a 404 Not Found status code if not found. + */ + @DeleteMapping + public ResponseEntity<Title> deleteTitle(@RequestBody TitleId id) { + Optional<Title> titleOpt = titleService.findById(id); + + if (titleOpt.isPresent()) { + titleService.deleteById(id); + return ResponseEntity.ok().build(); + } + + return ResponseEntity.notFound().build(); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Department.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Department.java new file mode 100644 index 0000000..4d2eb20 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Department.java @@ -0,0 +1,24 @@ +package com.example.lecture_12.data.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "departments") +@NoArgsConstructor +@AllArgsConstructor +public class Department { + + @Id + @Column(length = 4) + private String deptNo; + + @Column(length = 40, nullable = false, unique = true) + private String deptName; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/DeptEmp.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/DeptEmp.java new file mode 100644 index 0000000..e080649 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/DeptEmp.java @@ -0,0 +1,34 @@ +package com.example.lecture_12.data.model; + +import java.time.LocalDate; + +import com.example.lecture_12.data.model.composite.DeptEmpId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "dept_emp") +@NoArgsConstructor +@AllArgsConstructor +public class DeptEmp { + + @EmbeddedId + private DeptEmpId id; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate fromDate; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/DeptManager.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/DeptManager.java new file mode 100644 index 0000000..689924c --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/DeptManager.java @@ -0,0 +1,34 @@ +package com.example.lecture_12.data.model; + +import java.time.LocalDate; + +import com.example.lecture_12.data.model.composite.DeptManagerId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "dept_manager") +@NoArgsConstructor +@AllArgsConstructor +public class DeptManager { + + @EmbeddedId + private DeptManagerId id; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate fromDate; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Employee.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Employee.java new file mode 100644 index 0000000..f5c86b5 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Employee.java @@ -0,0 +1,44 @@ +package com.example.lecture_12.data.model; + +import java.time.LocalDate; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "employees") +@NoArgsConstructor +@AllArgsConstructor +public class Employee { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer empNo; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate birthDate; + + @Column(length = 14, nullable = false) + private String firstName; + + @Column(length = 16, nullable = false) + private String lastName; + + @Column(columnDefinition = "enum('M','F')", nullable = false) + private String gender; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate hireDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Salary.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Salary.java new file mode 100644 index 0000000..993dc7f --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Salary.java @@ -0,0 +1,33 @@ +package com.example.lecture_12.data.model; + +import java.time.LocalDate; + +import com.example.lecture_12.data.model.composite.SalaryId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "salaries") +@NoArgsConstructor +@AllArgsConstructor +public class Salary { + + @EmbeddedId + private SalaryId id; + + @Column(nullable = false) + private Integer salary; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Title.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Title.java new file mode 100644 index 0000000..3f3c4a8 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/Title.java @@ -0,0 +1,30 @@ +package com.example.lecture_12.data.model; + +import java.time.LocalDate; + +import com.example.lecture_12.data.model.composite.TitleId; + +import jakarta.persistence.Column; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Entity +@Table(name = "titles") +@NoArgsConstructor +@AllArgsConstructor +public class Title { + + @EmbeddedId + private TitleId id; + + @Temporal(TemporalType.DATE) + @Column(nullable = false) + private LocalDate toDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/DeptEmpId.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/DeptEmpId.java new file mode 100644 index 0000000..6e5f5b0 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/DeptEmpId.java @@ -0,0 +1,15 @@ +package com.example.lecture_12.data.model.composite; + +import java.io.Serializable; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class DeptEmpId implements Serializable { + private Integer empNo; + private String deptNo; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/DeptManagerId.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/DeptManagerId.java new file mode 100644 index 0000000..dae8580 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/DeptManagerId.java @@ -0,0 +1,15 @@ +package com.example.lecture_12.data.model.composite; + +import java.io.Serializable; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class DeptManagerId implements Serializable { + private Integer empNo; + private String deptNo; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/SalaryId.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/SalaryId.java new file mode 100644 index 0000000..06e2d6d --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/SalaryId.java @@ -0,0 +1,16 @@ +package com.example.lecture_12.data.model.composite; + +import java.io.Serializable; +import java.time.LocalDate; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class SalaryId implements Serializable { + private Integer empNo; + private LocalDate fromDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/TitleId.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/TitleId.java new file mode 100644 index 0000000..dd803d2 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/model/composite/TitleId.java @@ -0,0 +1,17 @@ +package com.example.lecture_12.data.model.composite; + +import java.io.Serializable; +import java.time.LocalDate; + +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Data; + +@Data +@Embeddable +@EqualsAndHashCode +public class TitleId implements Serializable { + private Integer empNo; + private String title; + private LocalDate fromDate; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DepartmentRepository.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DepartmentRepository.java new file mode 100644 index 0000000..19497ef --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DepartmentRepository.java @@ -0,0 +1,11 @@ +package com.example.lecture_12.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.lecture_12.data.model.Department; + +@Repository +public interface DepartmentRepository extends JpaRepository<Department, String> { +} + diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DeptEmpRepository.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DeptEmpRepository.java new file mode 100644 index 0000000..2be1e26 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DeptEmpRepository.java @@ -0,0 +1,10 @@ +package com.example.lecture_12.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_12.data.model.DeptEmp; +import com.example.lecture_12.data.model.composite.DeptEmpId; + +public interface DeptEmpRepository extends JpaRepository<DeptEmp, DeptEmpId> { +} + diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DeptManagerRepository.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DeptManagerRepository.java new file mode 100644 index 0000000..00abce8 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/DeptManagerRepository.java @@ -0,0 +1,10 @@ +package com.example.lecture_12.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_12.data.model.DeptManager; +import com.example.lecture_12.data.model.composite.DeptManagerId; + +public interface DeptManagerRepository extends JpaRepository<DeptManager, DeptManagerId> { +} + diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/EmployeeRepository.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/EmployeeRepository.java new file mode 100644 index 0000000..9376b7d --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/EmployeeRepository.java @@ -0,0 +1,15 @@ +package com.example.lecture_12.data.repository; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.example.lecture_12.data.model.Employee; + +@Repository +public interface EmployeeRepository extends JpaRepository<Employee, Integer> { + // Define a custom query method using Specification and Pageable + Page<Employee> findAll(Specification<Employee> spec, Pageable pageable); +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/SalaryRepository.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/SalaryRepository.java new file mode 100644 index 0000000..6606448 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/SalaryRepository.java @@ -0,0 +1,9 @@ +package com.example.lecture_12.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_12.data.model.Salary; +import com.example.lecture_12.data.model.composite.SalaryId; + +public interface SalaryRepository extends JpaRepository<Salary, SalaryId> { +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/TitleRepository.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/TitleRepository.java new file mode 100644 index 0000000..c7cb171 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/data/repository/TitleRepository.java @@ -0,0 +1,9 @@ +package com.example.lecture_12.data.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.lecture_12.data.model.Title; +import com.example.lecture_12.data.model.composite.TitleId; + +public interface TitleRepository extends JpaRepository<Title, TitleId> { +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/dto/EmployeeSearchCriteriaDTO.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/dto/EmployeeSearchCriteriaDTO.java new file mode 100644 index 0000000..b35accd --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/dto/EmployeeSearchCriteriaDTO.java @@ -0,0 +1,25 @@ +package com.example.lecture_12.dto; + +import java.time.LocalDate; + +import lombok.Data; + +@Data +public class EmployeeSearchCriteriaDTO { + private LocalDate birthDate; + private Integer birthMonth; + private Integer birthYear; + private String birthDateOperation; + private String firstName; + private String firstNameOperation; + private String lastName; + private String lastNameOperation; + private String gender; + private String genderOperation; + private LocalDate hireDate; + private Integer hireMonth; + private Integer hireYear; + private String hireDateOperation; + private String sortBy; + private String sortOrder; +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/DepartmentService.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/DepartmentService.java new file mode 100644 index 0000000..a20d23b --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/DepartmentService.java @@ -0,0 +1,22 @@ +package com.example.lecture_12.services; + +import com.example.lecture_12.data.model.Department; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; + +public interface DepartmentService { + // Retrieves a paginated list of {@link Department} entities. + Page<Department> findAll(Pageable pageable); + + // Retrieves an {@link Department} entity by its unique identifier. + Optional<Department> findById(String deptNo); + + // Saves or updates an {@link Department} entity in the database. + Department save(Department department); + + // Deletes an {@link Department} entity from the database by its unique identifier. + void deleteById(String deptNo); +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/EmployeeService.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/EmployeeService.java new file mode 100644 index 0000000..02c89c0 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/EmployeeService.java @@ -0,0 +1,26 @@ +package com.example.lecture_12.services; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import com.example.lecture_12.data.model.Employee; +import com.example.lecture_12.dto.EmployeeSearchCriteriaDTO; + +public interface EmployeeService { + // Retrieves a paginated list of {@link Employee} entities. + Page<Employee> findAll(Pageable pageable); + + // Retrieves a paginated list of {@link Employee} entities based on the provided search criteria. + Page<Employee> findByCriteria(EmployeeSearchCriteriaDTO criteria, Pageable pageable); + + // Retrieves an {@link Employee} entity by its unique identifier. + Optional<Employee> findById(Integer empNo); + + // Saves or updates an {@link Employee} entity in the database. + Employee save(Employee employee); + + // Deletes an {@link Employee} entity from the database by its unique identifier. + void deleteById(Integer empNo); +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/SalaryService.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/SalaryService.java new file mode 100644 index 0000000..e1932ce --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/SalaryService.java @@ -0,0 +1,17 @@ +package com.example.lecture_12.services; + +import java.util.Optional; + +import com.example.lecture_12.data.model.Salary; +import com.example.lecture_12.data.model.composite.SalaryId; + +public interface SalaryService { + // Retrieves an {@link Salary} entity by its unique identifier. + Optional<Salary> findById(SalaryId id); + + // Saves or updates an {@link Salary} entity in the database. + Salary save(Salary salary); + + // Deletes an {@link Salary} entity from the database by its unique identifier. + void deleteById(SalaryId id); +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/TitleService.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/TitleService.java new file mode 100644 index 0000000..bda9cdb --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/TitleService.java @@ -0,0 +1,17 @@ +package com.example.lecture_12.services; + +import java.util.Optional; + +import com.example.lecture_12.data.model.Title; +import com.example.lecture_12.data.model.composite.TitleId; + +public interface TitleService { + // Retrieves an {@link Title} entity by its unique identifier. + Optional<Title> findById(TitleId id); + + // Saves or updates an {@link Title} entity in the database. + Title save(Title title); + + // Deletes an {@link Title} entity from the database by its unique identifier. + void deleteById(TitleId id); +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/DepartmentServiceImpl.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/DepartmentServiceImpl.java new file mode 100644 index 0000000..441b96d --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/DepartmentServiceImpl.java @@ -0,0 +1,64 @@ +package com.example.lecture_12.services.impl; + +import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import com.example.lecture_12.data.model.Department; +import com.example.lecture_12.data.repository.DepartmentRepository; +import com.example.lecture_12.services.DepartmentService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class DepartmentServiceImpl implements DepartmentService { + + private final DepartmentRepository departmentRepository; + + /** + * Retrieves a paginated list of {@link Department} entities. + * + * @param pageable The pagination and sorting parameters. + * @return A {@link Page} of {@link Department} entities. + */ + @Override + public Page<Department> findAll(Pageable pageable) { + return departmentRepository.findAll(pageable); + } + + /** + * Retrieves an {@link Department} entity by its unique identifier. + * + * @param deptNo The unique identifier of the {@link Department} entity to retrieve. + * @return An {@link Optional} containing the {@link Department} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Department> findById(String deptNo) { + return departmentRepository.findById(deptNo); + } + + /** + * Saves or updates an {@link Department} entity in the database. + * + * @param department The {@link Department} entity to be saved or updated. + * @return The saved or updated {@link Department} entity. + */ + @Override + public Department save(Department department) { + return departmentRepository.save(department); + } + + /** + * Deletes an {@link Department} entity from the database by its unique identifier. + * + * @param deptNo The unique identifier of the {@link Department} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(String deptNo) { + departmentRepository.deleteById(deptNo); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/EmployeeServiceImpl.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/EmployeeServiceImpl.java new file mode 100644 index 0000000..62ee440 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/EmployeeServiceImpl.java @@ -0,0 +1,162 @@ +package com.example.lecture_12.services.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +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.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; + +import com.example.lecture_12.data.model.Employee; +import com.example.lecture_12.data.repository.EmployeeRepository; +import com.example.lecture_12.dto.EmployeeSearchCriteriaDTO; +import com.example.lecture_12.services.EmployeeService; + +import jakarta.persistence.criteria.Predicate; +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + + private final EmployeeRepository employeeRepository; + + /** + * Retrieves a paginated list of {@link Employee} entities. + * + * @param pageable The pagination and sorting parameters. + * @return A {@link Page} of {@link Employee} entities. + */ + @Override + public Page<Employee> findAll(Pageable pageable) { + return employeeRepository.findAll(pageable); + } + + /** + * Retrieves a paginated list of {@link Employee} entities based on the provided search criteria. + * + * @param criteria The criteria object containing fields to filter the search. + * - firstName: Filter by first name with the specified operation (eq, like). + * - lastName: Filter by last name with the specified operation (eq, like). + * - gender: Filter by gender with the specified operation (eq). + * - birthDate: Filter by birth date with the specified operation (eq, gt, lt, geq, leq). + * - hireDate: Filter by hire date with the specified operation (eq, gt, lt, geq, leq). + * - birthMonth: Filter by birth month. + * - birthYear: Filter by birth year. + * - hireMonth: Filter by hire month. + * - hireYear: Filter by hire year. + * - sortBy: Field to sort by. + * - sortOrder: Sort order (asc, desc). + * @param pageable Pagination and sorting parameters. + * @return A {@link Page} of {@link Employee} entities that match the specified criteria. + */ + @Override + public Page<Employee> findByCriteria(EmployeeSearchCriteriaDTO criteria, Pageable pageable) { + Specification<Employee> specification = (root, query, cb) -> { + List<Predicate> predicates = new ArrayList<>(); + + // First name handling + if (criteria.getFirstName() != null) { + if ("like".equalsIgnoreCase(criteria.getFirstNameOperation())) { + predicates.add(cb.like(root.get("firstName"), "%" + criteria.getFirstName() + "%")); + } else { + predicates.add(cb.equal(root.get("firstName"), criteria.getFirstName())); + } + } + + // Last name handling + if (criteria.getLastName() != null) { + if ("like".equalsIgnoreCase(criteria.getLastNameOperation())) { + predicates.add(cb.like(root.get("lastName"), "%" + criteria.getLastName() + "%")); + } else { + predicates.add(cb.equal(root.get("lastName"), criteria.getLastName())); + } + } + + // Gender handling + if (criteria.getGender() != null) { + predicates.add(cb.equal(root.get("gender"), criteria.getGender())); + } + + // Birth date handling + if (criteria.getBirthDate() != null) { + switch (criteria.getBirthDateOperation()) { + case "gt" -> predicates.add(cb.greaterThan(root.get("birthDate"), criteria.getBirthDate())); + case "lt" -> predicates.add(cb.lessThan(root.get("birthDate"), criteria.getBirthDate())); + case "geq" -> predicates.add(cb.greaterThanOrEqualTo(root.get("birthDate"), criteria.getBirthDate())); + case "leq" -> predicates.add(cb.lessThanOrEqualTo(root.get("birthDate"), criteria.getBirthDate())); + default -> predicates.add(cb.equal(root.get("birthDate"), criteria.getBirthDate())); + } + } + if (criteria.getBirthMonth() != null) { + predicates.add(cb.equal(cb.function("MONTH", Integer.class, root.get("birthDate")), criteria.getBirthMonth())); + } + if (criteria.getBirthYear() != null) { + predicates.add(cb.equal(cb.function("YEAR", Integer.class, root.get("birthDate")), criteria.getBirthYear())); + } + + // Hire date handling + if (criteria.getHireDate() != null) { + switch (criteria.getHireDateOperation()) { + case "gt" -> predicates.add(cb.greaterThan(root.get("hireDate"), criteria.getHireDate())); + case "lt" -> predicates.add(cb.lessThan(root.get("hireDate"), criteria.getHireDate())); + case "geq" -> predicates.add(cb.greaterThanOrEqualTo(root.get("hireDate"), criteria.getHireDate())); + case "leq" -> predicates.add(cb.lessThanOrEqualTo(root.get("hireDate"), criteria.getHireDate())); + default -> predicates.add(cb.equal(root.get("hireDate"), criteria.getHireDate())); + } + } + if (criteria.getHireMonth() != null) { + predicates.add(cb.equal(cb.function("MONTH", Integer.class, root.get("hireDate")), criteria.getHireMonth())); + } + if (criteria.getHireYear() != null) { + predicates.add(cb.equal(cb.function("YEAR", Integer.class, root.get("hireDate")), criteria.getHireYear())); + } + + return cb.and(predicates.toArray(Predicate[]::new)); + }; + + if (criteria.getSortBy() != null && criteria.getSortOrder() != null) { + Sort sort = Sort.by(Sort.Direction.fromString(criteria.getSortOrder()), criteria.getSortBy()); + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort); + } + + return employeeRepository.findAll(specification, pageable); + } + + /** + * Retrieves an {@link Employee} entity by its unique identifier. + * + * @param empNo The unique identifier of the {@link Employee} entity to retrieve. + * @return An {@link Optional} containing the {@link Employee} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Employee> findById(Integer empNo) { + return employeeRepository.findById(empNo); + } + + /** + * Saves or updates an {@link Employee} entity in the database. + * + * @param employee The {@link Employee} entity to be saved or updated. + * @return The saved or updated {@link Employee} entity. + */ + @Override + public Employee save(Employee employee) { + return employeeRepository.save(employee); + } + + /** + * Deletes an {@link Employee} entity from the database by its unique identifier. + * + * @param empNo The unique identifier of the {@link Employee} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(Integer empNo) { + employeeRepository.deleteById(empNo); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/SalaryServiceImpl.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/SalaryServiceImpl.java new file mode 100644 index 0000000..63e5541 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/SalaryServiceImpl.java @@ -0,0 +1,52 @@ +package com.example.lecture_12.services.impl; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.example.lecture_12.data.model.Salary; +import com.example.lecture_12.data.model.composite.SalaryId; +import com.example.lecture_12.data.repository.SalaryRepository; +import com.example.lecture_12.services.SalaryService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class SalaryServiceImpl implements SalaryService { + + private final SalaryRepository salaryRepository; + + /** + * Retrieves an {@link Salary} entity by its unique identifier. + * + * @param id The unique identifier of the {@link Salary} entity to retrieve. + * @return An {@link Optional} containing the {@link Salary} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Salary> findById(SalaryId id) { + return salaryRepository.findById(id); + } + + /** + * Saves or updates an {@link Salary} entity in the database. + * + * @param salary The {@link Salary} entity to be saved or updated. + * @return The saved or updated {@link Salary} entity. + */ + @Override + public Salary save(Salary salary) { + return salaryRepository.save(salary); + } + + /** + * Deletes an {@link Salary} entity from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Salary} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(SalaryId id) { + salaryRepository.deleteById(id); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/TitleServiceImpl.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/TitleServiceImpl.java new file mode 100644 index 0000000..fb6357a --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/java/com/example/lecture_12/services/impl/TitleServiceImpl.java @@ -0,0 +1,52 @@ +package com.example.lecture_12.services.impl; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.example.lecture_12.data.model.Title; +import com.example.lecture_12.data.model.composite.TitleId; +import com.example.lecture_12.data.repository.TitleRepository; +import com.example.lecture_12.services.TitleService; + +import lombok.AllArgsConstructor; + +@Service +@AllArgsConstructor +public class TitleServiceImpl implements TitleService { + + private final TitleRepository titleRepository; + + /** + * Retrieves an {@link Title} entity by its unique identifier. + * + * @param id The unique identifier of the {@link Title} entity to retrieve. + * @return An {@link Optional} containing the {@link Title} entity if found, or an empty {@link Optional} if not found. + */ + @Override + public Optional<Title> findById(TitleId id) { + return titleRepository.findById(id); + } + + /** + * Saves or updates an {@link Title} entity in the database. + * + * @param title The {@link Title} entity to be saved or updated. + * @return The saved or updated {@link Title} entity. + */ + @Override + public Title save(Title title) { + return titleRepository.save(title); + } + + /** + * Deletes an {@link Title} entity from the database by its unique identifier. + * + * @param id The unique identifier of the {@link Title} entity to be deleted. + * @return No return value, as the operation is void. + */ + @Override + public void deleteById(TitleId id) { + titleRepository.deleteById(id); + } +} diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/resources/application.properties b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/resources/application.properties new file mode 100644 index 0000000..2ad9b31 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/resources/application.properties @@ -0,0 +1,14 @@ +spring.application.name=lecture_12 + +# Datasorce connection data +spring.datasource.url=jdbc:mysql://localhost:3308/week6_lecture12?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource.username=root +spring.datasource.password=Michaeleon16606_ +spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.jpa.hibernate.ddl-auto=update + +# Enable SQL logging and show the statements and params + formatting +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE \ No newline at end of file diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/resources/data.sql b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/resources/data.sql new file mode 100644 index 0000000..e2981dd --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/main/resources/data.sql @@ -0,0 +1,247 @@ +-- Database schema initializer +-- Create employees table +CREATE TABLE employees ( + emp_no INT AUTO_INCREMENT PRIMARY KEY, + birth_date DATE NOT NULL, + first_name VARCHAR(14) NOT NULL, + last_name VARCHAR(16) NOT NULL, + gender ENUM('M', 'F') NOT NULL, + hire_date DATE NOT NULL +); + +-- Create departments table +CREATE TABLE departments ( + dept_no CHAR(4) PRIMARY KEY, + dept_name VARCHAR(40) NOT NULL UNIQUE +); + +-- Create dept_emp table +CREATE TABLE dept_emp ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create dept_manager table +CREATE TABLE dept_manager ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, dept_no), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments(dept_no) ON DELETE CASCADE +); + +-- Create salaries table +CREATE TABLE salaries ( + emp_no INT NOT NULL, + from_date DATE NOT NULL, + salary INT NOT NULL, + to_date DATE NOT NULL, + PRIMARY KEY (emp_no, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); + +-- Create titles table +CREATE TABLE titles ( + emp_no INT NOT NULL, + title VARCHAR(50) NOT NULL, + from_date DATE NOT NULL, + to_date DATE, + PRIMARY KEY (emp_no, title, from_date), + FOREIGN KEY (emp_no) REFERENCES employees(emp_no) ON DELETE CASCADE +); + +-- Database Initial Seeding +-- Insert employees +INSERT INTO employees (birth_date, first_name, last_name, gender, hire_date) VALUES +('1980-01-01', 'John', 'Doe', 'M', '2000-01-01'), +('1985-05-23', 'Jane', 'Smith', 'F', '2005-05-01'), +('1990-07-11', 'Alice', 'Johnson', 'F', '2010-06-01'), +('1975-02-14', 'Bob', 'Brown', 'M', '1995-03-01'), +('1988-12-25', 'Charlie', 'Davis', 'M', '2008-12-01'), +('1981-04-10', 'David', 'Evans', 'M', '2001-04-10'), +('1986-08-15', 'Laura', 'Wilson', 'F', '2006-08-15'), +('1991-03-22', 'Karen', 'Garcia', 'F', '2011-03-22'), +('1976-06-12', 'Paul', 'Martinez', 'M', '1996-06-12'), +('1989-11-30', 'Nancy', 'Rodriguez', 'F', '2009-11-30'), +('1977-09-05', 'Michael', 'Clark', 'M', '1997-09-05'), +('1982-11-02', 'Barbara', 'Lewis', 'F', '2002-11-02'), +('1987-10-18', 'James', 'Lee', 'M', '2007-10-18'), +('1992-01-26', 'Susan', 'Walker', 'F', '2012-01-26'), +('1978-04-17', 'Brian', 'Hall', 'M', '1998-04-17'), +('1983-03-30', 'Sarah', 'Allen', 'F', '2003-03-30'), +('1988-07-14', 'Christopher', 'Young', 'M', '2008-07-14'), +('1993-02-20', 'Patricia', 'King', 'F', '2013-02-20'), +('1979-11-23', 'George', 'Wright', 'M', '1999-11-23'), +('1984-08-09', 'Linda', 'Scott', 'F', '2004-08-09'), +('1989-06-15', 'Thomas', 'Green', 'M', '2009-06-15'), +('1994-09-29', 'Donna', 'Adams', 'F', '2014-09-29'), +('1975-12-31', 'Daniel', 'Baker', 'M', '1995-12-31'), +('1980-10-23', 'Betty', 'Gonzalez', 'F', '2000-10-23'), +('1985-05-05', 'Steven', 'Nelson', 'M', '2005-05-05'), +('1990-11-08', 'Sandra', 'Carter', 'F', '2010-11-08'), +('1976-07-15', 'Eric', 'Mitchell', 'M', '1996-07-15'), +('1981-02-27', 'Sharon', 'Perez', 'F', '2001-02-27'), +('1986-12-10', 'Kevin', 'Roberts', 'M', '2006-12-10'), +('1991-08-21', 'Carol', 'Turner', 'F', '2011-08-21'), +('1977-05-03', 'Edward', 'Phillips', 'M', '1997-05-03'), +('1982-03-11', 'Martha', 'Campbell', 'F', '2002-03-11'), +('1987-09-25', 'Joshua', 'Parker', 'M', '2007-09-25'), +('1992-06-30', 'Rebecca', 'Evans', 'F', '2012-06-30'), +('1978-12-08', 'Gregory', 'Edwards', 'M', '1998-12-08'), +('1983-07-22', 'Virginia', 'Collins', 'F', '2003-07-22'), +('1988-05-29', 'Andrew', 'Stewart', 'M', '2008-05-29'), +('1993-11-13', 'Kathleen', 'Sanchez', 'F', '2013-11-13'), +('1979-04-19', 'Henry', 'Morris', 'M', '1999-04-19'), +('1984-10-01', 'Diane', 'Rogers', 'F', '2004-10-01'), +('1989-02-16', 'Patrick', 'Reed', 'M', '2009-02-16'), +('1994-12-22', 'Deborah', 'Cook', 'F', '2014-12-22'), +('1975-03-28', 'Adam', 'Morgan', 'M', '1995-03-28'), +('1980-09-14', 'Frances', 'Bell', 'F', '2000-09-14'), +('1985-04-24', 'Raymond', 'Murphy', 'M', '2005-04-24'), +('1990-03-09', 'Jacqueline', 'Bailey', 'F', '2010-03-09'), +('1976-01-30', 'Jack', 'Rivera', 'M', '1996-01-30'), +('1981-11-18', 'Janet', 'Cooper', 'F', '2001-11-18'), +('1986-08-03', 'Walter', 'Richardson', 'M', '2006-08-03'), +('1991-04-26', 'Christine', 'Cox', 'F', '2011-04-26'), +('1977-06-06', 'Peter', 'Howard', 'M', '1997-06-06'), +('1982-12-31', 'Kathryn', 'Ward', 'F', '2002-12-31'), +('1987-05-21', 'Harold', 'Torres', 'M', '2007-05-21'), +('1992-10-15', 'Maria', 'Peterson', 'F', '2012-10-15'), +('1978-08-07', 'Douglas', 'Gray', 'M', '1998-08-07'), +('1983-01-14', 'Evelyn', 'Ramirez', 'F', '2003-01-14'), +('1988-11-27', 'Jerry', 'James', 'M', '2008-11-27'), +('1993-05-19', 'Janice', 'Watson', 'F', '2013-05-19'), +('1979-07-31', 'Ryan', 'Brooks', 'M', '1999-07-31'), +('1984-02-05', 'Heather', 'Kelly', 'F', '2004-02-05'), +('1989-10-22', 'Lawrence', 'Sanders', 'M', '2009-10-22'), +('1994-03-15', 'Judith', 'Price', 'F', '2014-03-15'), +('1975-11-29', 'Albert', 'Bennett', 'M', '1995-11-29'), +('1980-06-04', 'Ann', 'Wood', 'F', '2000-06-04'), +('1985-07-08', 'Joe', 'Barnes', 'M', '2005-07-08'), +('1990-02-03', 'Rachel', 'Ross', 'F', '2010-02-03'), +('1976-03-20', 'Arthur', 'Henderson', 'M', '1996-03-20'), +('1981-09-09', 'Julia', 'Coleman', 'F', '2001-09-09'), +('1986-11-30', 'Bruce', 'Jenkins', 'M', '2006-11-30'), +('1991-07-17', 'Hannah', 'Perry', 'F', '2011-07-17'), +('1977-02-12', 'Philip', 'Powell', 'M', '1997-02-12'), +('1982-04-29', 'Catherine', 'Long', 'F', '2002-04-29'), +('1987-03-06', 'Chris', 'Patterson', 'M', '2007-03-06'), +('1992-08-11', 'Kathy', 'Hughes', 'F', '2012-08-11'), +('1978-10-28', 'Jonathan', 'Flores', 'M', '1998-10-28'), +('1983-06-01', 'Megan', 'Washington', 'F', '2003-06-01'), +('1988-09-13', 'Albert', 'Butler', 'M', '2008-09-13'), +('1993-01-05', 'Katherine', 'Simmons', 'F', '2013-01-05'), +('1979-05-15', 'Anthony', 'Foster', 'M', '1999-05-15'), +('1984-08-25', 'Diana', 'Gonzales', 'F', '2004-08-25'), +('1989-12-02', 'Johnny', 'Bryant', 'M', '2009-12-02'), +('1994-11-14', 'Theresa', 'Alexander', 'F', '2014-11-14'), +('1975-04-07', 'Randy', 'Russell', 'M', '1995-04-07'), +('1980-01-19', 'Stephanie', 'Griffin', 'F', '2000-01-19'), +('1985-12-08', 'Jesse', 'Diaz', 'M', '2005-12-08'), +('1990-04-25', 'Angela', 'Hayes', 'F', '2010-04-25'), +('1976-08-18', 'Billy', 'Myers', 'M', '1996-08-18'), +('1981-07-04', 'Helen', 'Ford', 'F', '2001-07-04'), +('1986-05-10', 'Ralph', 'Hamilton', 'M', '2006-05-10'), +('1991-10-03', 'Frances', 'Graham', 'F', '2011-10-03'), +('1977-11-23', 'Roy', 'Sullivan', 'M', '1997-11-23'), +('1982-02-16', 'Virginia', 'Wallace', 'F', '2002-02-16'), +('1987-01-29', 'Bobby', 'Woods', 'M', '2007-01-29'), +('1992-07-20', 'Janet', 'Cole', 'F', '2012-07-20'), +('1978-06-24', 'Terry', 'West', 'M', '1998-06-24'), +('1983-09-06', 'Maria', 'Jordan', 'F', '2003-09-06'), +('1988-04-03', 'Bruce', 'Owens', 'M', '2008-04-03'), +('1993-10-30', 'Paula', 'Reynolds', 'F', '2013-10-30'), +('1979-03-18', 'Scott', 'Fisher', 'M', '1999-03-18'), +('1984-12-26', 'Kelly', 'Ellis', 'F', '2004-12-26'), +('1989-08-14', 'Sean', 'Harrison', 'M', '2009-08-14'), +('1994-05-09', 'Anne', 'Gibson', 'F', '2014-05-09'), +('1975-10-20', 'Walter', 'Mcdonald', 'M', '1995-10-20'), +('1980-05-17', 'Denise', 'Cruz', 'F', '2000-05-17'), +('1985-01-03', 'Eugene', 'Marshall', 'M', '2005-01-03'), +('1990-07-28', 'Judith', 'Ortiz', 'F', '2010-07-28'), +('1976-04-05', 'Jesse', 'Gomez', 'M', '1996-04-05'), +('1981-10-12', 'Jacqueline', 'Murray', 'F', '2001-10-12'); + +-- Insert departments +INSERT INTO departments (dept_no, dept_name) VALUES +('d001', 'Marketing'), +('d002', 'Finance'), +('d003', 'Human Resources'), +('d004', 'Engineering'), +('d005', 'Sales'); + +-- Insert dept_emp +INSERT INTO dept_emp (emp_no, dept_no, from_date, to_date) VALUES +(1, 'd001', '2000-01-01', '2002-01-01'), +(1, 'd002', '2002-01-01', '9999-01-01'), +(2, 'd002', '2005-05-01', '2010-05-01'), +(2, 'd003', '2010-05-01', '9999-01-01'), +(3, 'd003', '2010-06-01', '9999-01-01'), +(3, 'd004', '2011-01-01', '9999-01-01'), +(4, 'd004', '1995-03-01', '9999-01-01'), +(4, 'd005', '2000-01-01', '9999-01-01'), +(5, 'd001', '2008-12-01', '9999-01-01'), +(5, 'd005', '2010-01-01', '9999-01-01'), +(6, 'd002', '2001-04-10', '2003-04-10'), +(6, 'd003', '2003-04-10', '9999-01-01'), +(7, 'd003', '2006-08-15', '2011-08-15'), +(7, 'd004', '2011-08-15', '9999-01-01'), +(8, 'd001', '2011-03-22', '9999-01-01'), +(9, 'd004', '1996-06-12', '2006-06-12'), +(9, 'd005', '2006-06-12', '9999-01-01'), +(10, 'd005', '2009-11-30', '9999-01-01'); + +-- Insert dept_manager +INSERT INTO dept_manager (emp_no, dept_no, from_date, to_date) VALUES +(1, 'd001', '2000-01-01', '2002-01-01'), +(2, 'd002', '2005-05-01', '2010-05-01'), +(3, 'd003', '2010-06-01', '2011-01-01'); + +-- Insert salaries +INSERT INTO salaries (emp_no, salary, from_date, to_date) VALUES +(1, 60000, '2000-01-01', '2002-01-01'), +(1, 65000, '2002-01-01', '9999-01-01'), +(2, 75000, '2005-05-01', '2010-05-01'), +(2, 80000, '2010-05-01', '9999-01-01'), +(3, 80000, '2010-06-01', '2011-01-01'), +(3, 85000, '2011-01-01', '9999-01-01'), +(4, 90000, '1995-03-01', '2000-01-01'), +(4, 95000, '2000-01-01', '9999-01-01'), +(5, 85000, '2008-12-01', '2010-01-01'), +(5, 90000, '2010-01-01', '9999-01-01'), +(6, 65000, '2001-04-10', '2003-04-10'), +(6, 70000, '2003-04-10', '9999-01-01'), +(7, 70000, '2006-08-15', '2011-08-15'), +(7, 75000, '2011-08-15', '9999-01-01'), +(8, 72000, '2011-03-22', '9999-01-01'), +(9, 95000, '1996-06-12', '2006-06-12'), +(9, 100000, '2006-06-12', '9999-01-01'), +(10, 86000, '2009-11-30', '9999-01-01'); + +-- Insert titles +INSERT INTO titles (emp_no, title, from_date, to_date) VALUES +(1, 'Manager', '2000-01-01', '2002-01-01'), +(1, 'Senior Manager', '2002-01-01', '9999-01-01'), +(2, 'Analyst', '2005-05-01', '2010-05-01'), +(2, 'Senior Analyst', '2010-05-01', '9999-01-01'), +(3, 'HR Specialist', '2010-06-01', '2011-01-01'), +(3, 'HR Manager', '2011-01-01', '9999-01-01'), +(4, 'Engineer', '1995-03-01', '2000-01-01'), +(4, 'Senior Engineer', '2000-01-01', '9999-01-01'), +(5, 'Sales Representative', '2008-12-01', '2010-01-01'), +(5, 'Senior Sales Representative', '2010-01-01', '9999-01-01'), +(6, 'Finance Specialist', '2001-04-10', '2003-04-10'), +(6, 'Senior Finance Specialist', '2003-04-10', '9999-01-01'), +(7, 'HR Manager', '2006-08-15', '2011-08-15'), +(7, 'Senior HR Manager', '2011-08-15', '9999-01-01'), +(8, 'Marketing Specialist', '2011-03-22', '9999-01-01'), +(9, 'Senior Engineer', '1996-06-12', '2006-06-12'), +(9, 'Chief Engineer', '2006-06-12', '9999-01-01'), +(10, 'Senior Sales Representative', '2009-11-30', '9999-01-01'); diff --git a/Week 06/Lecture 12/Assignment 01/lecture_12/src/test/java/com/example/lecture_12/Lecture12ApplicationTests.java b/Week 06/Lecture 12/Assignment 01/lecture_12/src/test/java/com/example/lecture_12/Lecture12ApplicationTests.java new file mode 100644 index 0000000..fb1c433 --- /dev/null +++ b/Week 06/Lecture 12/Assignment 01/lecture_12/src/test/java/com/example/lecture_12/Lecture12ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.lecture_12; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Lecture12ApplicationTests { + + @Test + void contextLoads() { + } + +}