티스토리 뷰

반응형

 

JPA를 꾸준히 이용하며 학습중인데

이번에 테이블 연관관계를 설정하다가 복합키테이블을 연관지을일이 생겼다.

 

내 머리속으로 이해한데로 엔티티를 구성하고 테스트 쿼리를 수행했으나...

조인들이 제대로 걸리지 않았다.

 

그래서 무언가 복합키 상황일때 엔티티간 연관관계를 잘못 구성했는가 싶어

예제 테이블 만들어 다시 처음부터 구성해봤다.

 

 

테이블 구조는 아래와 같다.

사용자 정보가 있는 Users 테이블이 있고

권한정보가 있는 Roles 테이블이 있다.

그리고 유저에게 할당된 권한정보가 있는 UserRoles 테이블이 있다.

UserRoles는 Users의 키와 Roles의 키를 포함하는 식별관계이다.

 

 

테이블 ER구조

 

 

UserRoles 엔티티의 키값이 2개가 되어 일반 엔티티를 구성할 수 없다.

이런 복합키를 가진 엔티티를 구성하는 방법은 2가지가 있다.

@Embeddable, @IdClass 방식

 

@Embeddable 방식은 객체지향적으로 코드를 구성 할 수 있는 장점이 있으나,

데이터를 조회할때 한 뎁스 더 들어가서 조회해야하고 그리고 연관깊이가 깊어지면 그 만큼 조회 뎁스가 깊어진다.

 

@IdClass 방식은 데이터베이스 구조로 @Id 변수를 중복으로 또 작성해줘야 하나,

조회할때 즉시 접근할 수 있는 장점이 있다.

 

두가지 방식 중 무엇이 더 좋다! 라기 보단 데이터 구조 상황에 따라 쓰면 될 것 같다는 생각.

일단 나는 @Embeddable 방식으로 구성했다.

 

@Embeddable
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserRolesId implements Serializable {

    private Integer userId;
    private Integer roleId;
}

 

@Embeddable 어노테이션을 설정해주고 복합키값 2개를 설정해준다.

그런 다음 엔티티에는 아래와 같이 작성한다.

 

@Entity(name="user_roles")
@Getter
public class UserRoles {

    @EmbeddedId
    private UserRolesId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("userId")
    private Users users;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("roleId")
    private Roles roles;

}

 

복합키값을 지정한 UserRolesId를 @EmbeddedId 어노테이션으로 선언해주고

각각의 연관된 Users 테이블과 Roles 테이블을 N:1 (@ManyToOne) 으로 설정하고

@MapsId 로 각 외래키를 연결해준다.

 

그리고 Users 엔티티와 Roles 엔티티를 아래와 같이 만들어준다.

 

@Entity(name="users")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Users {

    @Id
    @Column(name="id")
    private Integer id;

    @Column(name="user_name")
    private String name;

    @OneToMany(mappedBy = "users", cascade = CascadeType.ALL)
    private Set<UserRoles> roles = new HashSet<>();
}
@Entity(name="roles")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Roles {

    @Id
    @Column(name="role_id")
    private Integer id;

    @Column(name="name")
    private String name;

    @OneToMany(mappedBy = "roles", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<UserRoles> users = new ArrayList<>();

}

 

JPQL 쿼리메소드를 아래와 같이 작성하고 쿼리를 보면 다음과 같다.

(위에 주석쿼리는 처음에 fetch join을 안걸어가지고 사용했던 쿼리다.)

public interface UserRolesRepository extends JpaRepository<UserRoles, Integer> {

//    @Query("FROM user_roles as ur Left JOIN ur.roles as r Left join ur.users as u where u.name = :username")
    @Query("select ur FROM user_roles as ur join fetch ur.roles as r join fetch ur.users as u where u.name = :username")
    List<UserRoles> findAllUserRoles(@Param("username") String username);

}
 select
        userroles0_.roles_role_id as roles_ro2_3_0_,
        userroles0_.users_id as users_id1_3_0_,
        roles1_.role_id as role_id1_2_1_,
        users2_.id as id1_4_2_,
        roles1_.name as name2_2_1_,
        users2_.user_name as user_nam2_4_2_       
    from
        user_roles userroles0_       
    inner join
        roles roles1_               
            on userroles0_.roles_role_id=roles1_.role_id       
    inner join
        users users2_               
            on userroles0_.users_id=users2_.id       
    where
        users2_.user_name='user@test.com'

 

fetch join 으로 관련 엔티티를 모두 가져오는 것을 볼 수 있다.

fetch 조인 안걸면 UserRoles 엔티티 키값만 가져온다.

        userroles0_.roles_role_id as roles_ro2_3_0_,
        userroles0_.users_id as users_id1_3_0_,

 

 


 

 

쿼리메소드를 QueryDSL로도 다시 작성해서 확인해봤다.

public List<UserRoles> findAllUserRolesDsl(String username) {
        return jpaQueryFactory
                .select(QUserRoles.userRoles)
                .from(QUserRoles.userRoles)
                .leftJoin(QUserRoles.userRoles.roles, QRoles.roles).fetchJoin()
                .leftJoin(QUserRoles.userRoles.users, QUsers.users).fetchJoin()
                .where(QUsers.users.name.eq(username))
                .fetch();
    }

 

결과는 위 쿼리결과와 같이 나올 꺼다.

select
        userroles0_.roles_role_id as roles_ro2_3_0_,
        userroles0_.users_id as users_id1_3_0_,
        roles1_.role_id as role_id1_2_1_,
        users2_.id as id1_4_2_,
        roles1_.name as name2_2_1_,
        users2_.user_name as user_nam2_4_2_       
    from
        user_roles userroles0_       
    left outer join
        roles roles1_               
            on userroles0_.roles_role_id=roles1_.role_id       
    left outer join
        users users2_               
            on userroles0_.users_id=users2_.id       
    where
        users2_.user_name='user@test.com'

 

 

 

 

 

 

아 JPA는 하면 할수록 뭔가 더 알아가야 할께 많다.

그 동안 DB 사용을 연관성없게 마구잡이로 짰다는 생각이 들게 만든다.

약간 법칙을 무시하고 결과론적으로만 작성했다고 해야하나?

 

JPA를 쓰다보니 지켜줘야 할 것들이 있어서 이런 느낌을 받는다.

 

 

반응형
댓글
반응형
최근에 올라온 글
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Total
Today
Yesterday