Entity에서 @Setter 사용을 지양하자

박영준·2023년 6월 28일
0

Spring

목록 보기
28/58

1. @Setter 사용 예시

아래는 내가 프로젝트를 하면서 작성했던 Entity 클래스다.
JPA 를 사용할 때, Entity 에서 @Getter 와 함께 @Setter 를 사용했다.

@Entity
@Getter
@Setter
@Table(name = "board")
@NoArgsConstructor
public class Board extends Timestamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "username", nullable = false)
    private String username;

    @Column(name = "password", nullable = false)
    private String password;

    @Column(name = "contents", nullable = false, length = 500)
    private String contents;

    // 게시글 작성
    public Board(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.password = requestDto.getPassword();
        this.contents = requestDto.getContents();
    }

    // 게시글 수정
    public void update(BoardRequestDto requestDto) {
        this.title = requestDto.getTitle();
        this.username = requestDto.getUsername();
        this.password = requestDto.getPassword();
        this.contents = requestDto.getContents();
    }
}

2. @Setter 의 문제점

금지까지는 아니지만, 지양하는 것이 좋다고 한다.
(필요할 경우 사용해도 된다.)

1) 사용 의도를 파악하기가 어렵다

Post post = new Post();
post.setId(1L);
post.setUserId("member1");
post.setTitle("제목입니다.");
post.setCont("내용입니다.");

post 엔티티 클래스를 set메서드를 통해 변화를 주고 있다.

그러나 set 메서드가 게시글의 값을 생성하는 것인지? 기존 값을 수정/변경하는 것인지?
그 의도 파악이 어렵다.

2) 일관성 유지가 어렵다

// 게시글 수정
public Post updatePost(Long id) {
    Post post = findById(id);
    post.setTitle("제목을 수정합니다.");
    post.setCont("내용을 수정합니다,");
    
    return post;
}

게시글 수정 메서드의 접근 제한자는 'public'이다.
따라서, 어디든(모든 메서드가) 접근이 가능하다.
(참고: 캡슐화, 접근 제한자, Getter/Setter, 문제점 및 대안)

그러나
본래 의도와는 달리, post 값을 변경해버리는 경우가 생길 수도 있다.
즉, 일관성 유지를 할 수 없게 돼버린다.

3. @Setter 의 대안

1) 사용 의도가 명확한 메서드를 사용하자

Entity

@Getter
@Entity
public class Post {
	
    private Long id;
    private String userId;
    private String title;
    private String cont;
    
    // 게시글 수정
    public void updatePost(Long id, String title, String cont) {
        this.id = id;
        this.title = title;
        this.cont = cont;
    }
}

service

// 게시글 수정
public void ... (...) {
	...
	post.updatePost(1L, "수정할 제목입니다.", "수정할 내용입니다.");
}
  • 메서드만 봐도 update 하겠다는 의도를 알 수 있다.

2) 생성 시점에 값을 넣자

@Builder 를 사용한다.

  • 생성 시점에 값을 넣음으로써, 일관성을 유지할 수 있다.

  • 요구사항에 맞게 필요한 데이터만 사용해서, 유연한 클래스 생성이 가능하다.

  • 전체에서 생성자 하나만을 가지게 되므로, 유지보수↑ 가독성↑

  • 객체를 생성 시, 인자 값의 순서가 상관X

Entity

@Getter
@Builder		// builder 설정
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String image;
    private String title;
    private String date;
    private String content;
    private String tag;
}       
  • 엔티티에서 @Build 를 설정해줘야, service 에서 사용할 수 있다.

service

private List<ArticleListResponseDto> processUrl(String url) throws IOException {
    Document document = Jsoup.connect(url).get();
    Elements contentList = document.select("div div.section-list-area div.list");

    List<ArticleListResponseDto> articleResponseDtoList = new ArrayList<>();

    for (Element contents : contentList) {
        String image = contents.select("a img").attr("abs:src");
        String title = contents.select("h4 a").text();
        String date = contents.select("p span").text().substring(0, 10).replaceAll("-", "/");
        String tag = contents.select("strong a").text();
            
        String articleLink = contents.select("h4 a").attr("abs:href");
        Document articleDocument = Jsoup.connect(articleLink).get();
        String content = articleDocument.select("div div.text").text();

            Article article = Article.builder()		// builder 사용
                	.image(image)
                	.title(title)
                	.tag(tag)
                	.date(date)
                	.content(content)
                	.build();

        articleRepository.save(article);
        articleResponseDtoList.add(new ArticleListResponseDto(article));
    }

    return articleResponseDtoList;
}
  • builder 를 통해, 값의 세팅도 가능하다.

  • 만약, setter 를 그대로 사용했다면?

    Article article = new Article();
     article.setId(currentId);
     article.setImage(image);
     article.setTitle(title);
     article.setDate(date);
     article.setTagName(tagName);
     article.setContent(content);

3) 생성자를 오버로딩

public class Member {
    private String name;
    private String age;
    
    public Member(String name){
    	this.name = name;
    }
    public Member(String name,int age){
    	this.name = name;
    	this.age = age;
    }

문제점
멤버 변수多 + 생성자多 경우, 코드多 가독성↓

이를 해결하기 위해 Builder 패턴을 사용한다.

4) 정적 팩토리 메소드

public class Member {
    private String name;
    private String age;
    
    public static void createMember(String name, int age){
    	this.name = name;
        this.age = age;
    }
    
    public static void createMemberName(String name){
    	this.name = name;
    }

이름(createMember, createMemberName ...)을 가질 수 있으므로, 반환될 데이터를 추측할 수 있다.

참고: 정적 팩토리 메서드

4. 사용 가능한 상황

핵심은 setter 의 외부 노출을 줄이는 것이기 때문에
하나의 필드만 변경할 경우 등... 일부 상황에서는 Setter 를 사용해도 큰 문제가 없다.


참고: 왜 Entity에 setter를 사용하지 말아야 할까?

profile
개발자로 거듭나기!

0개의 댓글