애플리케이션 아키텍쳐
- 계층형 구조 사용
Controller --> Service --> Repository --> DB
Controller, Web : 웹 계층
Service : 비즈니스 로직, 트랜잭션 처리
Repository : JPA를 직접 사용하는 계층, 엔티티 매니저 사용
Domain : 엔티티가 모여있는 계층, 모든 계층에서 사용
- 패키지 구조
jpabook. jpashop
- domain
- exception
- repository
- service
- web
<회원 리포지토리 개발>
package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import java.util.List;
@Repository // 컴포넌트 스캔으로 자동으로 스프링에서 스프링 빈으로 관리
@RequiredArgsConstructor
public class MemberRepository {
@PersistenceContext // jpa가 제공하는 표준 어노테이션
// 이 어노테이션으로 엔티티 매니저를 스프링이 생성한 엔티티 메니저에 주입을 해준다.
private EntityManager em; //entity manager 생성
//저장 로직
public void save(Member member) {
em.persist(member) ; // jpa가 member 객체를 저장
} // 영속성 컨텍스트에 객체를 주입(insert 쿼리 생성)
public Member findOne(Long id){
return em.find(Member.class, id); // 단건 조회(id는 pk)
} // member를 찾아서 반환
public List<Member> findAll(){
return em.createQuery("select m from Member m", Member.class)
.getResultList(); // jpa 쿼리 작성 = jpql (sql과는 다르지만 기능적으로는 동일)
// sql은 테이블 중심으로 처리하지만, jpql은 객체를 중심으로 처리
// Member.class는 반환 타입
// getResultList로 멤버를 리스트로 만들어준다.
}
public List<Member> findByName(String name) {
return em.createQuery("select m from Member m where m.name = name", Member.class)
.setParameter("name", name)
.getResultList();
} // 파라미터 바인딩을 통해 특정 이름을 가진 객체를 가져온다.
}
em.persist를 통해 트랜잭션이 커밋되는 시점에서 디비에 입력값이 반영된다.
@Repository : 컴포넌트 스캔으로 스프링에 등록
@PersistentContext : 엔티티 매니저를 만들어서 스프링에 injection하게 함
select m from Member m where m.name = :name
--> name을 기준으로 회원을 조회하여 List로 반환
회원 서비스 개발
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service // 컴포넌트 스캔으로 스프링 빈에 등록
@Transactional // lazy 로딩 등 트렌젝션 처리가 가능하기 위해 어노테이션 추가하기
// 영속성 컨텍스트
@RequiredArgsConstructor // lombok에서 생성자 자동 생성
public class MemberService {
//생성자 인젝션 시
private final MemberRepository memberRepository;
// 회원 가입
@Transactional // default이지만 추가해 봄
public Long join(Member member){
validateDuplicateMember(member); // 중복 회원 검증하는 로직 구현
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
// 문제가 있으면 예외 터뜨리기, 문제 없으면 넘어가서 정상적으로 save, id 반환
List<Member> findMembers = memberRepository.findByName(member.getName());
// 매개변수에 들어온 member 객체가 findMember에 존재하면 중복된 회원이므로 에러 메세지 출력
if (!findMembers.isEmpty()){
throw new IllegalStateException("이미 존재하는 회원입니다. ");
}
}
// 회원 전체 조회
// @Transactional(readOnly = true) // 조회하는 성능을 최적화 (읽기 전용에 추가)
public List<Member> findMembers(){
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
MemberRepository에서 구현한 findAll, findOne 메소드로 return 값을 구현한다.
스프링에 필드를 주입하는 방법은 다음과 같다
1) 필드 주입
public class MemberService {
@Autowired
MemberRepository memberRepository;
...
}
이렇게 @Autowired를 사용해, memberRepository와 memberservice 필드간의 의존성을 주입
2) 생성자 주입
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
필드를 final로 지정한 다음에, 생성자를 만들어준다. (매개변수에는 MemberRepository의 객체)
MemberService 내의 멤버 변수 memberRepository를 초기화 시키는 코드를 생성자에 넣어주어서 injection
3) lombok 라이브러리 활용
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
...
}
필드를 final로 지정한 다음에 클래스를 @RequiredArgsConstructor 어노테이션을 이용해 final 키워드가 붙은 필드의 생성자를 자동생성한다.
--> 생성자 주입을 간단히 수행하는 방법이라고 할 수 있다.
테스트 코드
package jpabook.jpashop.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import static org.junit.Assert.*;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
MemberRepository memberRepository;
@Test
public void 회원가입() throws Exception{
//given
Member member = new Member();
member.setName("kim");
//when
Long saveId = memberService.join(member);
//then
assertEquals(member, memberRepository.findOne(saveId));
}
@Test(expected = IllegalStateException.class)
public void 중복_회원_예외() throws Exception{
//given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//when
memberService.join(member1);
try{
memberService.join(member2); // 여기서 예외가 발생해야 한다.
} catch (IllegalStateException e){
return ;
}
//then
fail("예외가 발생해야 합니다.");
}
}
회원 가입을 테스트하고, 중복 회원에 대해 예외처리하는 테스트 코드를 작성하였다.
@Runwith(SpringRunner.class) : 스프링 컨테이너와 현재 테스트 코드를 연결하는 역할
@SpringBootTest : 테스트 시 스프링 부트를 띄우게 하는 어노테이션
@Transactional : 테스트를 실행할때마다 트랜잭션을 시작한다. 어노테이션이 테스트 케이스에서 사용될 때만 롤백된다.
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'logging.level' to java.util.Map<java.lang.String, org.springframework.boot.logging.LogLevel>
at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:363)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:323)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:308)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:238)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:212)
at org.springframework.boot.context.logging.LoggingApplicationListener.setLogLevels(LoggingApplicationListener.java:373)
at org.springframework.boot.context.logging.LoggingApplicationListener.initializeFinalLoggingLevels(LoggingApplicationListener.java:340)
at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:282)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:239)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:216)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:82)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:63)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:117)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:111)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:62)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:375)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:333)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
... 24 more
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, org.springframework.boot.logging.LogLevel>]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.boot.context.properties.bind.BindConverter$CompositeConversionService.convert(BindConverter.java:170)
at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:96)
at org.springframework.boot.context.properties.bind.BindConverter.convert(BindConverter.java:88)
at org.springframework.boot.context.properties.bind.MapBinder.bindAggregate(MapBinder.java:64)
at org.springframework.boot.context.properties.bind.AggregateBinder.bind(AggregateBinder.java:56)
at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$3(Binder.java:414)
at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:571)
at org.springframework.boot.context.properties.bind.Binder$Context.access$100(Binder.java:512)
at org.springframework.boot.context.properties.bind.Binder.bindAggregate(Binder.java:414)
at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:375)
at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:319)
... 47 more
에러 코드
테스트 돌리는 중에서 에러가 발생하는데, 뭐가 문제인지 모르겠다..
'Framework > Spring' 카테고리의 다른 글
스프링 부트 Application 실행 시 MySQL root@localhost Access Denied 오류 해결 (0) | 2021.09.13 |
---|---|
스프링 부트 JPA 활용 - 웹 계층 개발 (0) | 2021.07.07 |
스프링 부트 JPA 활용 1 - 도메인 분석 설계 / 엔티티 클래스 개발 (0) | 2021.06.02 |
[스프링 입문] 스프링 빈과 의존 관계 (0) | 2021.05.25 |
스프링 부트와 JPA 활용1 - 프로젝트 환경 설정 (0) | 2021.05.19 |