왜 이 글을 작성하였을까

Spring-data-jpa를 활용하여, 회원(Member Entity)회원권한(MemberRole)에 관한 테이블 설계를 진행하고 있었습니다.

이때, 회원과 회원권한을 OneToMany를 사용하여 설계를 진행하려고 하였지만, 

관련 정보를 좀 더 찾아보다가 제 프로젝트 같은경우,

회원권한에 들어가는 값들은 단순히 (USER, ADMIN, MANAGER) 이런식으로 단순한 고정 Value들이 들어갈 것이기에 OneToMany를 활용하지말고 ElementCollection을 활용하여 회원권한을 컬렉션형태로 사용하는 것이 더 나을 것같아 관련 글을 남기게 되었습니다.

 

다만, 해당글은 ElementCollection을 사용하는 하나의 예시일뿐, 회원권한관리에 대하여서는 일반적으로 OneToMany를 사용하는 것을 더 추천합니다.

 

 @OneToMany를 활용할경우,

1. 회원(Member Entity)의 회원권한(MemberRole)을

2. @OneToMany 형태로 Member Entity의 고유값을 MemberRole에  FK로 두어서 도메인을 설계한다.

 

이렇게 할경우

@OneToMany 어노테이션의 장점인

1. 연관된 엔티티를 통해 엔티티의 라이플사이클에 따라 컬렉션 값들을 관리할 수 있다.

2. 컬렉션 값들에 대해 검색과 조인이 용이하며, 복잡한 쿼리 작성이 가능하다.

 

@OneToMany 어노테이션의 단점으로는

1. 별도의 FK키를 사용하여 연관 엔티티를 저장하기에 테이블 구조가 복잡해집니다.

 

  @ElementCollection을 활용할경우,

1. 회원(Member Entity)의 회원권한(MemberRole)을 

2. @ElementCollection 을 활용하여 Collection을 HashSet으로 관리합니다.

 

이렇게 할경우

@ElementCollection 어노테이션의 장점인

1. Primitive types, String, Int 등 과 같은 타입들을 엔티티의 일부로 사용가능합니다. 예를 들어, 엔티티가 컬렉션 속성을 갖고 있을경우, ElementCollection을 사용하여 컬렉션의 요소를 매핑할 수 있습니다.

2. 단일테이블에 컬렉션 값들이 저장되어, 별도의 조인 테이블이 필요하지 않습니다.

3. 컬렉션을 지원하는 데이터타입을 사용할 수 있습니다. Set, List, Map 을 활용할 수 있습니다.

 

@ElementCollection 어노테이션의 단점인

1. 연관된 엔티티가 존재하지 않아서 엔티티의 생명주기에 종속되지 않습니다.

2. 컬렉션 값들에 대한 검색이나 조인이 어려울 수 있습니다.

 

 

이러한 장점과 단점들을 알아보았을때, 제 프로젝트 같이 단순히 USER, ADMIN, MANAGER 이런 권한이 들어갈경우 ElementCollection을 활용하여 권한 컬렉션들을 관리하는것이 좀 더 관리하기 편할 것 입니다.

 

ElementCollection을 사용한 예시코드

 

MemberEntity

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class Member extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mno;

    private String email;

    private String password;

    /**
     * ElementCollection을 활용하여 Member 객체의 일부로만 사용합니다.
     */
    @ElementCollection(fetch = FetchType.LAZY)
    @Builder.Default
    private Set<MemberRole> roleSet = new HashSet<MemberRole>();

    public void addMemberRole(MemberRole memberRole){
        roleSet.add(memberRole);
    }

}

 

MemberRole.java

public enum MemberRole {
    USER, MANAGER, ADMIN, 
}

 

위와 같이 코드를 작성할경우, hibernate 에서 자동으로 MemberRole 테이블도 만들어주어 관리를 해줍니다.

 

MemberRepository.java

public interface MemberRepository extends JpaRepository<Member, Long> {

    /**
     * 이메일을 기준으로 조회합니다.
     * @param email : email
     * @return
     */
    @EntityGraph(attributePaths = {"roleSet"}, type = EntityGraph.EntityGraphType.LOAD)
    @Query("select m from Member m where m.email = :email")
    Optional<Member> findByEmail(String email);


}

EntityGraph를 사용하여 자동으로 'left outer join'을 작동하게 합니다.

 

@SpringBootTest
public class MemberRepositoryTests {
    @Autowired
    private MemberRepository memberRepository;

    @DisplayName("Member 테스트데이터 삽입")
    @Test
    public void insertMembers(){

        IntStream.rangeClosed(1, 20).forEach(i ->{
            Member member = Member.builder()
                    .email("user"+i+"@hello.com")
                    .password("password")
                    .build();

            //default Role
            member.addMemberRole(MemberRole.USER);
            if( i > 10) member.addMemberRole(MemberRole.ADMIN);
            memberRepository.save(member);
        });
    }

    @DisplayName("Member 데이터 조회")
    @Test
    public void testRead(){
        Optional<Member> result = memberRepository.findByEmail("user1@hello.com");
        Member member = result.get();
        System.out.println(member);
    }

}

 

직접 insert한 뒤에 testRead를 할경우 올바르게 나오는 것을 알 수 있습니다.

 

 

 

마무리

이번에 elementCollection을 활용하여 고정된 value들을 사용할 경우 직관적으로 DB를 설계할 수 있다는 것을 알게되었던 것 같습니다.

또 추가로 elementCollection에 대해 찾아보니, Address(주소)에서와 같이 서울시, 경기도 이런식으로 고정된 값들을 사용하는곳에서도 사용되고 있던 것도 기억에 남습니다.

 

 

+ Recent posts