Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Java CI

on:
pull_request:
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Java 11
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "11"
cache: maven

- name: Run tests
run: ./mvnw test
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
target/
*.class

.idea/
.vscode/
*.iml

.classpath
.project
.settings/

*.log
.env
2 changes: 2 additions & 0 deletions .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
192 changes: 171 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,178 @@
# My-SpringBoot-MySQL-Application
# My Spring Boot MySQL Application

# Read Me First
The following was discovered as part of building this project:
This is an educational Spring Boot REST API for managing employees,
departments, addresses, profiles, and simple login/logout state. The app uses
Spring Web, Spring Data JPA, Bean Validation, and MySQL for local development.

* The original package name 'com.hemant.db.springboot-mysql' is invalid and this project uses 'com.hemant.db.springbootmysql' instead.
## Tech Stack

# Getting Started
- Java 11
- Spring Boot 2.7.x
- Spring Web
- Spring Data JPA
- MySQL Connector/J
- H2 for automated tests
- Maven Wrapper

### Reference Documentation
For further reference, please consider the following sections:
## Project Structure

* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.5.2/maven-plugin/reference/html/)
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.5.2/maven-plugin/reference/html/#build-image)
* [Spring Web](https://docs.spring.io/spring-boot/docs/2.5.2/reference/htmlsingle/#boot-features-developing-web-applications)
* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/2.5.2/reference/htmlsingle/#boot-features-jpa-and-spring-data)
* [JDBC API](https://docs.spring.io/spring-boot/docs/2.5.2/reference/htmlsingle/#boot-features-sql)
```text
.
|-- pom.xml
|-- mvnw / mvnw.cmd
|-- src/
| |-- main/
| | |-- java/com/hemant/db/
| | | |-- SpringbootMysqlApplication.java
| | | |-- exception/
| | | |-- model/
| | | |-- repository/
| | | |-- resource/
| | | `-- service/
| | `-- resources/application.yml
| `-- test/
| |-- java/com/hemant/db/
| `-- resources/application-test.yml
`-- .github/workflows/ci.yml
```

### Guides
The following guides illustrate how to use some features concretely:
### Main Packages

* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/)
* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
* [Accessing Relational Data using JDBC with Spring](https://spring.io/guides/gs/relational-data-access/)
* [Managing Transactions](https://spring.io/guides/gs/managing-transactions/)
`model` contains the JPA entities that map to database tables. Examples:
`Employee`, `Department`, `Address`, and `Profile`.

`repository` contains Spring Data JPA repositories. These interfaces provide
database access methods such as `findByEmail`, `findByCity`, and `findByName`.

`service` contains business logic. Controllers call services instead of talking
directly to repositories, which keeps request handling and application rules
separate.

`resource` contains REST controllers. These preserve the original `/rest/...`
routes while delegating work to services.

`exception` contains shared API error handling. Missing records, duplicate
records, bad requests, and validation failures return consistent JSON error
responses.

## Configuration

The app reads database settings from environment variables with local defaults:

| Variable | Default |
| --- | --- |
| `MYSQL_URL` | `jdbc:mysql://localhost:3306/employee_db` |
| `MYSQL_USERNAME` | `root` |
| `MYSQL_PASSWORD` | empty |
| `JPA_DDL_AUTO` | `update` |
| `JPA_SHOW_SQL` | `true` |

Example PowerShell setup:

```powershell
$env:MYSQL_URL="jdbc:mysql://localhost:3306/employee_db"
$env:MYSQL_USERNAME="root"
$env:MYSQL_PASSWORD="your-password"
```

## Run Locally

Start MySQL and create the database:

```sql
CREATE DATABASE employee_db;
```

Then run the application:

```powershell
.\mvnw.cmd spring-boot:run
```

On macOS/Linux:

```bash
./mvnw spring-boot:run
```

The API starts on the default Spring Boot port:

```text
http://localhost:8080
```

## API Overview

The existing route style is preserved.

### Employees

- `GET /rest/Employee/all`
- `POST /rest/Employee/insert`
- `GET /rest/Employee/findbyname?name=...`
- `GET /rest/Employee/findbydesignation?designation=...`
- `POST /rest/Employee/updatedesignation?Id=...&designation=...`
- `POST /rest/Employee/updatemobile?Id=...&mobile=...`
- `POST /rest/Employee/updatepassword?Id=...&password=...`
- `DELETE /rest/Employee/delete?Id=...`

### Departments

- `GET /rest/Department/all`
- `POST /rest/Department/insert`
- `GET /rest/Department/findbyname?name=...`
- `GET /rest/Department/findbyaddress?address=...`
- `POST /rest/Department/updatefloor?Id=...&floor=...`
- `POST /rest/Department/updateaddress?Id=...&address=...`
- `DELETE /rest/Department/delete?Id=...`

### Addresses

- `GET /rest/Address/all`
- `POST /rest/Address/insert`
- `GET /rest/Address/findbycity?city=...`
- `GET /rest/Address/findbystate?state=...`
- `POST /rest/Address/updateaddress?...`
- `DELETE /rest/Address/delete?Id=...`

### Profiles

- `GET /rest/Profile/all`
- `POST /rest/Profile/insert`
- `GET /rest/Profile/findbygender?gender=...`
- `GET /rest/Profile/findbyhobbies?hobbies=...`
- `POST /rest/Profile/updatehobbies?Id=...&hobbies=...`
- `DELETE /rest/Profile/delete?Id=...`

### Login

- `GET /rest/Login/findbyemail?email=...`
- `POST /rest/Login/login?email=...&password=...`
- `POST /rest/Login/logout?email=...`

## Testing

Tests use H2 in MySQL compatibility mode, so they do not require a local MySQL
server.

```powershell
.\mvnw.cmd test
```

On macOS/Linux:

```bash
./mvnw test
```

The GitHub Actions workflow also runs the Maven test suite on Java 11 for pushes
to `main` and pull requests.

## Notes

- Passwords are accepted for create/update/login operations but are hidden from
JSON responses.
- This project intentionally keeps the original route naming style to avoid
breaking existing clients.
- Authentication is educational only. It stores a login status and plain
password values, so it should not be used as-is for production security.
Empty file modified mvnw
100644 → 100755
Empty file.
13 changes: 9 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<version>2.7.18</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hemant.db</groupId>
Expand Down Expand Up @@ -35,14 +35,19 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version><!--$NO-MVN-MAN-VER$-->
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/com/hemant/db/exception/ApiError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.hemant.db.exception;

import java.time.Instant;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiError {
private final Instant timestamp;
private final int status;
private final String error;
private final String message;
private final Map<String, String> details;

public ApiError(int status, String error, String message, Map<String, String> details) {
this.timestamp = Instant.now();
this.status = status;
this.error = error;
this.message = message;
this.details = details;
}

public Instant getTimestamp() {
return timestamp;
}

public int getStatus() {
return status;
}

public String getError() {
return error;
}

public String getMessage() {
return message;
}

public Map<String, String> getDetails() {
return details;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.hemant.db.exception;

public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.hemant.db.exception;

public class DuplicateResourceException extends RuntimeException {
public DuplicateResourceException(String message) {
super(message);
}
}
64 changes: 64 additions & 0 deletions src/main/java/com/hemant/db/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.hemant.db.exception;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import javax.validation.ConstraintViolationException;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiError> handleResourceNotFound(ResourceNotFoundException ex) {
return buildResponse(HttpStatus.NOT_FOUND, ex.getMessage(), null);
}

@ExceptionHandler(DuplicateResourceException.class)
public ResponseEntity<ApiError> handleDuplicateResource(DuplicateResourceException ex) {
return buildResponse(HttpStatus.CONFLICT, ex.getMessage(), null);
}

@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ApiError> handleBadRequest(BadRequestException ex) {
return buildResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), null);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> details = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
details.put(fieldName, errorMessage);
});
return buildResponse(HttpStatus.BAD_REQUEST, "Validation failed", details);
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiError> handleConstraintViolation(ConstraintViolationException ex) {
Map<String, String> details = ex.getConstraintViolations().stream()
.collect(Collectors.toMap(
violation -> violation.getPropertyPath().toString(),
violation -> violation.getMessage(),
(left, right) -> left));
return buildResponse(HttpStatus.BAD_REQUEST, "Validation failed", details);
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ApiError> handleMissingParameter(MissingServletRequestParameterException ex) {
return buildResponse(HttpStatus.BAD_REQUEST, ex.getParameterName() + " parameter is required", null);
}

private ResponseEntity<ApiError> buildResponse(HttpStatus status, String message, Map<String, String> details) {
ApiError error = new ApiError(status.value(), status.getReasonPhrase(), message, details);
return new ResponseEntity<>(error, status);
}
}
Loading
Loading