Spring Boot Project Structure & POM.xml for JPA CRUD

Java Spring Boot CRUD API Guide with MySQL, Swagger, and Security

Module 2: CRUD with Spring Data JPA + MySQL

Standard Spring Boot Folder Structure

When you create a Spring Boot project using Spring Initializr, it generates a standard Maven-based project layout:

src/
 └── main/
     ├── java/
     │   └── com/
     │       └── example/
     │           └── demo/
     │               ├── controller/
     │               ├── service/
     │               ├── repository/
     │               ├── model/
     │               └── DemoApplication.java
     └── resources/
         ├── application.properties
         ├── static/
         └── templates/

test/
 └── java/
     └── com.example.demo/
         └── DemoApplicationTests.java

Explanation:

  • controller/ – REST endpoints (e.g., GET, POST)
  • service/ – Business logic
  • repository/ – Interfaces for JPA (extends JpaRepository)
  • model/ – Entity classes (mapped to DB tables)
  • resources/ – Config files, static content, templates

Understanding POM.xml in Spring Boot

The pom.xml file is the heart of any Maven-based Spring Boot project. It defines:

  • Project name, version, and metadata
  • Spring Boot version & parent starter
  • Dependencies (JPA, Web, MySQL, etc.)
  • Build plugins (e.g., Spring Boot Maven Plugin)

Sample POM.xml Explained

<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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>Spring CRUD Demo</name>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.15</version>
    <relativePath/>
  </parent>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <!-- Web layer -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- JPA support -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- MySQL Connector -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>

    <!-- Testing support -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

Summary of Key POM Blocks

  • <parent>: Inherits Spring Boot configurations
  • <dependencies>: Includes core starters for web, JPA, and MySQL
  • <build>: Enables Maven plugin for packaging and running
  • <properties>: Java version and other global props

Configuring application.properties for MySQL

The application.properties file (located in src/main/resources/) acts as the central configuration point for your Spring Boot application. This file allows you to define the database URL, username, password, Hibernate dialect, JPA behavior, server port, and more.

Sample MySQL Configuration

# Database connection details
spring.datasource.url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=your_password

# JPA & Hibernate configuration
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

# Server configuration
server.port=8080

# Custom app variable
app.name=Spring CRUD Demo

Explanation of Properties

  • spring.datasource.url: The JDBC URL to connect to your MySQL database.
  • spring.datasource.username: Username for the DB connection.
  • spring.datasource.password: Password for the DB connection.
  • spring.jpa.show-sql: If true, all SQL queries will be printed in the console.
  • spring.jpa.hibernate.ddl-auto: Determines schema update strategy:
    • none: No changes
    • update: Updates the schema without destroying data (recommended for dev)
    • create: Drops and creates the schema each time
    • create-drop: Drops schema on exit
  • spring.jpa.properties.hibernate.dialect: Specifies the SQL dialect. Use MySQL8Dialect for modern MySQL versions.
  • server.port: The port on which the Spring Boot app runs (default is 8080).
  • app.name: Custom variable that can be injected using @Value.

Defining the Entity (Model) Class

The Entity class maps to a database table and represents the domain object you want to manage. Spring Boot with Spring Data JPA automatically handles table creation and CRUD operations.

Example: User Entity

package com.example.demo.model;

import javax.persistence.*;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(unique = true)
    private String email;

    public User() {}

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters and setters
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

Important JPA Annotations

  • @Entity: Marks the class as a JPA entity.
  • @Table(name = "..."): Specifies the DB table name.
  • @Id: Marks the primary key.
  • @GeneratedValue: Defines auto-generation strategy for the primary key (e.g., auto-increment).
  • @Column: Configures individual column properties like nullable and unique.
The table users will be automatically created in your configured MySQL database based on this entity.

Why Use Entities?

  • Entities are the foundation of any CRUD application.
  • They represent real-world objects in Java code.
  • With Spring Data JPA, you don’t need SQL to perform CRUD operations on these entities.
  • JPA handles all mapping and database synchronization.

Creating the Repository Layer

The Repository layer interfaces with the database using Spring Data JPA. You don’t need to write SQL queries — just extend JpaRepository or CrudRepository.

UserRepository.java

package com.example.demo.repository;

import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email); // custom query method
}

Explanation

  • JpaRepository<User, Long>: Tells Spring to manage User entities with Long as primary key type.
  • findByEmail(String email): Custom finder method – Spring builds the SQL automatically!

Creating the Service Layer

The Service layer contains the application’s business logic. It calls repository methods and processes data before returning to controllers.

UserService.java

package com.example.demo.service;

import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }

    public User createUser(User user) {
        return userRepository.save(user);
    }

    public User updateUser(Long id, User updatedUser) {
        return userRepository.findById(id).map(user -> {
            user.setName(updatedUser.getName());
            user.setEmail(updatedUser.getEmail());
            return userRepository.save(user);
        }).orElse(null);
    }

    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

Why Use a Service Layer?

  • Keeps your business logic separate from controllers
  • Makes testing easier (you can mock repositories)
  • Reduces duplicate logic across endpoints

Creating the Controller Layer

The Controller is the public API interface. It exposes endpoints that users or frontend clients interact with using HTTP methods like GET, POST, PUT, and DELETE.

UserController.java

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userService.getUserById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }

    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
        User updated = userService.updateUser(id, user);
        return updated != null ? ResponseEntity.ok(updated) : ResponseEntity.notFound().build();
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

Key REST Annotations

  • @RestController: Combines @Controller + @ResponseBody
  • @RequestMapping: Maps the root path for all endpoints
  • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping: Shortcut methods for handling HTTP verbs
  • @PathVariable: Binds URL path to method parameter
  • @RequestBody: Binds request JSON to Java object

API Testing Guide

Once your application is running, test it using tools like:

  • Postman – GUI client for REST APIs
  • curl – CLI tool for sending HTTP requests
  • Browser – You can test GET APIs directly

Example API Endpoints

  • GET /api/users – List all users
  • GET /api/users/1 – Get user by ID
  • POST /api/users – Create user (JSON body)
  • PUT /api/users/1 – Update user by ID
  • DELETE /api/users/1 – Delete user

Integrating Swagger UI for API Documentation

Swagger (now called OpenAPI) allows you to visually interact with your API and share live documentation with your team or clients.

Step 1: Add Swagger Dependency

<dependency>
  <groupId>org.springdoc</groupId>
  <artifactId>springdoc-openapi-ui</artifactId>
  <version>1.6.15</version>
</dependency>

Step 2: Start the App and Visit

  • Run your Spring Boot app.
  • Open http://localhost:8080/swagger-ui.html

You will see a UI with all your endpoints, models, and input types listed interactively.

Using DTOs (Data Transfer Objects)

DTOs are used to separate your data model from how it’s exposed externally. This helps protect internal structures and control what fields are shared.

Example: UserDTO.java

package com.example.demo.dto;

public class UserDTO {
    private String name;
    private String email;

    public UserDTO() {}

    public UserDTO(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters and setters
}

Modify Controller to Accept DTO

@PostMapping
public User createUser(@RequestBody UserDTO userDTO) {
    User user = new User();
    user.setName(userDTO.getName());
    user.setEmail(userDTO.getEmail());
    return userService.createUser(user);
}

This way, we isolate external request data from internal JPA models.

Adding Validation to DTO

Using JSR-303 annotations (e.g., @NotNull, @Email), we can validate incoming data before it reaches service or DB layers.

Updated UserDTO with Validation

import javax.validation.constraints.*;

public class UserDTO {

    @NotBlank(message = "Name is required")
    private String name;

    @Email(message = "Email should be valid")
    @NotBlank(message = "Email is required")
    private String email;

    // Constructors, Getters, Setters...
}

Updated Controller

@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserDTO userDTO) {
    User user = new User(userDTO.getName(), userDTO.getEmail());
    return ResponseEntity.ok(userService.createUser(user));
}

Also ensure your controller class includes:

import javax.validation.Valid;

Result:

If invalid data is submitted, a structured error response will be returned automatically by Spring Boot.

Securing APIs with Spring Security

For production-grade apps, it’s critical to secure your API endpoints. You can enable basic authentication using Spring Security.

Step 1: Add Spring Security

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Step 2: Default Credentials

Spring Boot generates a default password on app start. The username is user. Check your console logs for the password.

Step 3: Customize with SecurityConfig.java

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .csrf().disable()
          .authorizeRequests()
          .antMatchers("/api/**").authenticated()
          .and()
          .httpBasic();
    }
}

This will secure all endpoints under /api/** using HTTP Basic Authentication.

Optional: Creating Custom User Auth with Roles

You can create a UserDetailsService and store users/roles in the database for advanced use cases. This is beyond the scope of this CRUD example but recommended for production.

Final Spring Boot Project Structure

After completing the CRUD application using Spring Boot, Spring Data JPA, Swagger, DTOs, and Security, your folder structure should look like this:

src/
├── main/
│   ├── java/
│   │   └── com/example/demo/
│   │       ├── controller/
│   │       │   └── UserController.java
│   │       ├── dto/
│   │       │   └── UserDTO.java
│   │       ├── model/
│   │       │   └── User.java
│   │       ├── repository/
│   │       │   └── UserRepository.java
│   │       ├── service/
│   │       │   └── UserService.java
│   │       └── DemoApplication.java
│   └── resources/
│       ├── application.properties
│       └── static/, templates/ (if needed)
└── test/
    └── java/...

This modular structure helps you separate concerns and scale your codebase.

Manual Testing Plan (Functional)

  • GET /api/users – Return all users
  • GET /api/users/{id} – Return user if exists, else 404
  • POST /api/users – Create new user (test valid and invalid input)
  • PUT /api/users/{id} – Update existing user
  • DELETE /api/users/{id} – Delete user

Test each endpoint using Postman, Swagger UI, or curl commands.

Running the Application Locally

After completing your code and verifying the configuration, run the app using:

Option 1: Maven Command

mvn spring-boot:run

Option 2: Package and Run

mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar

Ensure MySQL is running and the schema demo_db exists (or auto-created).

Deployment Tips (Spring Boot JAR)

  • Deploy your JAR to a cloud server (e.g., DigitalOcean, AWS EC2, GCP Compute)
  • Use screen or nohup to run in background
  • Use systemd service for automatic startup on reboot
  • Secure port access using firewall or nginx reverse proxy

Production Checklist

  • Use application-prod.properties for production config
  • Enable HTTPS with SSL via nginx or Spring Security
  • Disable ddl-auto=update in production
  • Implement detailed logging and error alerts

Bonus: Spring Profiles for Dev, Test, Prod

Organize environments using separate config files:

  • application-dev.properties
  • application-test.properties
  • application-prod.properties

Activate a profile by passing a flag:

java -jar demo.jar --spring.profiles.active=prod

Final Thoughts

Congratulations! You now have a fully functional CRUD application built with Java, Spring Boot, Spring Data JPA, MySQL, and Swagger. You’ve learned how to organize your project, handle entities and DTOs, validate inputs, secure endpoints, and prepare for production.

This foundation prepares you for enterprise Java development. You can now expand this project to include pagination, file uploads, REST-to-GraphQL support, or integration with AI APIs.

Explore more topics like:

  • Unit testing with JUnit and Mockito
  • Deploying with Docker and Kubernetes
  • Integrating with external APIs or microservices

Thank you for reading! Share this series or subscribe for updates on real-world Java and AI integrations.

Post a Comment

0 Comments