Spring Boot Data & Security: JPA, DTOs & Auth Basics
Connect to databases with Spring Data JPA, shape APIs with DTOs and validation, secure endpoints with Spring Security, and handle errors cleanly with @ControllerAdvice.
4 sections · ~32 min · 5-question quiz (pass ≥ 70%)
1Spring Data JPA and Repositories
Spring Data JPA eliminates boilerplate DAO code. Define an interface extending JpaRepository and Spring generates the implementation at runtime:
@Entity
@Table(name = "employees")
public class Employee {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// getters/setters
}
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Optional<Employee> findByEmail(String email);
List<Employee> findByDepartment(String department);
}
Method names like findByEmail are parsed into queries automatically (query derivation). For complex queries, use @Query:
@Query("SELECT e FROM Employee e WHERE e.department = :dept AND e.active = true")
List<Employee> findActiveInDepartment(@Param("dept") String dept);
Rules of thumb:
- Keep entities anemic to the database — business rules belong in services.
- Use
Optionalreturn types for single-result lookups that may miss. - Tune fetch strategies (
@ManyToOne(fetch = LAZY)) to avoid N+1 query problems in production.
2DTOs, Validation, and Mapping
Never expose JPA entities directly over HTTP — lazy associations, circular references, and internal fields leak. Use DTOs (Data Transfer Objects):
public record EmployeeDto(Long id, String name, String email) {
public static EmployeeDto from(Employee e) {
return new EmployeeDto(e.getId(), e.getName(), e.getEmail());
}
}
public record CreateEmployeeRequest(
@NotBlank String name,
@Email String email
) {}
Enable validation with @Valid on controller parameters:
@PostMapping
public EmployeeDto create(@RequestBody @Valid CreateEmployeeRequest req) {
return service.create(req);
}
Add spring-boot-starter-validation for Bean Validation (@NotBlank, @Size, @Email). Invalid requests return 400 Bad Request with field-level errors when handled properly.
For mapping many fields, consider MapStruct — compile-time mappers beat hand-written boilerplate at scale.
3Spring Security Basics: Filters, Form Login, and JWT Overview
Spring Security wraps your app in a filter chain that runs before requests reach controllers. Every HTTP request passes through authentication and authorization checks.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // common for stateless APIs
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults()) // or .formLogin() for browser apps
.build();
}
}
Form login (session-based) suits traditional web apps: the user posts credentials, Spring creates a server-side session, and a session cookie identifies subsequent requests.
JWT (JSON Web Token) suits stateless APIs: after login, the server returns a signed token; clients send Authorization: Bearer <token> on every request. A OncePerRequestFilter validates the token and sets the security context.
Rules of thumb:
- Default deny — explicitly permit only public endpoints.
- Never store passwords in plain text; use
BCryptPasswordEncoder. - Keep security config in one place; scattered
@PreAuthorizeannotations are fine but the filter chain is the foundation.
4Global Exception Handling with @ControllerAdvice
Scattering try/catch in every controller creates inconsistent error responses. @ControllerAdvice centralizes exception-to-HTTP mapping:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EmployeeNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse notFound(EmployeeNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse validation(MethodArgumentNotValidException ex) {
Map<String, String> fields = ex.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
(a, b) -> a
));
return new ErrorResponse("VALIDATION_FAILED", "Invalid input", fields);
}
}
Pair with a consistent error response shape (code, message, optional details) so API clients can handle failures programmatically.
Log unexpected exceptions at ERROR with stack traces; return sanitized messages to clients — never leak SQL or internal paths.