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

준준님의 프로필 이미지
준준

작성한 질문수

스프링 핵심 원리 - 기본편

스프링으로 전환하기

JpaRepository를 이용할시 에러가 발생합니다.

작성

·

4K

0

안녕하세요.

스프링 입문강의에서 들었던 내용과 코드를 바탕으로 스프링 핵심강의를 듣고 있습니다.

 

그런데 해당 문제가 발생합니다.

[ 문제점 ]

기존의 JpaRepository를 이용해 Repository의 빈을 등록해주는 코드를 사용해서 ApplicationContext 객체로 memberService, orderService 를 불러오면 아래와 같은 에러가 발생합니다.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springConfig': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'xik.ShoppingMall.Repository.MemberRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

 

그래서 MemoryRepository 로 Bean을 등록해서 테스트해주니 에러가 해결됬습니다.

이 부분에 대해서 구글링 몇 시간동안 찾아봤는데 원인을 모르겠더라구요

 

그래서 제가 의심가던 부분은 이것입니다.

JpaRepository로 MemberRepository를 구현하면 빈을 자동으로 등록해주는데 이게 ApplicationContext로 IoC컨테이너를 가져오는 코드인

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

이 부분에서 applicationContext 객체가 만들어지기 전에

아직 빈이 등록안되서 생기는 문제일까요??

 

답변 4

0

MemberRepository 구현체 코드와 SpringConfig 코드를 올려주세요.

준준님의 프로필 이미지
준준
질문자

지금 위의 AutoSpringConfig 파일이 IoC컨테이너이고,

MemberRepository 구현체가 SpringDataJpaMemberRepository

입니다.

그래서 아래 소스코드에 첨부해놓은 SpringConfig 는

AutoSpringConfig 의 filter 속성으로 안읽어들이고 있습니다.

 

 

[ SpringConfig ]

@Configuration
public class SpringConfig {

private final MemberRepository memberRepository;

@Autowired
public SpringConfig(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}

@Bean
public MemberServiceInterface memberService() {
return new MemberServiceImp(memberRepository);
}

// @Bean
// public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// }

@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}

@Bean
public OrderService orderService() {
return new OrderServiceImp(memberRepository, discountPolicy());
}



// @Bean
// public MemberRepository memberRepository() {
// // return new MemoryMemberRepository();
// //return new JDBCMemberRepository(dataSource);
// //return new JdbcTemplateMemberRepository(dataSource);
// //return new JpaMemberRepository(em);
//
// }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

"즉, JpaRepository를 통해 빈이 자동생성되는 줄 알았는데 안되고 있다는 의미이죠.

일주일 째 이유를 찾아보고, 강의를 몇번이고 돌려보고 있는데 이유를 못찾겠네요..."

왜 위와 같이 생각하셨는지 알려주실 수 있을까요?

지금 올려주신 코드로 보면 JpaRepository로 인해 빈이 생성될 수 있는 상황이 아니라서요.

준준님의 프로필 이미지
준준
질문자

jpaRepository로 인해 빈이 생성될 수 있는 상황이 아니라는 말씀이라는 뜻은

jpaRepository를 상속 받으면 빈이 자동생성되는게 아니라는 말씀이시거나

자동생성이 되지 않는 환경이라는 말씀이군요.

 

 

jpaRepository 의 역할 = 해당 상속을 받은 클래스는 빈이 자동 생성됨

AutoSpringConfig 역할 = 컴포넌트 스캔을 해서 컴포넌트( 레토지토리/서비스/컨트롤 ) 들을 읽고 빈을 자동 생성해줌

으로 이해를 했었는데 제가 잘못 이해하고있는것 같습니다.

다시 강의를 듣고 진행해보겠습니다.

감사합니다.

JpaRepository를 상속한 repository에 @Repository 애노테이션을 붙여야 컴포넌트 스캔에 의해 구현체가 빈으로 등록됩니다.

준준님의 프로필 이미지
준준
질문자

문제 해결되었습니다.

이전 수업에서 진행하던 MemberRepository 에서 메서드 명을 findall() 로 하고 있었으며,

Controller 에서 MemberService 호출 -> MemberService에서 findall() 메서드 호출하고 있었습니다.

그러면 jpaRepisotry에는 findall() 가 관련된 부분은 없는데 그 이유는 findAll() 로 메서드가 정의 되어 있기 때문이였습니다.

즉, findall() 을 쓰려면 따로 메서드를 정의 해줘야하던것이였지요..

 

그런데 대답해주신 부분에 대해서 궁금한점이 있습니다.

jpaRepository 는 @Configuration 에 정의 되어있는 @EnableJpaRepository Annotation 때문에 @Repository Annotation 없이도 자동으로 빈이 등록된다고 배웠습니다.

 

이 부분은 제가 잘못알고있는 내용일까요??

수업관련 ppt 55페이지의 소스코드에도 @Repository는 따로 없었습니다.

 

1. 지금 올려주신 코드 상에서는 해당 애노테이션이 보이지 않는데, 혹시 어디에 @EnableJpaRepositories 를 붙여주셨을까요??

2. findall이 아닌 findAll로 사용하시면 별도의 정의 없이 사용 가능합니다.

준준님의 프로필 이미지
준준
질문자

넵 !! 안그래도 findAll 로 바꿔서 진행중입니다 !

 

https://parkadd.tistory.com/106

위의 블로그에서 봤을 때 SpringBoot 에서는 자동으로 @EnableJpaRepository이 설정되기 때문에 따로 안붙쳐도 된다고 알고 있습니다.

그래서 실제로 @Repository를 안붙쳐도 잘 작동되기는 했는데

이 부분은 강의 내용에는 없는 내용이고 구글링으로 찾은 내용이라 부정확합니다 ㅠㅠ 혹시 제가 잘못알고있는 내용일까요??? 

JpaRepository를 구현한 클래스가 존재하면 자동설정에 의해 등록되는 게 맞습니다.

"jpaRepository 는 @Configuration 에 정의 되어있는 @EnableJpaRepository Annotation 때문에 @Repository Annotation 없이도 자동으로 빈이 등록된다고 배웠습니다."

위 문장에서 "@Configuration에 정의되어 있는 @EnableJpaRepository Annotation"이라고 언급하셔서 여쭤보았습니다. 올려주신 코드 상에서는 @EnableJpaRepository가 보이지 않아서요:)

준준님의 프로필 이미지
준준
질문자

제가 말을 잘못 한거 같습니다 !!

오랜 시간 도움 주셔서 감사합니다 !!

아닙니다. 저도 위에 @Repository를 붙여야 빈으로 등록된다고 써놓았는데, 자동설정 부분을 따로 언급하지 않아서 오해하시기 충분했을 것 같습니다. 죄송합니다.

0

준준님의 프로필 이미지
준준
질문자

안되는 부분 설명에 내용이 꼬인것 같아서 다시 정리해서 질문드립니다 !!

 

우선,

[ 현재 상황 ]

1) JpaRepository를 상속받음으로써 Repsitory에 대한 Bean에 넣어줌

2) ComponentScan을 이용한 AutoAppConfig 으로 자동으로 Component 붙은 클래스들을 Bean에 넣어줌

3) 이때 Filter를 통해 Configuration.class 는 읽지 못하게 해놨습니다.

[  에러 상황 ]

1) org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberController': Unsatisfied dependency expressed through field 'memberService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'memberServiceImp' defined in file [/Users/parksungjun/Desktop/창업동아리/ShoppingMall/out/production/classes/xik/ShoppingMall/Service/MemberServiceImp.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'xik.ShoppingMall.Repository.MemberRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

위의 에러 로그를 보면 " Repository에 MemberRepository 유형의 빈이 없으며, Autowired할 수 있는 빈이 최소 하나 필요하다 " 라는 내용으로 보입니다.

 

즉, JpaRepository를 통해 빈이 자동생성되는 줄 알았는데 안되고 있다는 의미이죠.

일주일 째 이유를 찾아보고, 강의를 몇번이고 돌려보고 있는데 이유를 못찾겠네요...

 

그래서 소스 코드를 다시 올리겠습니다.

 

1) 컨트롤러

@Controller
public class MemberController {

@Autowired
private MemberServiceInterface memberService;

@GetMapping("/login")
public String Login() {
return "/Login/login";
}

@GetMapping("/new")
public String New() {
return "/Login/memberNew";
}

@PostMapping("/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
member.setPhoneNumber(form.getPhoneNumber());

memberService.join(member);

// redirect:/ 하면 홈화면으로 보내는 것이다.
return "redirect:/5xik";
}

@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMember();
model.addAttribute("members", members);
return "/Login/memberCheck";
}
}

 

 

2) MemberService

@Transactional
@Component
public class MemberServiceImp implements MemberServiceInterface{

private MemberRepository memberRepository;

// 외부에서 리포지토리를 넣어줄 수 있게끔 직접 nw 하는게 아닌 생성자를 이용해서 만들어준다.
@Autowired
public MemberServiceImp(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

// 회원가입
@Override
public Long join(Member member) {
// 휴대폰 번호 중복 체크
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}

@Override
public void validateDuplicateMember(Member member) {
Optional<Member> result = memberRepository.findByphonenumber(member.getPhoneNumber());
result.ifPresent(m ->{
throw new IllegalStateException("이미 가입된 휴대폰 번호입니다.");
});
}

@Override
public List<Member> findMember() {
return memberRepository.findall();
}

@Override
public Optional<Member> findOne(Long memberId) {
return memberRepository.findByid(memberId);
}
}

 

3) OrderService

@Transactional
@Component
public class OrderServiceImp implements OrderService {

private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

@Autowired
public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}

@Override
public Order createOrder(Long MemberId,Integer price) {
Member member = memberRepository.findByid(MemberId).get();
int discountPrice = discountPolicy.discount(member,price);

return new Order(MemberId, discountPrice);
}
}

 

4) Repository

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

@Override
Optional<Member> findByname(String name);

}

 

5) 빈 조회 테스트 코드

public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoSpringConfig.class);

@Test
@DisplayName("모든 빈 출력하기")
void findBean() {
String[] beanDefinitionName = ac.getBeanDefinitionNames();
for (String i : beanDefinitionName) {
Object bean = ac.getBean(i);
System.out.println("name = " + i + "object" + bean);
}
}

@Test
@DisplayName("application 빈 출력하기")
void findApplication() {
String[] beanDefinitionName = ac.getBeanDefinitionNames();
for (String i : beanDefinitionName) {
BeanDefinition beanDefinition = ac.getBeanDefinition(i);
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(i);
System.out.println("name = " + i + "object" + bean);
}
}
}

}

 

 

6) AutoSpringConfig ( IoC컨테이너 )

@Configuration
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
Configuration.class))
public class AutoSpringConfig {

}

 

 

 

 

준준님의 프로필 이미지
준준
질문자

참고로 모두 같은 환경에서

JpaRepository가 아닌 MemoryMemberRepository를 이용해 @Repository로 빈을 등록해주는 방법을 사용하면 잘 되고 있습니다.

0

SpringConfig가 생성되며 MemberRepository가 주입되어야 하는데, 빈으로 등록되어 있지 않아 발생한 에러입니다. SpringConfig가 생성될 때 MemberRepository를 생성자의 파라미터로 받고 계신 부분과 관련이 있습니다.

준준님의 프로필 이미지
준준
질문자

밑에 정리해서 다시 질문했는데 봐주시면 감사하겠습니다 !

0

안녕하세요. 준준님, 공식 서포터즈 David입니다.

질문과 관련된 코드 모두를 함께 올려주세요. (ex. SpringConfig ... )

감사합니다.

준준님의 프로필 이미지
준준
질문자

1) JpaRepository

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

@Override
Optional<Member> findByName(String name);

}

2) SpringConfig

@Configuration
public class SpringConfig {

private final MemberRepository memberRepository;

public SpringConfig(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}

@Bean
public MemberServiceInterface memberService() {
return new MemberServiceImp(memberRepository);
}

@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}

@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}

@Bean
public OrderService orderService() {
return new OrderServiceImp(memberRepository, discountPolicy());
}
}

3) Test code

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

MemberServiceInterface memberService = applicationContext.getBean("memberService", MemberServiceInterface.class);

OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

@Test
@DisplayName("VIP 10퍼센트 할인이 들어간다.")
void discount() {
//given
Member member = new Member();
member.setName("hooper");
member.setPhoneNumber("013440");
member.setGrade(Grade.VIP);
memberService.join(member);
//when
Order order = orderService.createOrder(member.getId(), "itemB", 30000);

//then
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(3000);
System.out.println("Order: "+order);
}

4) OrderService

@Transactional
public class OrderServiceImp implements OrderService {

private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}

@Override
public Order createOrder(Long MemberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(MemberId).get();
int discountPrice = discountPolicy.discount(member, itemPrice);

return new Order(MemberId, itemName, itemPrice, discountPrice);
}
}

5) MemberService

public class MemberServiceImp implements MemberServiceInterface{

private MemberRepository memberRepository;

// 외부에서 리포지토리를 넣어줄 수 있게끔 직접 nw 하는게 아닌 생성자를 이용해서 만들어준다.
public MemberServiceImp(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

// 회원가입
@Override
public Long join(Member member) {
// 휴대폰 번호 중복 체크
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}

@Override
public void validateDuplicateMember(Member member) {
Optional<Member> result = memberRepository.findByPhoneNumber(member.getPhoneNumber());
result.ifPresent(m ->{
throw new IllegalStateException("이미 가입된 휴대폰 번호입니다.");
});
}

@Override
public List<Member> findMember() {
return memberRepository.findAll();
}

@Override
public Optional<Member> findOne(Long memberId) {
return memberRepository.findById(memberId);
}
}

 

 

 

 

 

 

 

 

 

준준님의 프로필 이미지
준준

작성한 질문수

질문하기