인프런 커뮤니티 질문&답변

플레어님의 프로필 이미지

작성한 질문수

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

failed to lazily initialize a collection of role 오류 관련 문의

작성

·

20K

4

안녕하세요. 항상 친절하고 정확한 답변 감사합니다.

지금 작업하다가 발생한 오류로 헤매고 있어서 질문드립니다.

소셜로그인으로 로그인 시 기존 회원정보가 없으면 생성하고 나서 소셜로그인 정보 생성하고,

기존 회원정보 있으면 해당 회원정보로 소셜로그인 정보를 생성하려고 합니다. (MemberService.java)

기존에 없는 회원일때 회원정보 생성하고 나서 소셜로그인 정보 생성하는건 잘 되는데,

이미 있는 경우에 기존회원정보를 member에 담아놓고 그걸로 소셜로그인 정보 생성할때 아래와 같은 오류가 발생합니다.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: kr.soleo.mall.base.domain.member.entity.Member.memberSocialProfiles, could not initialize proxy - no

...

수업들은거 잘 생각해서 구현했다고 생각했는데, 무슨 이유인지 모르겠네요. ㅠㅠ

Member.java

@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "email", "username", "age"})
public class Member extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;

private String email;
private String password;
private String username;
private int age;

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
List<MemberAuth> memberAuths = new ArrayList<>();

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
List<MemberSocialProfile> memberSocialProfiles = new ArrayList<>();

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
List<Board> boards = new ArrayList<>();

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;

public Member(String username) {
this(username, 0);
}

public Member(String username, int age) {
this(username, age, null);
}

public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if (team != null) {
changeTeam(team);
}
}

//연관관계 메쏘드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}

@Builder
public Member(String email, String password, String username, int age, Team team) {
this.email = email;
this.password = password;
this.username = username;
this.age = age;
if (team != null) {
changeTeam(team);
}
}

}

MemberSocialProfile.java

@Entity
@Table(name = "member_social_profile")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberSocialProfile extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_social_profile_id")
private Long id;

private String identifier; //소셜로그인시 id
private String photourl; //소셜로그인시 프사이미지 URL
private String email;

@Enumerated(EnumType.STRING)
private SocialType socialType; //facebook, kakao, google, naver

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

//연관관계 메쏘드
public void changeMember(Member member) {
this.member = member;
member.getMemberSocialProfiles().add(this);
}

@Builder
public MemberSocialProfile(SocialType socialType, String identifier, String photourl, String email, Member member) {
this.socialType = socialType;
this.identifier = identifier;
this.photourl = photourl;
this.email = email;
if (member != null) {
changeMember(member);
}
}
}

MemberService.java

@Service
//@RequiredArgsConstructor
@Component
@Configurable
public class MemberService implements UserDetailsService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private MemberAuthRepository memberAuthRepository;
@Autowired
private MemberSocialProfilesRepository memberSocialProfilesRepository;
@Autowired
private PasswordEncoder passwordEncoder;

@Transactional
public void createSocialAccount(MemberDto account) throws Exception {
Member member = null;
if (memberRepository.existsByEmail(account.getEmail())) { //1) member에 해당 이메일로 회원정보가 있으면 그 정보 이용해서 소셜정보생성
member = memberRepository.findByEmail(account.getEmail());

} else { //2) member에 해당 이메일로 회원정보가 없으면 회원정보 생성하고 소셜정보 생성

account.setPassword(passwordEncoder.encode(account.getPassword()));
member = Member.builder()
.email(account.getEmail())
.password(account.getPassword())
.username(account.getUsername())
.build();
memberRepository.saveAndFlush(member);

memberAuthRepository.saveAndFlush(
MemberAuth.builder()
.authType(AuthType.USER)
.member(member)
.build()
);
}

// 1) 조건인 경우 오류발생, 2) 조건인 경우 정상 저장 완료
memberSocialProfilesRepository.saveAndFlush(
MemberSocialProfile.builder()
.identifier(account.getIdentifier())
.photourl(account.getPhotourl())
.email(account.getEmail())
.socialType(account.getSocialType())
.member(member)
.build()
);
}

}

답변 3

7

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 조호영님^^

org.hibernate.LazyInitializationException 이 오류는 member를 조회까지는 성공했는데, member.get MemberSocialProfiles() 를 호출해서 사용할 때 영속성 컨텍스트가 종료되어 버려서, 지연 로딩을 할 수 없어서 발생하는 오류 입니다. JPA에서 지연로딩을 하려면 항상 영속성 컨텍스트가 있어야 하거든요.

보통 트랜잭션 밖에서 member를 조회하면 이런 문제가 발생합니다. 그런데 현재 코드만 보면 트랜잭션 안에서 조회한 것 처럼 보입니다. 트랜잭션이 해당 서비스에서 정상 처리 되는지 먼저 확인해보시겠어요?

그래도 잘 안되면, 동작하는, 그리고 실패하는 테스트 코드를 작성해서 전체 프로젝트를 압축해서 올려주세요.

감사합니다.

와.. 구글링하다 여기까지 왔는데 아무리 찾아봐도 fetch를 eager로 변경하라는 답변밖에 없었는데

@Transactional로 한방에 해결되었네요.

와...  안녕하세요 구글링하다 이 글에 왔는데

'사용할 때 영속성 컨텍스트가 종료되어 버려서, 지연 로딩을 할 수 없어서 발생하는 오류 입니다.'

에 힌트를 얻어 제가 가지고있던 문제 해결에 성공했습니다!

감사 인사를 여기에라도 남깁니다. 좋은 하루 되세요!

3

플레어님의 프로필 이미지
플레어
질문자

안녕하세요. 답변 감사합니다.

트랜잭션 말씀하셔서 소스 다시 봤더니 createSocialAccount 메쏘드에는 @Transaction 어노테이션 적용했는데 저걸 호출하는 메쏘드에는 안했네요. 이부분 추가했더니 정상 처리됩니다.

@Transactional  // <-- 이부분이 없었어요.
public void setOauth2Member(MemberDto account) throws Exception {
memberSocialProfile = memberSocialProfilesRepository.findByIdentifier(account.getIdentifier());
if(memberSocialProfile == null) {
createSocialAccount(account);
}

}

그런데 이해가 안가는게, 위 호출부분에 @Transaction 없었을때,  질문과 같이 한번 생성은 되고 이후에 오류가 났는데 안되려면 초기 생성도 안되어야 하는거 아닌가요? 

0

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 호형님

코드가 조각조각 되어 있어서, 분석하기가 쉽지 않네요^^;

테스트 할 수 있는 테스트 케이스를 만들고 코드를 압축해서

전체 프로젝트를 압축해서 올려주세요.