Spring BootIntermediate

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 Optional return 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 @PreAuthorize annotations 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.

Ready to test yourself?

Sign in to take the quiz, track progress, and earn a certificate.

Sign in