Inflearn Community Q&A
JpaRepository를 이용할시 에러가 발생합니다.
Written on
·
4.5K
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 객체가 만들어지기 전에
아직 빈이 등록안되서 생기는 문제일까요??
Quiz
What are the problems that easily occur when client code directly creates the object it wants to use with the `new` keyword?
Compile error occurs.
When requirements change, client code needs to be modified.
Object serialization becomes impossible.
Multithreading problems occur.
Answer 4
0
"즉, JpaRepository를 통해 빈이 자동생성되는 줄 알았는데 안되고 있다는 의미이죠.
일주일 째 이유를 찾아보고, 강의를 몇번이고 돌려보고 있는데 이유를 못찾겠네요..."
왜 위와 같이 생각하셨는지 알려주실 수 있을까요?
지금 올려주신 코드로 보면 JpaRepository로 인해 빈이 생성될 수 있는 상황이 아니라서요.
jpaRepository로 인해 빈이 생성될 수 있는 상황이 아니라는 말씀이라는 뜻은
jpaRepository를 상속 받으면 빈이 자동생성되는게 아니라는 말씀이시거나
자동생성이 되지 않는 환경이라는 말씀이군요.
jpaRepository 의 역할 = 해당 상속을 받은 클래스는 빈이 자동 생성됨
AutoSpringConfig 역할 = 컴포넌트 스캔을 해서 컴포넌트( 레토지토리/서비스/컨트롤 ) 들을 읽고 빈을 자동 생성해줌
으로 이해를 했었는데 제가 잘못 이해하고있는것 같습니다.
다시 강의를 듣고 진행해보겠습니다.
감사합니다.
문제 해결되었습니다.
이전 수업에서 진행하던 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가 보이지 않아서요:)
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를 생성자의 파라미터로 받고 계신 부분과 관련이 있습니다.
넵
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);
}
}






지금 위의 AutoSpringConfig 파일이 IoC컨테이너이고,
MemberRepository 구현체가 SpringDataJpaMemberRepository
입니다.
그래서 아래 소스코드에 첨부해놓은 SpringConfig 는
AutoSpringConfig 의 filter 속성으로 안읽어들이고 있습니다.
[ SpringConfig ]