[Java] Java spring에서 안전한 객체 복사와 불변 설계 패턴

2025. 10. 17. 18:40·Java

자바의 정석에서 생성자를 이용한 인스턴스 복사에 관해 공부를 하다

굳이 생성자를 이용해서 인스턴스 복사를 하는 이유가 뭐지에 대해서 생각해보게 됐다.

그러던 중 spring에서 생성자를 이용해서 인스턴스를 복사하고, 불변 패턴을 설계하는 방법과 연관이 있다는 걸 알게되어 포스팅하게 됐다.

1. 생성자를 이용한 인스턴스 복사(Copy Constructor)

개념

  • 기존 객체를 기반으로 새로운 객체를 만들 때 사용
  • 원본과 독립적인 객체를 생성 가능 → 깊은 복사(Deep Copy) 가능
  • 얕은 복사(Shallow Copy)와 깊은 복사 비교가 핵심

예제 코드

class Address {
    private String city;
    public Address(String city) { this.city = city; }
    public Address(Address other) { this.city = other.city; }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    @Override
    public String toString() { return city; }
}

class User {
    private String name;
    private Address address;

    public User(String name, Address address) { this.name = name; this.address = address; }

    public static User shallowCopy(User other) { return new User(other.name, other.address); }
    public static User deepCopy(User other) { return new User(other.name, new Address(other.address)); }

    public Address getAddress() { return address; }
    @Override
    public String toString() { return name + " (" + address + ")"; }
}

public class CopyExample {
    public static void main(String[] args) {
        Address address = new Address("Seoul");
        User original = new User("Alice", address);

        User shallow = User.shallowCopy(original);
        User deep = User.deepCopy(original);

        original.getAddress().setCity("Busan");

        System.out.println("원본: " + original);
        System.out.println("얕은 복사: " + shallow);
        System.out.println("깊은 복사: " + deep);
    }
}

실행 결과

원본: Alice (Busan)
얕은 복사: Alice (Busan)
깊은 복사: Alice (Seoul)
  • 얕은 복사는 참조를 공유 → 원본 변경 시 복사본도 변경
  • 깊은 복사는 새로운 객체 생성 → 독립적

2. clone() vs 생성자 복사

  • clone(): Cloneable 구현 필요, 얕은 복사 기본, 깊은 복사 수동 필요
  • 복사 생성자: 명시적, 안전, 가독성 좋음
  • 실무에서는 생성자 기반 복사를 선호

예제

// clone() 기반 깊은 복사 vs 생성자 기반 복사
User deepClone = original.deepClone(); // clone() 사용
User constructorCopy = new User(original); // 생성자 사용

3. Builder + 복사 생성자 패턴 (불변 객체)

목적

  • 불변 객체(Immutable Object) 설계
  • 일부 속성만 변경해 새로운 객체 생성
  • 실무에서는 Lombok @Builder(toBuilder = true)와 함께 사용

예제 코드

public class User {
    private final String name;
    private final int age;
    private final Address address;

    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.address = builder.address;
    }

    public Builder toBuilder() { return new Builder(this); }

    public static class Builder {
        private String name;
        private int age;
        private Address address;

        public Builder() {}
        public Builder(User other) {
            this.name = other.name;
            this.age = other.age;
            this.address = new Address(other.address);
        }

        public Builder name(String name) { this.name = name; return this; }
        public Builder age(int age) { this.age = age; return this; }
        public Builder address(Address address) { this.address = address; return this; }
        public User build() { return new User(this); }
    }
}
  • 기존 객체 기반으로 일부만 수정 가능
User modified = original.toBuilder()
    .age(30)
    .build();

4. Lombok + JPA Entity 환경

Entity 설계

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(toBuilder = true)
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int age;
    private String city;

    public User updateAge(int newAge) { return this.toBuilder().age(newAge).build(); }
    public User updateCity(String newCity) { return this.toBuilder().city(newCity).build(); }

    public UserDto toDto() {
        return UserDto.builder()
            .id(id).name(name).age(age).city(city).build();
    }
}
  • setter 없이 불변 패턴 유지
  • toBuilder() + 수정 메서드로 안전하게 값 변경

Service + Controller 흐름

User user = userRepository.findById(id).orElseThrow();
User updatedUser = user.updateAge(30);
userRepository.save(updatedUser);
return updatedUser.toDto();

5. DTO 분리 이유

  • Entity = DB 중심
  • DTO = API/외부 요구사항 중심
  • 장점: 보안, 불변성, 계층 분리, 유연성
@Getter @Builder
public class UserRequestDto { private String name; private int age; private String city; }

@Getter @Builder
public class UserResponseDto { private Long id; private String name; private int age; private String city; }
  • Entity → DTO 변환 메서드 활용
public UserResponseDto toResponseDto() {
    return UserResponseDto.builder()
        .id(id).name(name).age(age).city(city).build();
}

6. 전체 실무 패턴 요약

  1. 불변 Entity 설계 (setter 없음, @Builder(toBuilder = true))
  2. 값 변경 시 → 기존 객체 기반 toBuilder() + 수정 메서드 → 새 객체 생성
  3. DB 반영 → save() 호출
  4. Controller/Service 통신 → Entity → DTO 변환
  5. DTO 사용 → API 보안, 계층 분리, 테스트 용이
Client → Controller → Service → Repository → DB
           ↑                    ↑
           DTO                Entity
           ←-------------------

 

'Java' 카테고리의 다른 글

[Java] this와 super 이해하기 – 멤버 변수와 지역 변수의 차이  (0) 2025.10.17
[Java] 상속의 정의  (0) 2025.10.17
[Java] this()(생성자에서의 this 호출), this(참조변수로 자기 자신을 가리키는 경우)  (0) 2025.10.17
[JAVA] 객체지향개념Ⅱ  (0) 2025.10.13
[Java] 문자열 처리 성능비교 - String vs StringBuilder vs StringBuffer  (0) 2025.10.11
'Java' 카테고리의 다른 글
  • [Java] this와 super 이해하기 – 멤버 변수와 지역 변수의 차이
  • [Java] 상속의 정의
  • [Java] this()(생성자에서의 this 호출), this(참조변수로 자기 자신을 가리키는 경우)
  • [JAVA] 객체지향개념Ⅱ
싹난 감자🥔🌱
싹난 감자🥔🌱
개발 블로그
  • 싹난 감자🥔🌱
    감자에 싹이나서 잎이나서
    싹난 감자🥔🌱
  • 전체
    오늘
    어제
    • 분류 전체보기 (43)
      • Spring (3)
      • Java (17)
      • LLM (1)
      • DevOps (4)
      • Algorithm (14)
        • 백준 (4)
        • 프로그래머스 (0)
        • 코드업 (10)
      • Computer Science (3)
        • Operating System (2)
        • Computer Architecture (1)
      • Trouble Shooting 🚀 (0)
      • 회고 & 성장기록 (1)
      • 설계 📐 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    구간합
    코딩테스트
    2진수
    자바Scanner비교
    비트
    docker
    알고리즘입력
    배포
    자바BufferedReader
    BufferedReader
    자바 입력
    Scanner
    자바성능
    Java
    2차원 배열
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
싹난 감자🥔🌱
[Java] Java spring에서 안전한 객체 복사와 불변 설계 패턴
상단으로

티스토리툴바