📎 관련 Github PR: #120
📎 관련 Github Issue: #99
다음과 같은 실제 상황을 가정해보겠습니다.
test@naver.com 으로 최초 로그인한 뒤, 포털 연동을 완료했습니다.test@kakao.com으로 변경한 뒤, 다시 척척학사 서비스에 로그인했습니다.그러나,
왜 이런 일이 벌어진 걸까요?
기존 로그인 흐름은 아래와 같았습니다.

email 필드를 기반으로 User를 조회하거나 생성했습니다.@OneToOne 관계로 Student를 연결했습니다.@Entity
@Table(name = "students")
public class Student {
@Column(name = "student_code", nullable = false, unique = true)
private String studentCode; // 학번
}
studentCode 필드는 UNIQUE 제약 조건이 걸려 있었습니다.
email을 기준으로만 유저를 식별했기 때문에,studentCode로 Student 생성 시도 → ❌ UNIQUE 제약 조건 위반!ERROR:
ERROR: duplicate key value violates unique constraint "uk_student_code"
🙅 이 문제는 이메일 변경뿐만 아니라 아래의 경우에도 발생할 수 있습니다:
SocialAccount 도입이 문제를 해결하기 위해 SocialAccount 테이블을 도입했습니다.
User.email이 아닌 (provider + socialId)로 위임@Entity
@Table(name = "social_accounts", uniqueConstraints = {
@UniqueConstraint(name = "uk_provider_social_id", columnNames = {"provider", "social_id"})
})
public class SocialAccount {
@Enumerated(EnumType.STRING)
private OidcProvider provider;
@Column(name = "social_id", nullable = false)
private String socialId;
@Column(name = "email", nullable = true)
private String email;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
public void updateUser(User user) {
this.user = user;
}
}
@Transactional
public User tryMergeWithExistingUser(UUID currentUserId, String studentCode) {
User currentUser = getUserById(currentUserId);
Optional<User> existingUserOpt = userRepository.findByStudent_StudentCode(studentCode);
if (existingUserOpt.isEmpty()) {
return currentUser;
}
User existingUser = existingUserOpt.get();
// 기존 유저로 소셜 계정 이전
List<SocialAccount> accounts = socialAccountRepository.findAllByUserId(currentUserId);
for (SocialAccount sa : accounts) {
sa.updateUser(existingUser);
}
// 임시 유저 제거
userRepository.delete(currentUser);
log.info("[BIZ] user.merged currentUserId={} → existingUserId={}", currentUserId, existingUser.getId());
return existingUser;
}

(provider=KAKAO, socialId=xxx)로 SocialAccount 생성(provider=APPLE, socialId=yyy)로 로그인이번 구조 개선을 통해 다음과 같은 이점을 얻었습니다.
기존에는 '이메일'이 식별 기준이었다면,
이제는 '소셜 Provider + 고유 ID(socialId)'가 확실한 유저 구분 기준입니다.
앞으로 소셜 로그인 확장에 있어서도 더 이상 충돌 없이 안정적인 확장이 가능합니다.