Spring/스프링 MVC
[스프링 MVC 1편] 3 - (1) 웹 애플리케이션 요구사항
Poorm 푸름
2023. 11. 5. 06:36
* 스프링 입문은 Window로 스프링 MVC 1편은 Mac으로 진행합니다
* 진도 : 섹션3 - (1)
* : 자바 클래스명, : 코드, : 단축키
1. 회원 관리 웹 애플리케이션 요구사항 _ [스프링 입문] 3 - (1), (3) 편 참고
회원 정보
- 이름: username
- 나이: age
기능 요구사항
- 회원 저장
- 회원 목록 조회
▶ 웹 애플리케이션 계층 구조
1. 도메인
더보기
웹을 설계할때 도메인을 먼저 정하는게 중요하다
Domain
- 내가 개발하고자 하는 영역의 모델(객체)
- 온라인 쇼핑몰을 예를 든다면 주문 (핵심 기능), 회원, 결제, 배송, 리뷰로 도메인을 나눌 수 있다
이 도메인 중 또 하위 도메인으로 나눌 수 있다
Domain Model
- Entity
- 식별자를 가진다
- 식별자 이외의 데이터가 변경이 되어도 그 객체가 다른 객체가 되는것이 아니다
예) Member = member_id 식별자라면, Member에서 닉네임을 변경해도 Member 불변
- 식별자 이외의 데이터가 변경이 되어도 그 객체가 다른 객체가 되는것이 아니다
- 식별자를 가진다
- Value
- 식별자를 가지지 않고 값 그 자체
- 데이터가 변경되면 아예 다른 객체가 된다
- 따라서 value를 immutable로 구현하는게 좋다
- 이를 위해 값을 생성자를 통해서만 받고 setter를 구현하지 않는다
- 기존 객체를 변경하고 싶으면 아예 새로운 객체를 만든다
- 식별자를 가지지 않고 값 그 자체
2. 레포지토리
더보기
- Entity에 의해 생성된 DB에 접근하는 메서드(ex) findAll()) 들을 사용하기 위한 인터페이스
위에서 엔티티를 선언함으로써 데이터베이스 구조를 만들었다면 여기에 어떤 값을 넣거나, 넣어진 값을 조회하는 등의 CRUD(Create, Read, Update, Delete)를 정의- DB에 접근하는 객체(MyBatis 사용시에 DAO or Mapper 사용)
- Repository라고도 부름(JPA 사용시 Repository 사용)
- Service와 Controller를 연결
- DAO(Data Access Object)
사용하는 코드 함수 예시
- Optional
- null이 올 수 있는 값을 감싸는 Wrapper 클래스
- 참조하더라도 NPE가 발생하지 않도록 한다
- Value에 값을 저장하기 때문에 값이 null이더라도 바로 NPE가 발생 X
- NPE(NullPointerException)이란?
- 개발을 할 때 가장 많이 발생하는 예외
- NPE를 피하려면 null 여부를 검사해야 하는데, null 검사를 해야하는 변수가 많은 경우 코드가 복잡해지고 번거롭다 그래서 null 대신 초기값을 사용하길 권장하기도 한다
- @Repository
- @Component가 포함된 annotation이다
- 싱글톤 클래스 빈을 생성하는 어노테이션이다
- 해당 어노테이션은 "이 클래스를 정의했으니 빈(자바 객체(POJO))으로 등록해줘." 라는 뜻
- db에 저장을 하지 않고 메모리에 저장 (Map 사용)
- 실무에선 동시성 문제 때문에 공유되는 변수일때는 ConcurrnetHashMap을 사용
- public Member save(Member member)
- 파라미터로 member 객체를 받아서 그 객체에 id를 sequence를 1증가시키고 넣어주기
- store라는 Hashmap을 생성해서 실제로 메모리에 저장하는데 id와 객체를 각각넣는다
- new = 생성, put = 추가, 삭제 = remove(key) / clear()
- 멤버 객체를 반환
- public Optional<Member> findByName(String name)
- stream 이란?
- 데이터의 흐름
- 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다
- 람다를 이용해서 코드의 양을 줄이고 간결하게 표현 (배열과 컬렉션을 함수형으로 처리 가능)
- 병렬처리 가능 (하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행 = 스레드 이용)
- Lambda 란?
- 람다 함수는 함수형 프로그래밍 언어에서 사용되는 개념으로 익명 함수
- 람다식의 표현
- ( 파라미터 ) -> { 몸체 }
- filter 란?
- filter()는 인자로 함수를 받으며, 어떤 조건으로 Stream의 요소들을 필터링
- stream 이란?
- public List<Member> findAll()
- 모든 멤버를 반환
3. 컨트롤러
더보기
- 브라우저상의 웹 클라이언트의 요청 및 응답을 처리하는 레이어
- 서비스계층, 데이터 엑세스 계층에서 발생하는 Exception을 처리
- @Controller annotation을 사용하여 작성된 Controller 클래스
- Controller 클래스안에 @RequestBody를 사용해서 REST API로도 만들질 수 있다
사용하는 코드 함수 예시
- @Controller
- @Component가 포함된 annotation
- 싱글톤 클래스 빈을 생성하는 어노테이션
- 즉, 패키지 스캔 안에 이 어노테이션은 "이 클래스를 정의했으니 빈(자바 객체(POJO))으로 등록해줘." 라는 뜻
- private final MemberService memberService;
- 멤버 서비스 객체를 final로 선언.
- @Autowired
- Controller 이랑 Service를 연결 (Controller가 Service를 의존하는 관계(DI))
4. 서비스
더보기
- 로직 처리와 도메인 모델의 적합성 검증
- 프레젠테이션 계층과 데이터 엑세스 계층 사이를 연결하는 역할
두 계층이 직접적으로 통신하지 않게 한다 - Service 구현 클래스
- Controller 가 Service를 통해 회원가입과 데이터를 가져올 수 있게 된다 (컨트롤러가 서비스를 의존하는 관계)
- 트렌젝션 관리
- 예) (1) 판매처에 돈보내기, (2) 판매처에서 돈 받기 이 있다고 하면, (1)은 성공했지만 (2)가 실패를 하게되면 작업의 실행하기 전 상태로 돌리(rollback)는것을 하는것이 트렌젝션
- 트랜잭션 ACID
- Atomicity; 원자성: 트랜잭션 내의 작업들은 모두 성공 또는 모두 실패한다.
- Consistency; 일관성: 모든 트랜잭션은 일관성 있는 DB 상태를 유지한다. (ex: DB의 무결성 제약 조건 항상 만족)
- Isolation; 격리성: 동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않는다. (ex: 동시에 같은 데이터 수정 X)
- Durability; 지속성: 트랜잭션이 성공적으로 끝나면 그 결과는 항상 기록되어야 한다.
2. Domain 모델 만들기
[ src - java - hello - servlet - domain - member - Member ]
@Getter @Setter
public class Member {
private Long id;
private String username;
private int age;
public Member() {
}
public Member(String username, int age) {
this.username = username;
this.age = age;
}
}
- Getter, Setter 롬복 생성
- 필드 : id, username, age 추가
- 생성자 : 기본 생성자, username, age 갖는 생성자
3. Repository 저장소 만들기
[ src - java - hello - servlet - domain - member - MemberRepository ]
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
private static final MemberRepository instance = new MemberRepository();
public static MemberRepository getInstance() {
return instance;
}
private MemberRepository() {
}
- 실무에서 고려 사향 : 동시성 문제 (ConcurrentHashMap, AtomicLong 사용 고려)
- Map<key,value> → key = id, value = member 로 저장
- id 증가를 의미하는 sequence 생성
- 싱글톤 만들기
- 싱글톤 패턴은 객체를 단 하나만 생생해서 공유해야 하므로 생성자를 private 접근자로 막아둔다
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
public Member findById(Long id) {
return store.get(id);
}
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore() {
store.clear();
}
}
- save 만들기
- member.setId(++sequence)
- set 할 때 마다 sequence 값 하나씩 올려주기
(name은 모두 넘어온 상태 name은 내가 적는 값, id는 시스템이 정해주는 값으로서 id는 이 부분에서 설정)
- set 할 때 마다 sequence 값 하나씩 올려주기
- store.put(member.getId(), member)
- member 객체(Map)에 id 넣어주고 member 다시 store에 저장
- member.setId(++sequence)
- findById 만들기
- id로 회원 찾기
- findAll
- List
- option + enter_java.util
- return new ArrayList<>(store.values())
- 스토어에 있는 모든 값 ( = Member ) List에 넣기
- 이렇게 하면 arraylist 조작해도 store는 영향 받지 않는다
- List
- store.clear
- test에 쓸 메서드
- store 날리기
4. 회원 저장소 테스트 코드
[ src - test - java - hello - servlet - domain - member - MemberRepositoryTest ]
class MemberRepositoryTest { //test는 public 생략가능
//싱글톤이라 new 안써준다
MemberRepository memberRepository = MemberRepository.getInstance();
@AfterEach
void afterEach(){
memberRepository.clearStore();
}
- 테스트할 대상 불러오기
- @AfterEach : 테스트 메서드 실행 이후에 수행
- 테스트 순서를 올바르게 하기 위해서 test가 끝날 때마다 초기화 하는 메서드 사용
@Test // 저장 테스트
void save(){
//given
Member member = new Member("hello", 20);
//when
Member savedMember = memberRepository.save(member);
//then
Member findMember = memberRepository.findById(savedMember.getId());
assertThat(findMember).isEqualTo(savedMember);
}
- 멤버 만들기
- 리포지토리에 member 저장
- 저장했던 member에서 id 꺼내 찾기
- Assertions
- option + enter 로 assertj 설정
- 저장된 값과 찾아온 값이 같은지 확인
@Test // 모든거 조회 테스트
void findAll(){
//given
Member member1 = new Member("member1", 20);
Member member2 = new Member("member2", 30);
memberRepository.save(member1);
memberRepository.save(member2);
//when
List<Member> result = memberRepository.findAll();
//then
assertThat(result.size()).isEqualTo(2); //option+enter
assertThat(result).contains(member1,member2);
}
}
- 리포지토리에 member 1 , 2 저장
- memberRepository.findAll()
- option + command + v
- 검증단계
- Assertions
- option + enter 로 Add ~ static import 설정
- 사이즈 2개인지 확인
result 안에 멤버 데이터 있는지도 확인
[출처] 김영한 강사님 인프런 스프링 mvc1
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의
웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원
www.inflearn.com