작성
·
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
"즉, 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로 빈을 등록해주는 방법을 사용하면 잘 되고 있습니다.
넵
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 ]