본문 바로가기

프로젝트

SpringBoot로 그룹캘린더 만들기 - 복합키 사용 (feat. EmbeddedId) 에러 해결 : must not have @Id properties when used as an @EmbeddedId:

단두린더의 연관관계는 유저와 그룹이 다대다로 이를 일대다 다대일로 풀어서 해결했다.

그런데..! 잘 해결된줄 알았는데 막상 스프링 데이터 JPA를 적용하려하니 UserGroup 이자식의 키값을 뭘로 써야할지 너무 애매한거다..!

 

왜냐하면

 

// User
public class User {
    @Id
    private String user_id;
    private String user_name;
    
    @OneToMany(mappedBy = "user")
    private List<UserGroup> userGroups = new ArrayList<>();
    
  	// 생략
}

// Group
public class Group {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long group_id;

    @OneToMany(mappedBy = "group")
    private List<UserGroup> userGroups = new ArrayList<>();
	
    // 생략
}

// UserGroup
public class UserGroup {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "group_id")
    private Group group;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
	
    // 생략
}

 

바로 아이디가 두개 이상 즉 복합키로 되어 있었다.

 

열심히 서치 해보니 이럴때는 두가지 방법이 있는데 @IdClass@EmbeddedId 를 사용해서 해결할 수 있다고 한다.

IdClass는 좀 더 직관적이고 값을 직접 조회할 수 있는 반면, EmbeddedId는 뎁스가 한 단계 더 깊어진다고 한다..

객체지향적인 관점에서 @EmbeddedId로 구현할 수 있는 장점이 있어 나는 이번에 EmbeddedId를 사용해보았다.

 

 

 

우선 EmbeddedId를 사용하기 위해서 클래스를 하나 더 생성해줘야 한다. 이 클래스가 JpaRepository<T(엔티티 타입), ID (식별자 타입 PK)> ID 식별자 값으로 들어간다.

 

@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class UserGroupId implements Serializable {
    @Column(name = "user_id")
    private String userId;
    @Column(name = "group_id")
    private Long groupId;
}

EmbeddedId 방식을 사용하면 필수로 지켜야 하는 방식이 있다고 하는데 public 깡통 생성자와 equal & hashCode 오버라이드, serializable... 암튼 열심히 룰에 맞춰 클래스를 생성한다.

유저그룹의 복합키 두 개를 가져와 각각 찐 키값의 타입으로 설정해준다. 유저아이디, 그룹아이디 설정

 

@Entity
@Getter
@Builder
@AllArgsConstructor
@DynamicInsert
public class UserGroup {
    @EmbeddedId
    private UserGroupId id;

    @MapsId("groupId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "group_id")
    private Group group;

    @MapsId("userId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    protected UserGroup() {

    }
}
에러 해결 : must not have @Id properties when used as an @EmbeddedId:

 

여기서 제목에 적은 에러를 해결할 수 있었다..

복합키 두개가 사실은 유저와 그룹을 외래키로 연결해줘야한다. 그것을 연결 하는게 바로 @MapsId 이다..!! 이 전에는 모르고 @Id를 둘다 할당해줘서 썼는데 맵스아이디라는 친구가 있었다.

 

바붕 ㅠ MpapsId에 변수명 잘 할당 해주면 끝난다.

아맞다 @EmbeddedId는 우리가 바로 전에 만들어준 UserGroupId로 넣어주면 끝!

 

 

Q. userId를 사용해서 UserGroup을 얻어오려면 어떻게 해야하나요?

그러니까 복합키 중 키 값 하나만 사용해서 값을 얻어오고 싶다.

아놔 진짜 Spring Data JPA는 findById 해서 ID에 UserGroupId 를 넣게 되어 있다.. ㅠ 씨잉 이거 진짜 어케 찾는거란 말이야~!~!

 

UserGroupId userGroupId = UserGroupId.builder().userId(userId).build();
Optional<UserGroup> byId = userGroupRepository.findById(userGroupId);

삽질 삽질

이렇게 찾으면 조건절에 groupId=null and userId='' 이렇게 들어가서 그룹아이디 설정도 안해줬는데 .. 결국 최종 리턴값이 null로 나온다. 흑

 

 

암튼 열심히 찾아낸 결과

 

public interface UserGroupRepository extends JpaRepository<UserGroup, UserGroupId> {
    Optional<UserGroup> findByIdUserId(String userId);
}

Repository에서 메소드명으로 구현하기~!를 활용해서 findByIdUserId 라고 해주면 된다.

 

Hibernate: 
    select
        usergroup0_.group_id as group_id1_4_,
        usergroup0_.user_id as user_id2_4_,
        usergroup0_.modr_id as modr_id3_4_,
        usergroup0_.reg_dt as reg_dt4_4_,
        usergroup0_.regr_id as regr_id5_4_ 
    from
        UserGroup usergroup0_ 
    where
        usergroup0_.user_id=?

그러면 쿼리도 userId 알아서 잘 서치해서 나간다. 유후~!