강의

멘토링

커뮤니티

Cộng đồng Hỏi & Đáp của Inflearn

Hình ảnh hồ sơ của sorjfkrh50782458
sorjfkrh50782458

câu hỏi đã được viết

Trong thực tế! Sử dụng Spring Boot và JPA1 - Phát triển ứng dụng Web

Kiểm tra chức năng thành viên

@Transacional의 범위에 대해서 궁금한 점이 하나 있습니다!

Đã giải quyết

Viết

·

600

1

안녕하세요 강사님. 항상 훌륭한 강의 감사드립니다.

스프링 MVC 1, 2편에서 사용했던 프로젝트에 JPA를 적용시키는 도중 궁금한점이 하나 생겨서 질문드립니다.

TestInitData 클래스에 @PostConstruct로 데이터베이스에 초기 데이터들을 넣어두려고 합니다.

package com.myservice.web.test;

import com.myservice.domain.item.Item;
import com.myservice.domain.item.ItemRepository;
import com.myservice.domain.member.Grade;
import com.myservice.domain.member.Member;
import com.myservice.domain.member.MemberRepository;
import com.myservice.domain.member.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;

@Slf4j
@Component
@RequiredArgsConstructor
@Transactional
public class TestDataInit {

private final ItemRepository itemRepository;
private final MemberService memberService;
private final MemberRepository memberRepository;

/**
* 테스트용 데이터 추가
*/
@PostConstruct
public void init() {
itemRepository.save(new Item("itemA", 10000, 10));
itemRepository.save(new Item("itemB", 20000, 20));
itemRepository.save(new Item("itemC", 15000, 15));

Member member1 = new Member();
member1.setLoginId("manager");
member1.setPassword("manager");
member1.setUsername("최한슬");
member1.setGrade(Grade.MANAGER);

Member member2 = new Member();
member2.setLoginId("user");
member2.setPassword("user");
member2.setUsername("USER");

//바로 memberRepository.save로 접근하면 현재 스레드에서 사용할 수 있는 EntityManager가 없다고 오류 발생
memberRepository.save(member1);
memberRepository.save(member2);
//memberService.save -> memberRepository.save로 접근하면 정상적으로 작동
memberService.save(member1);
memberService.save(member2);
}

}

또한, memberRepository와 memberService는 다음과 같습니다.

[memberRepository]

package com.myservice.domain.member;

import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface MemberRepository {

Long save(Member member);

Optional<Member> findById(Long id);

Optional<Member> findByLoginId(String loginId);

List<Member> findAll();
}
package com.myservice.domain.member;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

@Repository
@RequiredArgsConstructor
@Primary
public class JpaMemberRepository implements MemberRepository {

private final EntityManager em;

@Override
public Long save(Member member) {
em.persist(member);
return member.getId();
}

@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}

@Override
public Optional<Member> findByLoginId(String loginId) {
Member member = em.createQuery("select m from Member m where m.loginId = :loginId", Member.class)
.setParameter("loginId", loginId)
.getResultStream()
.findAny()
.orElse(null);

return Optional.ofNullable(member);
}

@Override
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}

}

[memberService]

package com.myservice.domain.member;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {

private final MemberRepository memberRepository;

public void save(Member member) {
memberRepository.save(member);
}
}

현재 MemberService에는 @Transactional이 걸려있고, memberRepository에는 @Transactional 걸려있지 않습니다.

궁금한점은 TestDataInit 클래스의 init() 메서드에서 바로 memberRepository로 접근하게되면 사용할 수 있는 EntityManager가 없다고 나오며,

memberService->memberRepository로 접근하게 되면 정상적으로 처리되는 것을 확인하였습니다.

두 방식 모두 결국 memberRepository를 통해 save를 수행하게 되는데 바로 memberRepository의 접근은 오류가 발생하고 memberService를 통한 접근은 정상적으로 처리되는 이유를 모르겠습니다.

JPAjavaspring웹앱spring-boot

Câu trả lời 4

1

yh님의 프로필 이미지
yh
Người chia sẻ kiến thức

아 한슬님 무엇이 문제인지 알겠네요.

@Transactional이 @PostConstruct에 함께 걸려있으면 스프링 라이프사이클 문제때문에 @Transactional이 정상 동작하지 않을 수 있습니다.

@PostConstruct가 먼저 실행되고 이후에 @Transactional AOP가 적용되기 때문입니다.

간단한 해결방안은 제가 강의에서 한 것 처럼 초기화 하는 메서드와 초기화를 실행하는 메서드를 분리해주세요.

그러면 라이프사이클 문제가 해결됩니다.

감사합니다.

1

yh님의 프로필 이미지
yh
Người chia sẻ kiến thức

안녕하세요. 최한슬님

다음을 참고해주세요.

https://www.inflearn.com/questions/158967

https://www.inflearn.com/questions/159466

감사합니다.

0

sorjfkrh50782458님의 프로필 이미지
sorjfkrh50782458
Người đặt câu hỏi

감사합니다. 강사님 ㅠㅠ, 몇일동안 많이 고민했었는데 강사님 덕분에 속이 뚫린 느낌입니다!

AOP는 모든 스프링 빈이 처리되고 나서 적용되기 때문에 TestDataInit 클래스가 빈에 등록될때 호출되는 @PostConstruct 시점에는 AOP 중 하나인 @Transactional이 적용되는 것을 보장해주니 않는 것이군요.

그렇다면 MemberService로 접근해서 성공한 것도 정확히 말한다면 @Transactional이 보장되지 않지만 운이 좋아서 성공한 것이라고 생각하면 되는 것일까요?

yh님의 프로필 이미지
yh
Người chia sẻ kiến thức

MemberService로 접근하는 것은 @PostConstruct 본인이 아니라 다른 외부 객체를 호출한 것이기 때문에 이때는 @Transactional이 잘 적용됩니다.

의존관계 주입을 받을 때는 AOP가 이미 적용된 부분을 받게 됩니다.

감사합니다.

sorjfkrh50782458님의 프로필 이미지
sorjfkrh50782458
Người đặt câu hỏi

감사합니다!!!

0

sorjfkrh50782458님의 프로필 이미지
sorjfkrh50782458
Người đặt câu hỏi

안녕하세요 강사님, 올려주신 두 링크를 확인하고 많은 고민을 해봤습니다.

제가 이해한 바는 다음과 같습니다.

[첫번째 링크]

Repository에는 @Transactionl이 없기 때문에 EntityManger에 우선 프록시 객체를 주입해준 다음 다른 트랜잭션에서 해당 Repository에 접근하게 되면 그 EntityManger를 사용하겠다.

[두번쨰링크]

메서드 A에 @Transactional을 걸었다면 메서드 내부에 트랜잭션이 전파되기 때문에 A에서 호출되는 다른 기능들은 동일한 트랜잭션이다.

두 링크를 다음과 같이 이해하였습니다.

그렇다면 init() 메서드를 트랜잭션 A라고 가정하겠습니다.

[MemberService]

init()에서 memberService.save 호출 => 트랜잭션 A

memberService에 @Transactional이 있음, 따라서 memberService.save => 트랜잭션 B

memberRepository.save, Repository는 @Transactional이 없으니 EntityManager에는 프록시 객체가 있음. memberService로 인해 호출되었으니 meberService의 EntityManger를 사용해서 디비에 저장 => 트랜잭션 B

[MemberRepository]

init()에서 memberRepository 호출 => 트랜잭션 A

memberRepository.save, Repository는 @Transactional이 없으니 EntityManager에는 프록시 객체가 있음. init()로 인해 호출되었으니 init()의 EntityManger를 사용해서 디비에 저장 => 트랜잭션 A

그렇다면 두 방법 모두 EntityManger에는 프록시객체가 아닌 트랜잭션 A or B의 EntityManger가 존재하는 것이 아닌건가요?

둘다 존재한다면 MemberService를 거치지 않고 바로 MemberRepository.save()로 처리했을때 오류가 발생하는지 모르겠습니다.

Hình ảnh hồ sơ của sorjfkrh50782458
sorjfkrh50782458

câu hỏi đã được viết

Đặt câu hỏi