SpringBoot and MySQL
Spring Boot has become the go‑to framework for building lightweight, production‑ready microservices in Java. When paired with a relational database such as MySQL, it offers a fast path to a fully functional, data‑driven application.
Below is a step‑by‑step guide that covers everything from setting up the development environment to deploying a Docker‑based container, all while keeping the code concise and idiomatic.
Before writing any code, make sure the following components are available on your machine:
- JDK 17+ (OpenJDK is recommended)
- Maven 3.8+ or Gradle 7+ (the examples here use Maven)
- MySQL 8.0+ – create a test database, e.g.
springboot_demo - An IDE such as IntelliJ IDEA or VS Code for quick project scaffolding
If you prefer containers, a single Docker command can spin up a MySQL instance:
docker run --name mysql-demo \
-e MYSQL_ROOT_PASSWORD=Root@123 \
-e MYSQL_DATABASE=springboot_demo \
-p 3306:3306 \
-d mysql:8.0
2. Scaffold a Spring Boot Project
Use Spring Initializr (https://start.spring.io) to generate a minimal project. Select the following starters:
- Spring Web
- Spring Data JPA
- MySQL Driver
- Spring Boot DevTools (optional, for hot reloading)
After downloading, unzip the archive and open it in your IDE. The pom.xml will contain the core dependencies:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3. Configure the Data Source
Add MySQL connection details to src/main/resources/application.yml (or application.properties):
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: Root@123
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update # use 'none' in production
show-sql: true
properties:
hibernate:
format_sql: true
ddl-auto options
| Value | Effect |
|---|---|
create |
Recreates tables on each startup, wiping data |
update |
Adjusts schema based on entity changes |
validate |
Checks schema matches entities |
none |
Disables automatic schema handling |
4. Define the Entity
Create a simple User entity to illustrate mapping, constraints, and Lombok usage:
package com.example.demo.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String username;
@Column(nullable = false)
private String password;
@Column(name = "email", unique = true, length = 100)
private String email;
@Column(name = "created_at", updatable = false)
private java.time.LocalDateTime createdAt = java.time.LocalDateTime.now();
}
The GenerationType.IDENTITY strategy lets MySQL handle auto‑incrementing primary keys, avoiding extra sequence tables.
5. Create the Repository
Spring Data JPA provides CRUD operations out of the box. Extend JpaRepository and add custom finder methods:
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
List<User> findByEmailContaining(String keyword);
}
6. Implement the Service Layer
The service encapsulates business logic, validation, and transaction boundaries:
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepo;
public UserService(UserRepository userRepo) {
this.userRepo = userRepo;
}
@Transactional
public User createUser(User user) {
if (userRepo.findByUsername(user.getUsername()).isPresent()) {
throw new IllegalArgumentException("Username already exists");
}
return userRepo.save(user);
}
public Optional<User> getUser(Long id) {
return userRepo.findById(id);
}
public List<User> listAll() {
return userRepo.findAll();
}
@Transactional
public void deleteUser(Long id) {
userRepo.deleteById(id);
}
}
7. Expose REST Endpoints
Map service methods to HTTP routes with @RestController:
package com.example.demo.controller;
import com.example.demo.entity.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;
}
@PostMapping
public ResponseEntity<User> create(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}
@GetMapping("/{id}")
public ResponseEntity<User> get(@PathVariable Long id) {
return userService.getUser(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping
public List<User> list() {
return userService.listAll();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}
Run the application and test the endpoints with Postman or curl:
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"pass123","email":"alice@example.com"}'
8. Common Issues & Performance Tuning
8.1 Connection Pool Settings
Spring Boot uses HikariCP by default. Adjust pool size for higher concurrency:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 300000
connection-timeout: 30000
8.2 Avoiding N+1 Queries
When loading related entities, use @EntityGraph or eager fetching sparingly:
@EntityGraph(attributePaths = {"roles", "profile"})
List<User> findAll();
8.3 Pagination & Sorting
Leverage Spring Data’s paging support:
Page<User> page = userRepository.findAll(
PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "createdAt"))
);
8.4 SQL Logging & Slow Query Detection
spring.jpa.show-sql=trueprints every executed statement.- Enable MySQL’s
slow_query_logto capture queries exceeding a threshold, aiding in pinpointing bottlenecks.
8.5 Transaction Management
By default, @Transactional rolls back on unchecked exceptions. To include checked exceptions:
@Transactional(rollbackFor = Exception.class)
public void riskyOperation() { … }
9. Build & Deploy
9.1 Maven Packaging
mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.datasource.password=NewPass
9.2 Docker Containerization
Create a Dockerfile:
FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Build and run:
docker build -t springboot-mysql-demo .
docker run -d -p 8080:8080 --name sb-demo springboot-mysql-demo
10. Takeaway
This walkthrough covered:
- Environment setup and project scaffolding
- Data source and JPA configuration
- Entity, repository, service, and controller layers
- Performance tuning for connection pools, N+1 avoidance, pagination, and transaction handling
- Packaging into a JAR and deploying via Docker
With these building blocks, you can rapidly prototype a Spring Boot application backed by MySQL, then iterate and optimize for production workloads. Happy coding!