본문 바로가기
Java/Spring Boot

Getter/Setter 사용 기준

by curious week 2025. 5. 31.

 

1. Getter 허용 기준 (상대적으로 자유로움)

● 허용: 조회 목적의 필드

  • 조회용으로 필드 값을 외부에 제공할 필요가 있을 때 허용됩니다.
public String getEmail() {
    return email;
}

● 제한 또는 금지:

  • 민감 정보 (password, token, salt, secretKey, 등)
  • 내부적으로만 사용하는 정보 (internalFlag, deleted, 등)
// ❌ 이렇게 민감한 값은 외부 노출 X
public String getPassword() {
    return password;
}

권장:

  • 필요한 필드만 Getter 생성 (@Getter 대신 개별 작성)
  • Lombok @Getter는 DTO나 읽기 전용 뷰 모델에만 사용

2. Setter 허용 기준 (매우 신중)

● 기본 원칙: 엔터티에 Setter를 직접 노출하지 않는다

  • 데이터 변경은 Setter가 아닌 비즈니스 메서드를 통해 이루어져야 합니다.
// ❌ setter 공개 X
public void setRole(String role) {
    this.role = role;
}

// ✅ 의미 있는 이름으로 비즈니스 행위로 감쌈
public void changeRole(UserRole newRole) {
    this.role = newRole;
}

● 제한적으로 허용하는 경우:

  • ID, 생성일 같은 변경 불가능한 필드는 setter 자체를 만들지 않음
  • JPA가 내부적으로 사용하는 용도의 필드 (예: 양방향 관계 매핑 시 setParent() 등)는 protected로 제한

3. 실무에서의 Setter 관리 전략

조회만 필요한 필드 public getter 허용
상태 변경 (예: 이름 변경, 탈퇴 등) changeName(), deactivate() 처럼 명시적 비즈니스 메서드 사용
JPA 내부 용도 (양방향 관계 연결) protected setter, 혹은 패키지 접근 수준 제한
Lombok 사용 @Getter만 선택적 사용. @Setter는 지양 또는 DTO 전용 클래스에서만 사용

4. 실전 예시 (추천 구조)

@Entity
public class User {

    @Id @GeneratedValue
    private Long id;

    private String email;

    private String password;

    private String role;

    protected User() {} // JPA 기본 생성자

    // 생성자 or 팩토리 메서드
    private User(String email, String password) {
        this.email = email;
        this.password = encrypt(password);
        this.role = "USER";
    }

    public static User create(String email, String password) {
        return new User(email, password);
    }

    // getter는 필요한 필드만 공개
    public String getEmail() {
        return email;
    }

    public String getRole() {
        return role;
    }

    // setter 대신 비즈니스 메서드 사용
    public void changePassword(String rawPassword) {
        this.password = encrypt(rawPassword);
    }

    public void changeRole(String newRole) {
        if (!List.of("USER", "ADMIN").contains(newRole)) {
            throw new IllegalArgumentException("Invalid role");
        }
        this.role = newRole;
    }

    private String encrypt(String raw) {
        // 실제 암호화 로직
        return raw + "_encrypted";
    }
}

1. ⚠ Getter와 Setter의 문제점

1.1 Setter의 위험성

@Entity
public class User {
    @Id
    private Long id;
    private String role;

    public void setRole(String role) {
        this.role = role;
    }
}

위처럼 setter를 공개하면:

  • 누구나 객체의 상태를 마음대로 변경할 수 있어요.
    • 예: user.setRole("ADMIN") → 관리자 권한 탈취 가능성
  • 객체 내부의 비즈니스 규칙을 우회해서 값을 바꿀 수 있어요.
    • 예: 비밀번호 암호화 없이 바로 바꾸거나, 유효하지 않은 이메일을 그대로 넣는 경우
  • 변경 추적이 어려워짐 → 추후 디버깅, 감사 로직에도 치명적

문제 요약: 객체의 캡슐화(encapsulation) 원칙을 깨뜨립니다.


1.2 Getter의 위험성

Getter는 Setter보다는 덜 위험하지만, 다음과 같은 문제가 있어요:

  • 민감한 데이터(예: password, security token 등)가 노출될 수 있음
  • 불필요하게 내부 구현이 외부에 공개됨 → 객체의 내부 정보 유출
  • 클라이언트 코드가 getter에 의존해 객체의 상태를 바꾸려는 유혹을 가짐

2. 안전한 대안들

2.1 생성자 + 정적 팩토리 메서드 사용

@Entity
public class User {
    @Id
    private Long id;
    private String email;
    private String password;

    protected User() {} // JPA용 기본 생성자 (protected)

    private User(String email, String password) {
        this.email = email;
        this.password = password;
    }

    public static User create(String email, String password) {
        // 검증, 암호화 등 비즈니스 로직 추가
        return new User(email, encrypt(password));
    }

    private static String encrypt(String raw) {
        return "..."; // 암호화 로직
    }
}

장점: 객체 생성을 제어하고, 생성 시 검증, 로직 삽입 가능


2.2 불변(immutable) 엔터티는 어렵지만, Setter를 제한적으로만 허용

JPA는 리플렉션을 통해 필드를 조작하기 때문에 완전 불변 객체는 어려워요. 따라서 다음 방식으로 제한적으로 Setter를 사용합니다:

public void changePassword(String rawPassword) {
    this.password = encrypt(rawPassword);
}

비즈니스 메서드 형태로 Setter를 감싸기
➡ 필드 이름 그대로 노출하지 않고, 의미 있는 동작으로 감쌈


2.3 Builder 패턴 사용 시 주의점

Builder는 가독성과 객체 생성 유연성에 좋지만, 불완전한 객체 생성 가능성이 있어요.

User.builder()
    .email("admin@example.com")
    .build(); // password 빠짐 (위험)

➡ 해결책:

  • 필수값을 Builder 생성자에 넣거나
  • 생성 이후 검증 로직 추가

2.4 Query Projection DTO를 따로 만들기

엔터티에는 비즈니스 로직만 넣고, 조회 용도는 따로 DTO를 사용:

public record UserDto(String email, String role) {}

3. 정리 요약

Setter 무분별한 변경 가능성 비즈니스 메서드 (changePassword())
Getter 민감 데이터 노출 필요한 필드만 제한 공개
Builder 불완전 객체 생성 생성자 강제 / 검증 메서드
생성자 외부에서 직접 생성 protected + static factory method
데이터 조회 엔터티 노출 위험 DTO 분리

4. 실무에서 추천하는 구조

  • 엔터티는 최대한 캡슐화
    • 필드 private, Setter 지양
    • 생성자 + 팩토리 메서드로 생성
    • 변경은 메서드를 통해
  • DTO는 별도로 분리
    • 조회용, 전송용, 수정용 DTO 각각 사용
  • Builder는 DTO에만 쓰는 것이 더 안전

 

1. @Access(AccessType.FIELD) – JPA의 접근 전략 지정

● 기본 동작:

JPA는 기본적으로 필드 접근 또는 프로퍼티 접근 중 하나를 선택해서 매핑합니다:

  • 프로퍼티 접근: getter/setter를 통해 접근 (@Id가 메서드 위에 있으면)
  • 필드 접근: private 필드에 직접 접근 (@Id가 필드 위에 있으면)
@Access(AccessType.FIELD)
@Entity
public class User {
    @Id
    private Long id; // 필드 기준으로 접근
}

● 왜 필요한가?

  1. Setter 없이도 JPA가 값을 주입할 수 있게 함
    → @Access(AccessType.FIELD)를 설정하면 JPA가 필드에 직접 접근하므로 setter 없이도 동작
  2. 비즈니스 메서드 외에는 상태 변경을 막을 수 있음
    → 외부에서 getter/setter로 상태를 바꾸지 못하게 제한 가능
  3. 명확하게 전략을 고정하여 실수 방지
    → 필드와 메서드가 혼합된 경우, 예상과 다른 동작 방지

2. @NoArgsConstructor(access = AccessLevel.PROTECTED) – JPA 기본 생성자 보호

JPA는 내부적으로 프록시를 생성하거나 리플렉션으로 객체를 생성해야 하므로, 반드시 매개변수가 없는 생성자가 필요합니다. 하지만 이 생성자를 public으로 열어두는 건 위험할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {
    // 기본 생성자를 외부에서 호출 못하게 보호
}

● 왜 필요한가?

  • public 생성자를 두면 외부에서 무분별하게 객체를 생성할 수 있음
  • protected로 제한하면 JPA는 생성 가능하고, 개발자는 외부에서 생성 불가

➡ 도메인 객체는 정적 팩토리 메서드로만 생성하게 유도하는 구조로 만들 수 있습니다.


실무 추천 설정 (정리)

import jakarta.persistence.*;
import lombok.*;

@Access(AccessType.FIELD)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class User {

    @Id @GeneratedValue
    private Long id;

    private String email;

    private String password;

    private String role;

    // 정적 팩토리 메서드
    public static User create(String email, String password) {
        User user = new User();
        user.email = email;
        user.password = encrypt(password);
        user.role = "USER";
        return user;
    }

    public void changePassword(String rawPassword) {
        this.password = encrypt(rawPassword);
    }

    private static String encrypt(String raw) {
        return "..."; // 비밀번호 암호화
    }
}

  • @Builder를 함께 사용하면 생성자 제한이 무력화될 수 있음 → DTO나 응답 객체에만 사용하세요.
  • 도메인 로직이 많은 경우, 엔티티는 가능한 한 읽기 전용(immutable에 가깝게) 설계하세요.
  • Spring Data JPA 사용 시에도 위 설정은 매우 권장됩니다.

@Access(AccessType.FIELD) JPA가 필드에 직접 접근하도록 설정 엔터티 클래스 상단
@NoArgsConstructor(access = AccessLevel.PROTECTED) 기본 생성자 생성 + 외부 생성 제한 엔터티 클래스 상단

'Java > Spring Boot' 카테고리의 다른 글

ResponseEntity  (0) 2025.05.15
Swagger와 API 명세  (1) 2025.03.31
Spring Boot Test 관련 (JUnit, Mockito, AssertJ,Spring Boot Test, Testcontainers )  (0) 2025.03.31
Optional<T>  (0) 2025.03.31
JPQL과 QueryDSL  (0) 2025.03.31