250226 TIL

2025. 2. 26. 21:11·TIL

double > Double 차이점

둘 다 실수 값을 저장하지만,

double ▶️ 기본형 (primitive type)

▫️기본 값 : 0.0

▫️Stack 메모리에 저장

Double▶️ 참조형 (Wrapper class) 주소를 가지고 있는 객체임

▫️ 기본 값 : null (객체이므로 Null 값 할당 가능)

▫️ Double 클래스의 메서드 사용 가능 (parseDouble, toString 등)

▫️ Heap 메모리에 저장 (객체이므로)

// 기본형 double
double a = 10.5;

// 객체형 Double
Double b = 20.5;  // 자동 박싱 (Auto-boxing)
double c = b;     // 자동 언박싱 (Auto-unboxing)

System.out.println(b.toString()); // 객체이므로 메서드 사용 가능

 

📌 언제 사용 해야 할까 

double: 성능이 중요하고 객체 기능이 필요 없는 경우 (예: 수학 연산)

Double: null 값을 허용해야 하거나, 객체 메서드를 사용해야 할 경우 (예: 컬렉션에 저장)


스프링(Spring) 필터

 

▫️ 클라이언트의 요청(Request)과 응답(Response) 사이에서 특정 로직을 수행할 수 있도록 도와주는 중간 역할

▫️ 필터(Filter)는 요청과 응답을 가로채는 역할

▫️ 보안, CORS, 로깅 등 다양한 용도로 사용

▫️ 필터의 실행 순서를 지정할 수 있음 

▫️ 서블릿(Servlet) 기반: javax.servlet.Filter 인터페이스를 구현하여 사용

필터(Filter) 구현 방법

Filter 인터페이스 구현

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 필터 초기화 로직 (필요하면 사용)
        System.out.println("MyFilter 초기화");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        System.out.println("요청이 필터를 통과 중...");
        
        // 다음 필터 또는 컨트롤러로 요청을 전달
        chain.doFilter(request, response);
        
        System.out.println("응답이 필터를 통과 중...");
    }

    @Override
    public void destroy() {
        // 필터 종료 시 실행할 로직 (필요하면 사용)
        System.out.println("MyFilter 종료");
    }
}

 

javax.servlet.Filter 인터페이스를 구현

init(),destroy는 필수가 아니다

필터 등록

@Component를 사용한 자동 등록

import org.springframework.stereotype.Component;
import javax.servlet.*;

@Component
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        System.out.println("요청 필터 실행!");
        chain.doFilter(request, response); // 다음 필터 or 컨트롤러로 요청 전달
    }
}

 

@Componet로 등록하면 스프링이 필터를 감지하고 등록 해줌

FilterRegistrationBean을 사용한 수동 등록

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>(new MyFilter());

        registrationBean.setOrder(1); // 필터 실행 순서 지정 (숫자가 낮을수록 먼저 실행)
        registrationBean.addUrlPatterns("/api/*"); // 특정 URL 패턴에만 적용

        return registrationBean;
    }
}

 

더 자세하게 규칙을 정하고 싶으면 FilterRegistrationBean을 사용 ▶️필터 실행 순서 지정, 특정 URL 패턴에만 적용 등등

필터 실행 순서

registrationBean1.setOrder(1); // 먼저 실행됨
registrationBean2.setOrder(2); // 나중에 실행됨

 

숫자가 작을 수록 우선 순위가 높다

요청: Filter1 → Filter2 → Controller
응답: Controller → Filter2 → Filter1

 

응답을 보낼 때는 반대로 실행


인증(Authentication)과 인가(Authorization)

인증(Authentication)

▫️ "너 누구임??"

▫️ 사용자의 신원 확인

▫️ 이 사람이 정말 본인이 맞는지 확인

 

📌예시

  • 로그인 화면에서 아이디와 비밀번호 입력
  • 지문 인식 또는 얼굴 인식
  • OTP(일회용 비밀번호) 사용
  • SNS 로그인(Google, Kakao, Facebook 등)

▫️ 사용자가 ID/PW 입력

➡️시스템이 DB에 저장된 정보와 비교

➡️맞으면 "인증 성공", 틀리면 "인증 실패"

인가(Authorization)

▫️ "너 이거 할 수 있니?"

▫️ 사용자가 특정 작업을 수행할 권한이 있는지 확인하는 과정

 

📌예시

  • 관리자만 회원 삭제 가능
  • VIP 사용자만 특별 할인 적용
  • 일반 사용자는 관리자 페이지 접근 불가
  • 내 게시글만 수정/삭제 가능

▫️ 사용자가 "게시글 삭제" 요청

➡️시스템이 사용자의 권한(Role)을 확인

  • 일반 사용자 → ❌ 삭제 불가
  • 관리자 → ✅ 삭제 가능

먼저 인증을 해서 사용자의 신원을 확인하고

그 다음 요청이 들어왔을 때 인가를 해서 권한이 있는지 확인


스프링 시큐리티(Spring Security)

▫️ 인증(Authentication)과 인가(Authorization) 를 쉽게 적용할 수 있도록 도와주는 보안 프레임워크

▶️로그인, 회원가입, 권한 관리, 보안 정책 적용 등을 직접 구현하려면 시간이 많이 걸리고 복잡

▶️스프링에서 안전하고 쉽게 사용하게 해줌! 이외에도,,,

  1. 보안 취약점 자동 방어
  2. 비밀번호 암호화 지원
  3. OAuth2 로그인 (구글, 카카오, 네이버) 지원

등등 기능이 있다


DTO(Data Transfer Object)

▫️ 스프링에서 DTO(Data Transfer Object)와 엔터티(Entity)를 분리해서 사용

▫️ DTO는 데이터를 클라이언트(프론트엔드)와 주고받을 때 사용하는 객체.

  • Entity를 그대로 사용하지 않고 DTO를 통해 필요한 데이터만 전달.
  • API 응답/요청 시 필요한 필드만 담아 보낼 수 있음.
import lombok.Getter;

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

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

 

✔ 엔터티와 달리 DTO에는 비밀번호(password)가 없음 → 보안 강화

✔ API 응답 시 불필요한 데이터(예: role, password)를 포함하지 않음 → 성능 최적화

 

📌왜 그렇게 사용하나?

1. 보안 강화 (Security)

엔터티에는 비밀번호 같은 민감한 정보가 포함될 수 있음

DTO를 사용하면 민감한 데이터를 감출 수 있다.

 

❌ Entity를 직접 반환하면 보안 위험

@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    return userRepository.findById(id).orElseThrow();
}

 

⭕ DTO 사용

@GetMapping("/user/{id}")
public UserDTO getUser(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow();
    return new UserDTO(user.getName(), user.getEmail()); // 비밀번호 제외
}

2. 성능 최적화 (Performance)

엔터티는 불필요한 필드까지 포함될 수 있어 성능 저하 가능

 

❌ 엔터티를 직접 API 응답으로 보내기

@GetMapping("/users")
public List<User> getUsers() {
    return userRepository.findAll(); // 모든 필드 포함됨
}

 

⭕ DTO 사용

@GetMapping("/users")
public List<UserDTO> getUsers() {
    return userRepository.findAll().stream()
            .map(user -> new UserDTO(user.getName(), user.getEmail()))
            .collect(Collectors.toList());
}

3. 유지보수성 향상 (Maintainability)

엔터티는 데이터베이스 구조와 강하게 결합됨 → 변경이 어려움

DTO를 사용하면 엔터티 변경 없이 API 응답 구조를 자유롭게 변경 가능

 

❌ Entity를 그대로 사용하면 변경이 어려움

만약 User 엔터티에서 name 필드를 fullName으로 변경하면?

@Entity
public class User {
    private String fullName; // 기존 name 필드 변경
}

 

기존 API (/users)가 깨질 수 있음!

API 응답을 사용하는 프론트엔드와 백엔드 코드도 모두 수정해야 함

 

⭕ DTO 사용

public class UserDTO {
    private String name;

    public UserDTO(User user) {
        this.name = user.getFullName(); // 엔터티 변경에도 API는 그대로 유지
    }
}

 

DTO를 사용하면 엔터티 변경이 API에 직접 영향을 주지 않음! 

4. 계층 분리 (Separation of Concerns)

엔터티는 "데이터베이스 저장"을 담당

DTO는 "데이터 전송"을 담당

이렇게 역할을 분리하면 코드가 더 명확하고 유지보수하기 쉬워짐

 

❌ DTO 없이 사용하면 계층이 섞임

public class UserService {
    public User getUser(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
}
  • 서비스 계층이 데이터베이스와 API 응답 역할을 동시에 처리
  • 변경 시 서비스 계층 코드가 복잡해짐

⭕ DTO 사용(계층 분리)

public class UserService {
    public UserDTO getUser(Long id) {
        User user = userRepository.findById(id).orElseThrow();
        return new UserDTO(user.getName(), user.getEmail());
    }
}

 

엔터티는 데이터 저장만 담당하고, DTO는 데이터 전송을 담당

서비스 클래스 (UserService.java)

import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Transactional
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder; // 비밀번호 암호화

    // 🏗️ 사용자 등록 (DTO → 엔터티 변환)
    public UserDTO createUser(UserDTO userDTO, String password) {
        User user = User.builder()
                .name(userDTO.getName())
                .email(userDTO.getEmail())
                .password(passwordEncoder.encode(password)) // 비밀번호 암호화
                .role("USER") // 기본 역할
                .build();

        User savedUser = userRepository.save(user);
        return UserDTO.fromEntity(savedUser);
    }

    // 🔍 사용자 조회 (엔터티 → DTO 변환)
    public UserDTO getUser(Long id) {
        User user = userRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));
        return UserDTO.fromEntity(user);
    }

    // 🔍 모든 사용자 조회 (List<User> → List<UserDTO>)
    public List<UserDTO> getAllUsers() {
        return userRepository.findAll().stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
    }
}

 

하지만! DTO를 꼭 쓰라는 것은 아님 설계자 마음이다!


빌더 패턴(Builder Pattern)

객체를 단계별로 생성할 수 있도록 도와주는 디자인 패턴

복잡한 객체를 생성할 때 가독성을 높이고, 코드의 유연성을 증가

 

📌왜 필요?

1. 생성자의 매개변수가 너무 많을 때

public class User {
    private String name;
    private int age;
    private String email;
    private String phone;
    private String address;

    public User(String name, int age, String email, String phone, String address) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.phone = phone;
        this.address = address;
    }
}

User user = new User("Alice", 25, "alice@example.com", "010-1234-5678", "Seoul");

 

매개변수가 많아지면 순서가 헷갈린다.

2. 필요 없는 값도 설정해야 할 때

public class User {
    private String name;
    private int age;
    private String email;
    private String phone;
    private String address;

    // 생성자 오버로딩 (필드 개수에 따라 다르게 제공)
    public User(String name, int age) {
        this(name, age, null, null, null);
    }

    public User(String name, int age, String email) {
        this(name, age, email, null, null);
    }

    public User(String name, int age, String email, String phone) {
        this(name, age, email, phone, null);
    }

    public User(String name, int age, String email, String phone, String address) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.phone = phone;
        this.address = address;
    }
}

 

생성자 오버로딩을 하면 코드가 길어지고 유지보수가 어려움

새로운 필드가 추가되면 모든 생성자를 수정해야 함

📌 빌더 패턴 적용

public class User {
    private String name;
    private int age;
    private String email;
    private String phone;
    private String address;

    // 🏗️ 1. private 생성자: 객체 직접 생성 방지
    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    // 🔨 2. 정적 Builder 클래스
    public static class Builder {
        private String name;
        private int age;
        private String email;
        private String phone;
        private String address;

        public Builder(String name, int age) { // 필수 값 설정
            this.name = name;
            this.age = age;
        }

        public Builder email(String email) { // 선택적 값 설정
            this.email = email;
            return this;
        }

        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        public User build() { // 최종 객체 생성
            return new User(this);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        // 빌더 패턴을 사용한 객체 생성
        User user1 = new User.Builder("Alice", 25)
                .email("alice@example.com")
                .phone("010-1234-5678")
                .address("Seoul")
                .build();

        User user2 = new User.Builder("Bob", 30)
                .phone("010-5678-1234") // 이메일 없이 설정 가능
                .build();

        System.out.println(user1);
        System.out.println(user2);
    }
}

Lombok을 활용한 빌더 패턴

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {
    private String name;
    @Builder.Default
    private int age=25; //기본값 설정 가능
    private String email;
    private String phone;
    private String address;
}

public class Main {
    public static void main(String[] args) {
        User user = User.builder()
                .name("Alice")
                .age(25)
                .email("alice@example.com")
                .phone("010-1234-5678")
                .build();

        System.out.println(user);
    }
}

jakarta.validation 라이브러리

데이터 입력이 올바른지 사전에 확인 가능

DTO(데이터 전송 객체)의 필드 값이 유효한지 자동으로 검증할 수 있다

 

📌 Gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-validation'
}

 

📌 제공 어노테이션

@NotNull 값이 null이면 안됨 @NotNull private String name;
@NotBlank 빈 값 또는 공백 문자열 불가 @NotBlank private String name;
@NotEmpty 빈 값("") 불가 (null 허용) @NotEmpty private List<String> items;
@Size(min, max) 문자열/컬렉션 크기 제한 @Size(min=2, max=10) private String username;
@Email 이메일 형식 검증 @Email private String email;
@Pattern 정규식을 이용한 검증 @Pattern(regexp="^[a-zA-Z]*$") private String username;
@Min / @Max 숫자의 최소/최대 값 지정 @Min(18) private int age;
@Past / @Future 과거/미래 날짜만 허용 @Past private LocalDate birthDate;

DTO 클래스 (UserDTO.java)

import jakarta.validation.constraints.*;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class UserDTO {

    @NotBlank(message = "이름은 필수 입력값입니다.") // 공백 불가
    private String name;

    @Email(message = "올바른 이메일 형식이어야 합니다.") // 이메일 형식 검증
    @NotBlank(message = "이메일은 필수 입력값입니다.")
    private String email;

    @Size(min = 8, message = "비밀번호는 최소 8자 이상이어야 합니다.") // 최소 길이 8자
    private String password;

    @Min(value = 18, message = "나이는 최소 18세 이상이어야 합니다.") // 최소 18세
    private int age;
}

컨트롤러 (UserController.java)

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

    private final UserService userService;

    // ✅ 사용자 등록 (자동 검증 적용)
    @PostMapping
    public ResponseEntity<String> createUser(@RequestBody @Valid UserDTO userDTO) {
        userService.createUser(userDTO);
        return ResponseEntity.ok("사용자가 성공적으로 등록되었습니다.");
    }
}

 

유효성 검사 실패 시 자동으로 400 Bad Request 응답 반환!

글로벌 예외 처리 클래스 (GlobalExceptionHandler.java)

Spring Boot는 기본적으로 검증 실패 시 예외(MethodArgumentNotValidException)를 던짐

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();

        ex.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage()));

        return errors; // JSON 형식으로 에러 메시지 반환
    }
}

 

검증 오류 발생 시 JSON 형식으로 에러 메시지 반환 가능!

 

예제 요청 및 응답 예시

{
  "name": "이름은 필수 입력값입니다.",
  "email": "올바른 이메일 형식이어야 합니다.",
  "password": "비밀번호는 최소 8자 이상이어야 합니다."
}

'TIL' 카테고리의 다른 글

250228 TIL  (0) 2025.02.28
250227 TIL  (0) 2025.02.27
250225 TIL  (0) 2025.02.25
250224 TIL  (1) 2025.02.24
250221 TIL  (1) 2025.02.21
'TIL' 카테고리의 다른 글
  • 250228 TIL
  • 250227 TIL
  • 250225 TIL
  • 250224 TIL
Jiyuuuuun
Jiyuuuuun
  • Jiyuuuuun
    Hello, World!
    Jiyuuuuun
  • 전체
    오늘
    어제
    • 분류 전체보기 (112)
      • TIL (56)
      • CS (17)
        • Network (4)
        • Algorithm (10)
      • JAVA (5)
      • Project (10)
        • HakPle (3)
        • JUSEYO (4)
      • Spring (2)
      • C (3)
      • C++ (16)
      • Snags (2)
  • 블로그 메뉴

    • 홈
    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    JPA
    HTML
    springboot
    front-end
    JDBC
    java
    db
    CSS
    SQL
    react
    부트캠프
    node.js
    Kubernetes
    멋쟁이사자처럼
    back-end
    nginx
    hakple
    Docker
    juseyo
    javascript
    my_favorite_place
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Jiyuuuuun
250226 TIL
상단으로

티스토리툴바