스프링 | MVC 아키텍처

Salt·2023년 5월 31일
post-thumbnail

ScoreEntity

@Setter @Getter @ToString
@NoArgsConstructor @AllArgsConstructor

public class Score {

	private String name;
	private int korScore;
	private int engScore;
	private int mathScore;
	
	private int studentNumber;
	private int total;
	private int average;
}

ScoreController

  1. 일단 Controller 클래스를 빈 컨테이너에 만들자. @Controller
  2. score 경로로 들어오는 요청을 처리하겠다. @RequestMapping("score")
  3. repository 정보를 갖고오자. (중간에 DB 교체 없기 위해 final로 선언)
  4. final 생성자를 초기화해주자. @RequiredArgsConstructor
  5. ScoreRepository 타입객체 생성repository 변수(컨트롤러 내부의) 안에 주입하자. @Autowired (빈 컨테이너 상상)
@Controller
@RequestMapping("score")
@RequiredArgsConstructor
public class ScoreController {
	
	@Autowired
	private final ScoreRepository repository;

이제 컨트롤러 기능 메서드를 만들어보자.

1) 전체조회 + 등록폼 (화면구현)

2) 등록

3) 삭제

4) 상세조회 (화면구현)


@RequestMapping(value="**/list**", method=RequestMethod.**GET**)
public String list(Model model) {

**List<Score> scoreList = repository.findAll();**
model.addAttribute("scoreList", scoreList);
****
return "chap04/score-list";
}

http 경로 /list 로 지정, get 요청받기 @RequestMapping

list 메서드 매개변수로 Model 객체를 받는다. (뷰로 전달해야하기 때문)

repository의 findAll() 메서드 호출하여, scoreList 변수에 저장한다.
(보통 TDD코드에서 붙여넣는다.)

뷰단으로 보내기 (Model 객체에 “scoreList”라는 이름으로 scoreList 저장)

score-list 페이지로 넘기기 (score-list.jsp 에 전체성적테이블과 입력폼 작성)


@RequestMapping(value="/register", method= RequestMethod.POST)
public String register(Score score) {

**repository.save(score);**

return "redirect:/score/list";
}

http 경로 /register 로 지정, post 요청받기

register 메서드 매개변수로 Score 객체를 받는다. (Score에 등록해야되기 때문)

repository의 save(score) 메서드 호출

/score/list 페이지로 다시 넘기기


@RequestMapping(value="/remove", method=RequestMethod.POST)
public String remove(int studentNumber) {

**repository.deleteByStudentNumber(studentNumber);**

return "";
}

http 경로 /remove 지정, post 요청받기

remove 메서드 매개변수로 studentNumber 를 받는다.

repository의 deleteByStudentNumber(studentNumber) 메서드 호출

학생 번호에 해당하는 Score 객체를 삭제한다.

“” 페이지로 넘기기


@RequestMapping(value="/detail", method=RequestMethod.GET)
public String detail(Model model, int studentNumber) {

**Score score = repository.findByStudentNumber(studentNumber);**
model.addAttribute("Score", score);

return "";
}

http 경로 /detail 지정, get 요청받기

detail 메서드 매개변수로 Model 객체와 studentNumber 를 받는다. (조회, 뷰도 해야해서)

repository의 findByStudentNumber() 메서드 호출

학생 번호에 해당하는 객체를 찾아서 score 변수에 저장한다.

뷰단으로 보내기 (Model 객체에 “Score”라는 이름으로 score 저장)

“” 페이지로 넘기기


TDD

ScoreRepositoryTest (TDD)

기능 하나하나 별로 TEST코드도 하나하나 분리해서 만드는
테스트코드 먼저 돌려보고, 이거 위주로 구현하기 !

  1. 원본 클래스에 원하는 기능을 구현하지는 말고 선언만 해 둔다.

  2. 테스트 코드 내부에 원하는 기능대로 작성되었을 때 어떤 결과가 나올지 단언해둔다.

  3. 테스트가 통과될때까지, 원본 클래스를 수정하면서 계속 테스트 해본다.


대표적으로 GIVEN, WHEN, THEN 패턴으로 구성한다.
given : 테스트할 대상 데이터 정하기
when : 실제 메서드 실행 구간
then : 테스트 결과 ‘~이렇게 나올 것이다’ 단언하기


public class ScoreRepositoryTest {
		**ScoreRepository repository = new ScoreRepositoryImp();**

일단, 테스트 대상인 ScoreRepositoryImp 선언해준다.


이제 기능 메서드를 정해주자.

  1. 전체 성적 조회 잘 나오는지
  2. 해당 학생 있을 때, 조회 결과 잘 나오는지
  3. 해당 학생 없을 때, 조회 결과 잘 나오는지
  4. 저장하는 기능 잘 되는지
  5. 삭제하는 기능 잘 되는지
@Test
public void findAllTest() {
		// given

		// when
		List<Score> result = **repository.findAll();**

		// then
		System.out.println(result.size() == 3);
		assertEquals(3, result.size());
		
}

given : findAll 이라서 특별한 조건 없이 전체 데이터를 가져오는거라 딱히 데이터 선언 필요없음

when : repository의 findAll() 메서드 실행해서, Score맵 자료형에 result 변수 안에 저장한다.

then : result 내부에 3개의 Score 객체가 있을 것이다.


@Test
public void findByStudentNumberTest() {
		// given
		int studentNumber = 2;

		// when
		Score result = **repository.findByStudentNumber(studentNumber);**

		// then
		assertEquals(33, result.getKorScore());
		assertEquals("이부트", result.getName());

}

given : 2번 학생

when : repository의 findByStudentNumber() 메서드 실행해서, result 변수 안에 저장한다.

then : 2번 유저의 점수는 33점일 것이다. 2번 유저의 이름은 “이부트”일 것이다.


@Test
public void notFindByStudentNumberTest() {
		// given
		int studentNumber = 99;

		// when
		Score result = **repository.findByStudentNumber(studentNumber);**

		// then
		assertNull(result);

}

given : 99번 학생

when : repository의 findByStudentNumber() 메서드 실행해서, result 변수 안에 저장한다.

then : result 변수 안에 들어있는 건 null일 것이다.


@Test
public void saveTest() {
	// given
	Score score = new Score();
	score.setName("유정원");
	score.setKorScore(99);
	score.setMathScore(88);
	score.setKorScore(77);

	// when
	boolean boolResult = **repository.save(score);**
	List<Score> result = **repository.findAll();**

	// then
	assertEquals(4, result.size());
	assertTrue(boolResult);

}

given : Score 객체에 새로운 학생정보 저장

when : repository의 save() 메서드 실행해서, score를 boolResult변수 안에 저장한다.

then : 전체 데이터 개수가 4개가 될 것이다. boolResult에 저장 됐다.


@Test
public void deleteTest() {
		// given
		int studentNumber = 2;

		// when
		boolean boolResult = **repository.deleteByStudentNumber(studentNumber);**
		List<Score> scoreList = **repository.findAll();**
		Score score = **repository.findByStudentNumber(studentNumber);**

		// then
		assertNull(score);
		assertEquals(2, scoreList.size());
		assertTrue(boolResult);

}

given : 2번 학생

when : repository의 deleteByStudentNumber() 메서드 실행해서, boolResult변수에 저장한다.

repository의 findAll() 메서드 실행해서, ScoreList변수에 저장한다.

repository의 findByStudentNumber() 메서드 실행해서, score변수에 저장한다.

then : score 변수 안에는 null일 것이다, scoreList 전체 사이즈는 2개일 것이다. boolResult에 잘 삭제됐다.


ScoreRepository

구현체 추상화

public interface ScoreRepository {
	
	public List<Score> findAll();
	boolean save(Score score);
	boolean deleteByStudentNumber(int studentNumber);
	Score findByStudentNumber(int studentNumber);
	

}
  1. 전체 성적 조회 — Score객체를 List형식으로 전체성적 조회하기
  2. 성적 저장 — Score객체 저장하기, 성공실패여부 결과리턴
  3. 성적 삭제 — studentNumber 받아서 삭제하기, 성공실패여부 결과리턴
  4. 개별 성적 조회 — studentNumber 받아서 개별성적 조회하기

ScoreRepositoryImp

실제 데이터 적재해서 구현하는 곳, 위에 있는 메서드 구체적인 로직 짜기

@Repository
public class ScoreRepositoryImp implements ScoreRepository {
	
	**private static final Map<Integer, Score> scoreMap;
	private static int sequence;**

	static {
		scoreMap = new HashMap<>();
		Score stu1 = new Score("김자바", 100, 50, 70, ++sequence, 0, 0);
		Score stu1 = new Score("이부트", 33, 56, 0, ++sequence, 0, 0);
		Score stu1 = new Score("박디비", 88, 12, 54, ++sequence, 0, 0);

		scoreMap.put(stu1.getStudentNumber(), stu1);
		scoreMap.put(stu2.getStudentNumber(), stu2);
		scoreMap.put(stu3.getStudentNumber(), stu3);

	}
}

스프링 컨테이너 내부에 ScoreRepositoryImp 객체가 생성됨 @Repository

scoreMap 이라는 맵자료형 선언 key. studentNumber value. score객체

sequence 라는 학번에 쓸 번호 선언

static 영역을 부여해서 임시 DB 영역을 만든다.

해시맵형식으로 score 객체 저장

studentNumber도 객체에 각각 집어넣어주기


@Override
public List<Score> findAll() {
	**List<Score> resultList = new ArrayList<Score>();**
	**for(Score score : scoreMap.values()) {
			resultList.add(score);
}**
	return resultList;
}

List<> findAll() (여러 행을 가져와야하니까, List 형식으로 불러온다.)

반복문으로 resultList에 score객체를 돌면서 집어넣기 x3

resultList 리턴


@Override
public boolean save(Score score) {
	if(scoreMap.containsKey(score.getStudentNumber())) {
			return false;
	}
	
	**score.setStudentNumber(++sequence);**
	**scoreMap.put(score.getStudentNumber(), score);**
	
	return true;
}

boolean save(Score score) Score객체를 매개변수로 받는다. (Score에 저장해야하기 때문)

scoreMap에 score.studentNumber가 없으면,

score.studentNumber를 sequence로 설정한다.

scoreMap에 (score.studentNumber, score)(키, 밸류) 를 집어넣는다.

저장 성공! 리턴


@Override
public boolean deleteByStudentNumber(int studentNumber) {
	if(!scoreMap.containsKey(score.getStudentNumber())) {
			return false;
	}

	**scoreMap.remove(studentNumber);**
	return true;
}

boolean deleteByStudentNumber(int studentNumber) studentNumber 매개변수로 받는다.

scoreMap에 score.studentNumber가 있으면,

score.studentNumber를 scoreMap에서 삭제한다.

삭제 성공! 리턴


@Override
public Score findByStudentNumber(int studentNumber) {
		
	**return scoreMap.get(studentNumber);**
}

Score findByStudentNumber(int studentNumber) studentNumber 매개변수로 받는다.

(해당 studentNumber를 가지고 있는 Score 객체를 반환해라.

상세 내역을 알아야하니까, Score 객체 자체를 가지고 와야 함)

scoreMap의 studentNumber 리턴

0개의 댓글