개인취향 JPA 사용기 2편 — Entity with Getter,Setter and Test

안녕하세요? gemini 입니다.

지난 1편에서는 기본적인 Spring Boot + Gradle + JPA 설정을 해보고, 기본적인 Entity 를 생성해봤습니다.

오늘은 EntityGetter, Setter 그리고 Test 에 대한 제 개인적인 견해와 활용방법을 공유하려 합니다.

우선 1편에서 다룬 User Entity 를 보겠습니다.

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
        @Column(length = 20, nullable = false)
private String name;
        @Column(length = 20, nullable = false, unique = true)
private String phone;
        public Long getId() {
return id;
}
        public String getName() {
return name;
}
        public void setName(String name) {
this.name = name;
}
        public String getPhone() {
return phone;
}
        public void setPhone(String phone) {
this.phone = phone;
}
}

name, phone 필드는 Getter, Setter 를 모두 가지고 있습니다만, id 필드는 Getter 만 존재합니다.

Setter 를 만들지 않은 이유는 id 필드의 수동적 제어를 막기 위함입니다.
애초에 id 필드를 제어 불가로 만들어 혹시 모르는 실수를 막는 것입니다.

또한 기존 코드는 lombok 을 사용하지 않았습니다만.
제 취향대로 User Entity iombok 을 적용해보겠습니다.

@Getter
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
        @Column(length = 20, nullable = false)
private String name;
        @Column(length = 20, nullable = false, unique = true)
private String phone;
}

우선 저는 다른 기능을 사용하지 않고 @Getter Annotation 만 사용하였습니다,
또한 User Entity 에서 Setter 를 모두 제거하였습니다.

Setter 를 삭제한 이유는 이렇습니다. JPA 는 Transaction 안에서 Entity 의 변경사항을 감지히여 UPDATE SQL을 생성합니다.

SetterUpdate 기능을 수행하고 있습니다. 이런 상황에서 무분별하게 Setter 를 쓰게 된다면,
예측하지 못한 필드가 Update 가 될 때 Setter 를 일일이 체크해야 합니다.

그럼 Setter 없이 어떻게 데이터 수정을 할까요? 수정 된 User Entity 를 보겠습니다.

@Getter
@Entity
public class User {
@Column(length = 20, nullable = false)
private String name;
        @Column(length = 20, nullable = false, unique = true)
private String phone;
        public void updateInfo(String name, String phone) {
this.name = name;
this.phone = phone;
}
}

updateInfo Method 를 통해서 수정이 가능해졌습니다 어떻게 보면 Setter 가 더 유연해 보일 수도 있습니다. name 만 수정하고 싶을 수도 있기 때문이죠

모든 Entity Method 를 기능화 시키긴 힘듭니다. 그렇지만 생각 없이 @Setter 를 달고 무분별하게 Setter를 사용하는 것은 지양하는 게 좋다고 생각합니다.

이제 앞으로 추가될 모든 Entity Primary Key id 필드로 제한하고 싶습니다.
여러 이유가 있지만 우선 PK 를 변경해야 하는 상황이 왔을 때 유연하게 대처하고 싶기 때문입니다.

JPA 는 @MappedSuperclass Annotation으로 Parent Entity Class 를 지원합니다.

그럼, 모든 Entity 의 부모가 될 BaseEntity 를 작성하겠습니다.

@Getter
@MappedSuperclass
public class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
        @Column(updatable = false)
protected LocalDateTime createdAt;
        @Column
protected LocalDateTime updatedAt;
        @PrePersist
protected void onPersist() {
this.createdAt = this.updatedAt = LocalDateTime.now();
}
        @PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
}

추가적으로 모든 Entity 의 생성시간, 수정시간을 기록 할 수 있게 @PrePersist, @PreUpdate Annotation 을 사용했습니다.

이제 User EntityBaseEntity 를 상속받게 구현하겠습니다.

@Getter
@Entity
public class User extends BaseEntity {
@Column(length = 20, nullable = false)
private String name;
        @Column(length = 20, nullable = false, unique = true)
private String phone;
        public void updateInfo(String name, String phone) {
this.name = name;
this.phone = phone;
}
}

변경된 User 입니다. 자식 Entity 쪽에서는 id 필드가 사라졌으나.

여전히 id 는 외부 수정이 불가능하고 오직 Persist 시에만 생성됩니다.

다른 팀원들이 BaseEntity를 상속받는 규칙을 지키면 모든 Entityid 필드를 Primary Key 를 가진 형태가 될 것입니다.

자 그럼 조금 더 제 취향에 맞게 변경되었으니, User Entity 에 대하여 단위 테스트를 작성해봅시다.

public class UserTest {
@Test
public void testUpdateUserInfo() {
User user = new User();
        user.updateInfo("gemini","01011223344");
        assertThat(user.getName(), is("gemini"));
assertThat(user.getPhone(), is("01011223344"));
}
}

정상적으로 기능이 동작하는 것을 볼 수 있습니다.

위의 테스트는 단순하지만 연관관계가 엮인 복잡한 테스트는 id 필드에 값이 있어야 할 수도 있습니다,
난감합니다, 테스트 시에는 Id 필드의 임의적 값 할당이 필요할 수도 있겠네요..

setter 를 만들어줘야 할까요? 그러고 싶지는 않으므로, test Module 에 아래와 같은 클래스를 만듭니다.

/** test Module에 만드는 이유는 main Module에서 해당 클래스를 접근할 수 없게 하기 위함입니다. **/
public class MockEntity extends BaseEntity {
public static <T extends BaseEntity> T mock(Class<T> clazz, long id) {
try {
T mock = clazz.newInstance();
mock.id = id;
return mock;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

지저분 해 보이기도 하고 그냥 Setter 만들고 싶어질 수도 있습니다.
그래도 만들었으니 사용해보겠습니다.

@Test
public void testUpdateUserInfoByMock() {
User user = MockEntity.mock(User.class, 1l);
        user.updateInfo("gemini","01011223344");
        assertThat(user.getId(), is(1l));
assertThat(user.getName(), is("gemini"));
assertThat(user.getPhone(), is("01011223344"));
}

이제 테스트 시에만 id 가 존재하는 Entity 생성이 가능합니다.

저의 경우는 테스트 코드 작성 시 Builder 를 만들어서 사용하는 것을 지향하기에 Builder 내부에서 MockEntity 을 사용할 것 같습니다.

굳이 이정도로 idSetter 를 막을 필요는 없을 수도 있습니다. Setter 쓰는 곳 찾는 게 어렵진 않습니다.

그럼에도 제 생각엔 객체의 행위들은 의미가 있어야 하고, 낭비적 코드를 줄여야합니다.
코드를 간결하고 유의미하게 만드는 것이 가장 중요하다고 생각합니다.

어떤 객체는 모든 필드에 대한 Setter 가 존재해야 할 수도 있습니다.
중요한 것은 무작정 @Setter Annotation 부터 선언하지 않는 것이라고 생각합니다.

두서 없는글 읽어주셔서 감사합니다. 피드백은 언제나 환영입니다. geminikims@gmail.com

https://github.com/geminiKim/gemini-jpa-sample/tree/2th

- 개인취향 JPA 사용기 1편

Originally published at gemini.zone on September 1, 2016.

Like what you read? Give Gemini Kim a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.