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) 를 쉽게 적용할 수 있도록 도와주는 보안 프레임워크
▶️로그인, 회원가입, 권한 관리, 보안 정책 적용 등을 직접 구현하려면 시간이 많이 걸리고 복잡
▶️스프링에서 안전하고 쉽게 사용하게 해줌! 이외에도,,,
- 보안 취약점 자동 방어
- 비밀번호 암호화 지원
- 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 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 |