inflearn logo
강의

Course

Instructor

Real-world! Spring Boot and JPA Utilization 1 - Web Application Development

Member functionality test

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

Resolved

613

sorjfkrh50782458

5 asked

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를 통한 접근은 정상적으로 처리되는 이유를 모르겠습니다.

JPA java spring 웹앱 spring-boot

Answer 4

1

yh

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

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

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

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

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

감사합니다.

1

yh

안녕하세요. 최한슬님

다음을 참고해주세요.

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

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

감사합니다.

0

sorjfkrh50782458

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

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

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

0

yh

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

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

감사합니다.

0

sorjfkrh50782458

감사합니다!!!

0

sorjfkrh50782458

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

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

[첫번째 링크]

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()로 처리했을때 오류가 발생하는지 모르겠습니다.

sdk 설정 오류

0

49

2

오탈자 - @Transactional

0

55

1

src/test/resources 테스트 경로 문제

0

50

1

상품 등록후 H2 db 출력 순서 바꿀 수 있나요?

0

63

1

MemberRepositoryTest 실행오류

0

80

1

boot 4.x >>> trasasction rolled back log & p6spy(영한님, 수업 자료 업데이트 해주시면 감사하겠습니다!!)

1

183

2

강의 마지막 QueryDSL 사용 부분 질문있습니다

1

137

2

클라이언트에서 isbn과 author 수정 요청을 한 경우에 대해 질문드립니다.

0

51

1

도메인 모델 패턴 vs 트랜잭션 스크립트 패턴

0

71

1

기본 생성자

0

60

1

h2 DB 연결시 jdbc url 변경 이유가 궁금합니다.

0

100

1

멤버서비스테스트 부분에서 막힙니다.

0

164

4

실무에서도 EntityManager를 이용해서 많이 작업하는 편일까요?

0

116

1

초반에 h2 다운로드 과정 꼭 필요한가요?

0

118

2

자신 필드에도 get으로 접근하는 이유가 있을까요?

0

112

1

24분 27초 연관관계 편의 메서드 위치

0

113

1

단건 주문만 가능하게 한건 의도한 부분이신가요?

0

108

2

빌드 툴, Gradle

0

58

1

h2연결은 된 것 같은데 엔티티 테이블까지 작성 후 확인해보아도 테이블이 안보입니다

0

76

2

Repository에서 EntityManager 주입 방식 차이

0

88

1

롬복과 사용자 정의 setter 메서드

0

71

1

주문 목록 조회 fetch join 질문드립니다

0

81

1

dirty checking 질문드립니다.

0

81

1

동시성 관련 질문입니다

0

73

1