
먼저 문제 원본은 아래와 같다.
문제에서 실제로 구현해야 하는 조건만 다시 정리하면 이렇다.
TeamDTO클래스를 구현한다.TeamDTO에는teamName과teamMember멤버변수가 필요하다.teamMember는TeamMemberVO객체들을 담는List타입으로 만든다.TeamDTO는Lombok을 사용해서getter,setter,toString()을 생성한다.TeamMemberVO클래스를 구현한다.TeamMemberVO에는name,nickName,food멤버변수가 필요하다.TeamMemberVO는Lombok을 사용해서getter, 전체 생성자,toString()을 생성한다.MyTeamController1클래스를 구현한다.- 컨트롤러 매핑명은
/myTeam1로 한다.- 응답 화면은
myTeamView.html에서 처리한다.- 컨트롤러 메서드는 1개만 구현한다.
TeamDTO객체는myteam이라는 이름으로 보관한다.- 클라이언트가 팀명 출력을 요청하면
TeamDTO객체에 팀명을 저장해서 화면에 전달한다.- 클라이언트가 팀원 정보 출력을 요청하면
TeamMemberVO객체를 3~5개 생성하고ArrayList에 저장한 뒤, 이 리스트를TeamDTO객체에 저장해서 화면에 전달한다.
이 문제는 단순히 팀명이나 팀원 이름을 화면에 출력하는 문제가 아니다.
DTO,VO,Controller,View가 서로 어떻게 연결되는지 확인하는 문제다.
먼저TeamMemberVO는 팀원 한 명의 정보를 담는 객체다.
한 명의 이름, 별명, 좋아하는 음식을 저장한다.
그 다음TeamDTO는 화면에 전달할 팀 전체 정보를 담는 객체다.
팀명만 담을 수도 있고, 여러 명의 팀원 정보를 리스트로 담을 수도 있다.
여기서 중요한 점은 팀원이 한 명이 아니라 여러 명이라는 점이다.
팀원 한 명은TeamMemberVO객체 하나로 만들 수 있지만, 팀원 여러 명은 객체 하나로 표현할 수 없다.
그래서 여러 개의TeamMemberVO객체를List에 담아야 한다.
컨트롤러는 사용자가 어떤 버튼을 눌렀는지 확인한다.
사용자가우리 팀명버튼을 누르면TeamDTO에 팀명만 저장한다.
사용자가우리 팀원버튼을 누르면TeamMemberVO객체 여러 개를 만들고, 이 객체들을ArrayList에 담은 뒤TeamDTO에 저장한다.
마지막으로myTeamView.html은 컨트롤러가 전달한myteam객체를 꺼내 화면에 출력한다.
팀명 데이터가 있으면 팀명을 출력하고, 팀원 리스트가 있으면 반복문으로 팀원 목록을 출력한다.
즉 이 실습의 핵심 흐름은 아래와 같다.
버튼 클릭 → type 요청 파라미터 전달 → Controller에서 type 값 확인 → TeamDTO 생성 → 팀명 또는 팀원 목록 저장 → ModelAndView에 myteam 이름으로 저장 → myTeamView.html에서 조건에 따라 출력
이번 실습은 객체 구조를 먼저 만들고, 그 객체를 컨트롤러에서 채운 뒤, 화면에서 꺼내 출력하는 순서로 진행했다.
1.TeamMemberVO클래스 생성
2.TeamMemberVO에name,nickName,food멤버변수 선언
3.Lombok으로getter, 전체 생성자,toString()생성
4.TeamDTO클래스 생성
5.TeamDTO에teamName,teamMember멤버변수 선언
6.teamMember를List<TeamMemberVO>타입으로 작성
7.Lombok으로getter,setter,toString()생성
8.DTO와VO의 역할 차이 정리
9.myTeamView.html에서 요청 버튼 만들기
10. 버튼의name과value로 요청 구분값 전달하기
11.MyTeamController1에서/myTeam1요청 처리하기
12.@RequestParam으로type요청 파라미터 받기
13.if문과equals()로 팀명 요청과 팀원 요청 구분하기
14. 팀명 요청이면TeamDTO에 팀명 저장하기
15. 팀원 요청이면TeamMemberVO객체 여러 개 생성하기
16. 생성한 팀원 객체들을ArrayList에 저장하기
17. 팀원 리스트를TeamDTO에 저장하기
18.ModelAndView에myteam이라는 이름으로TeamDTO저장하기
19.myTeamView.html에서 팀명 또는 팀원 목록 출력하기
이번 문제에서는
TeamDTO와TeamMemberVO가 같이 나온다.
이름이 비슷해서 처음에는 둘 다 데이터를 담는 객체처럼 보일 수 있다.
하지만 이 실습에서는 두 객체의 역할을 나누어 이해하는 것이 좋다.
TeamDTO는 화면으로 전달할 전체 데이터를 담는 객체이고,TeamMemberVO는 팀원 한 명의 정보를 담는 객체다.
먼저DTO는Data Transfer Object의 줄임말이다.
이름 그대로 데이터를 전달하기 위한 객체다.
즉 컨트롤러에서 만든 데이터를 화면으로 넘기거나, 화면에서 받은 데이터를 컨트롤러로 전달할 때 사용하는 객체라고 보면 된다.
이번 실습에서TeamDTO는 화면으로 전달할 팀 전체 정보를 담는다.
팀명만 요청한 경우에는teamName만 담고, 팀원 정보를 요청한 경우에는teamMember리스트를 담는다.
그래서TeamDTO는 요청 결과에 따라 담는 데이터가 달라질 수 있다.
반면VO는Value Object의 줄임말이다.
일반적으로는 하나의 값을 표현하는 객체, 또는 특정 정보 묶음을 값처럼 다루는 객체를 의미한다.
이번 실습에서TeamMemberVO는 팀원 한 명의 정보를 담는다.
팀원 한 명은 이름, 별명, 좋아하는 음식으로 표현된다.
그래서TeamMemberVO객체 하나는 팀원 한 명의 정보 묶음이라고 볼 수 있다.
즉 이번 실습 기준으로 정리하면 아래와 같다.
TeamDTO는 화면에 전달할 전체 결과 데이터를 담는 객체다.TeamMemberVO는 팀원 한 명의 정보를 담는 객체다.TeamDTO안에는 팀원 여러 명을 담기 위해List<TeamMemberVO>가 들어간다.이 관계를 구조로 생각하면 아래와 같다.
TeamDTO
└── teamName
└── teamMember = List<TeamMemberVO>
├── TeamMemberVO 1명
├── TeamMemberVO 1명
├── TeamMemberVO 1명
└── TeamMemberVO 1명
그래서TeamDTO와TeamMemberVO는 서로 따로 노는 객체가 아니다.
TeamDTO가 팀 전체 데이터를 화면으로 전달하고, 그 안에 들어 있는TeamMemberVO들이 각각 팀원 한 명씩을 표현하는 구조다.
정리하면DTO는 주로 데이터를 전달하는 큰 그릇이고,VO는 그 안에 들어가는 하나의 값 묶음이라고 이해하면 된다.
이번 실습에서는TeamDTO가 응답 전체를 담고,TeamMemberVO가 팀원 한 명의 정보를 담는다.
이제 풀이 순서에 맞춰 실제 구현 흐름을 정리한다.
이번 실습의 핵심은 객체를 따로따로 만드는 것이 아니라, 객체들이 어떤 관계로 연결되는지 이해하는 것이다.1. TeamMemberVO 클래스 만들기
TeamMemberVO는 팀원 한 명의 정보를 담는 객체다.
팀원 한 명에게 필요한 정보는 이름, 별명, 좋아하는 음식이다.
그래서 멤버변수는name,nickName,food로 구성했다.
이 객체는 값을 만든 뒤 수정하는 용도보다, 팀원 한 명의 정보를 한 번에 담아두는 용도에 가깝다.
그래서setter는 사용하지 않고, 전체 값을 받는 생성자로 값을 저장한다.
그리고 화면에서 값을 꺼낼 수 있도록getter를 사용한다.package com.example.springlab.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @Getter @ToString @AllArgsConstructor public class TeamMemberVO { private String name; private String nickName; private String food; }
@AllArgsConstructor는 모든 멤버변수를 매개변수로 받는 생성자를 자동으로 만들어준다.
따라서 아래처럼 팀원 객체를 만들 수 있다.new TeamMemberVO("이현석", "이언덕", "김치찌개")이 코드를 실행하면
name에는이현석,nickName에는이언덕,food에는김치찌개가 저장된다.
데이터 흐름
TeamMemberVO객체 하나는 팀원 한 명을 의미한다.
예를 들어 아래 코드는 이현석이라는 팀원 한 명의 정보를 객체 하나로 만든 것이다.TeamMemberVO member = new TeamMemberVO("이현석", "이언덕", "김치찌개");이 객체 안에는 세 개의 값이 들어 있다.
name → 이현석
nickName → 이언덕
food → 김치찌개
나중에Thymeleaf화면에서${member.name},${member.nickName},${member.food}라고 작성하면 내부적으로getter를 통해 이 값들을 꺼내 출력한다.
2. TeamDTO 클래스 만들기
TeamDTO는 화면으로 전달할 팀 전체 정보를 담는 객체다.
팀원 한 명을 담는TeamMemberVO와 역할이 다르다.
TeamDTO에는 팀명인teamName과 팀원 목록인teamMember가 들어간다.
여기서teamMember는 팀원 한 명이 아니라 여러 명의 팀원 정보를 담아야 하므로List<TeamMemberVO>타입으로 작성한다.package com.example.springlab.domain; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.util.List; @Getter @Setter @ToString public class TeamDTO { private String teamName; private List<TeamMemberVO> teamMember; }여기서
private List<TeamMemberVO> teamMember;는TeamMemberVO객체 여러 개를 담을 수 있는 팀원 목록 변수를 선언한 것이다.
TeamMemberVO하나가 팀원 한 명이라면,List<TeamMemberVO>는 팀원 여러 명을 담는 목록이다.
즉teamMember에는 팀원 이름만 따로따로 들어가는 것이 아니라, 이름, 별명, 좋아하는 음식을 가진TeamMemberVO객체들이 들어간다.
그래서 화면에서는 이 리스트를 반복하면서 팀원 한 명씩 꺼내 출력할 수 있다.
TeamDTO에는setter가 필요하다.
이유는 컨트롤러에서 빈TeamDTO객체를 먼저 만들고, 요청 종류에 따라 팀명만 넣거나 팀원 리스트를 넣기 때문이다.
예를 들어 팀명 요청이면 아래처럼 팀명만 저장한다.team.setTeamName("우주수석");팀원 요청이면 아래처럼 팀원 리스트를 저장한다.
team.setTeamMember(members);즉
TeamDTO는 요청 결과에 따라 값이 나중에 채워지는 객체이기 때문에setter를 사용한다.
왜 List를 사용하는가
팀원 정보는 한 명만 있는 것이 아니다.
이번 실습에서는 팀원 4명의 정보를 화면에 출력해야 한다.
팀원 한 명은TeamMemberVO객체 하나로 표현할 수 있다.
하지만 팀원 4명은TeamMemberVO객체 4개가 필요하다.
이 여러 개의 객체를 하나로 묶어서 관리하기 위해List를 사용한다.List<TeamMemberVO> members = new ArrayList<>(); members.add(new TeamMemberVO("이현석", "이언덕", "김치찌개")); members.add(new TeamMemberVO("김윤우", "유누", "마라탕")); members.add(new TeamMemberVO("강민주", "만두", "샤브샤브")); members.add(new TeamMemberVO("김민수", "라멘집아들", "라멘"));
List<TeamMemberVO>는TeamMemberVO객체만 담을 수 있는 목록이다.
즉 문자열을 그냥 여러 개 담는 것이 아니라, 팀원 한 명의 정보가 들어 있는 객체들을 순서대로 담는다.
이렇게 만든 리스트를TeamDTO에 저장하면 화면에서는myteam.teamMember를 통해 팀원 목록 전체를 사용할 수 있다.
정리하면 아래와 같다.
TeamMemberVO 1개 = 팀원 1명
List<TeamMemberVO> = 팀원 여러 명
TeamDTO = 팀명과 팀원 목록을 화면으로 전달하는 객체
3. myTeamView.html에서 요청 버튼 만들기
화면에는
우리 팀명버튼과우리 팀원버튼이 필요하다.
두 버튼은 같은 컨트롤러인/myTeam1로 요청을 보내지만, 어떤 버튼을 눌렀는지는 구분되어야 한다.
그래서 버튼의name은 둘 다type으로 맞추고,value만 다르게 작성했다.<form method="get" action="/myTeam1"> <button type="submit" name="type" value="teamName">우리 팀명</button> <button type="submit" name="type" value="teamMember">우리 팀원</button> </form>
우리 팀명버튼을 누르면 요청 주소는 아래처럼 만들어진다./myTeam1?type=teamName
우리 팀원버튼을 누르면 요청 주소는 아래처럼 만들어진다./myTeam1?type=teamMember즉 컨트롤러는
type값을 보고 사용자가 어떤 버튼을 눌렀는지 알 수 있다.
데이터 흐름
처음 화면에서는 버튼만 보인다.
사용자가우리 팀명버튼을 누르면type=teamName값이 서버로 전달된다.
사용자가우리 팀원버튼을 누르면type=teamMember값이 서버로 전달된다.
이 값은 컨트롤러에서@RequestParam으로 받는다.
버튼 클릭 → type 값 생성 → /myTeam1 요청 → Controller에서 type 값 받기
4. MyTeamController1에서 요청 처리하기
컨트롤러는
/myTeam1요청을 처리한다.
요청 파라미터type값을 받아서 팀명 요청인지 팀원 요청인지 구분한다.
이때required = false를 사용했다.
처음/myTeam1에 접속할 때는 아직 버튼을 누르지 않았기 때문에type값이 없을 수 있다.
required = false를 사용하면type값이 없어도 오류가 나지 않고 처음 화면을 보여줄 수 있다.@GetMapping("/myTeam1") public ModelAndView myTeam(@RequestParam(value = "type", required = false) String type) {컨트롤러 전체 코드는 아래와 같다.
package com.example.springlab.controller; import com.example.springlab.domain.TeamDTO; import com.example.springlab.domain.TeamMemberVO; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; import java.util.List; @Controller public class MyTeamController1 { @GetMapping("/myTeam1") public ModelAndView myTeam(@RequestParam(value = "type", required = false) String type) { ModelAndView mav = new ModelAndView(); TeamDTO team = new TeamDTO(); if ("teamName".equals(type)) { team.setTeamName("우주수석"); } else if ("teamMember".equals(type)) { team.setTeamName("우주수석"); List<TeamMemberVO> members = new ArrayList<>(); members.add(new TeamMemberVO("이현석", "이언덕", "김치찌개")); members.add(new TeamMemberVO("김윤우", "유누", "마라탕")); members.add(new TeamMemberVO("강민주", "만두", "샤브샤브")); members.add(new TeamMemberVO("김민수", "라멘집아들", "라멘")); team.setTeamMember(members); } mav.addObject("myteam", team); mav.setViewName("myTeamView"); return mav; } }처음에는
ModelAndView객체를 만든다.
ModelAndView는 화면에 전달할 데이터와 이동할 뷰 이름을 함께 담을 수 있는 객체다.
그 다음TeamDTO객체를 만든다.
이 객체는 최종적으로 화면에 전달할 팀 정보를 담는 역할을 한다.
여기서 팀원 요청일 때 중요한 코드가 아래 한 줄이다.List<TeamMemberVO> members = new ArrayList<>();이 코드는 팀원 여러 명을 담을 빈 목록을 준비하는 코드다.
앞에서TeamDTO에는private List<TeamMemberVO> teamMember;가 있었다.
이 코드는TeamDTO안에 팀원 목록을 저장할 수 있는 멤버변수를 선언한 것이다.
하지만 멤버변수 선언만으로는 컨트롤러 안에서 팀원 객체들을 바로 추가할 수 있는 실제 리스트가 만들어지는 것은 아니다.
그래서 컨트롤러에서는 팀원 객체들을 먼저 담아둘 실제 리스트 객체를 따로 만들어야 한다.
그 역할을 하는 코드가List<TeamMemberVO> members = new ArrayList<>();다.
왼쪽의List<TeamMemberVO> members는 변수 선언이다.
members라는 변수는TeamMemberVO객체만 여러 개 담는 목록으로 사용하겠다는 의미다.
오른쪽의new ArrayList<>()는 실제 저장 공간을 만드는 코드다.
ArrayList는 데이터를 순서대로 저장할 수 있는 리스트 객체다.
따라서 이 코드가 실행되어야members.add(...)로 팀원 객체들을 하나씩 넣을 수 있다.
즉 이 코드는 아래처럼 나누어 이해하면 된다.
List<TeamMemberVO>는 팀원 객체만 담는 목록 타입이다.members는 컨트롤러 안에서 사용할 팀원 목록 변수 이름이다.new ArrayList<>()는 실제로 팀원 객체들을 담을 빈 리스트를 만드는 코드다.그 다음 아래 코드처럼
add()를 사용해서 팀원 객체를 하나씩 넣는다.members.add(new TeamMemberVO("이현석", "이언덕", "김치찌개")); members.add(new TeamMemberVO("김윤우", "유누", "마라탕")); members.add(new TeamMemberVO("강민주", "만두", "샤브샤브")); members.add(new TeamMemberVO("김민수", "라멘집아들", "라멘"));첫 번째
add()에서는 이현석 팀원 객체가 들어간다.
두 번째add()에서는 김윤우 팀원 객체가 들어간다.
세 번째add()에서는 강민주 팀원 객체가 들어간다.
네 번째add()에서는 김민수 팀원 객체가 들어간다.
이 과정이 끝나면members리스트 안에는TeamMemberVO객체 4개가 들어 있다.
이 리스트를 아래 코드로TeamDTO에 저장한다.team.setTeamMember(members);즉
members는 컨트롤러 안에서 팀원 객체들을 임시로 모아두는 리스트다.
그리고team.setTeamMember(members)를 실행해야 그 리스트가TeamDTO안의teamMember에 저장된다.
마지막으로mav.addObject("myteam", team)은team객체를myteam이라는 이름으로 화면에 전달한다.
mav.setViewName("myTeamView")는 응답 화면으로myTeamView.html을 사용하겠다는 의미다.
if문에서 equals를 사용한 이유
type은 문자열이다.
문자열 값을 비교할 때는==보다equals()를 사용해야 한다.
==는 문자열의 내용이 같은지 비교하는 것이 아니라, 두 객체가 같은 객체인지 비교하는 쪽에 가깝다.
반면equals()는 문자열 안에 들어 있는 실제 글자 값이 같은지 비교한다.
그래서type값이teamName인지 확인할 때는 아래처럼 작성한다.if ("teamName".equals(type)) { team.setTeamName("우주수석"); }여기서
"teamName".equals(type)처럼 문자열 리터럴을 앞에 둔 이유도 중요하다.
처음/myTeam1에 접속하면type값이 없어서null일 수 있다.
만약 아래처럼 작성하면,if (type.equals("teamName")) { team.setTeamName("우주수석"); }
type이null일 때NullPointerException이 발생할 수 있다.
하지만"teamName".equals(type)처럼 작성하면type이null이어도 오류가 발생하지 않고, 그냥false가 된다.
그래서 요청 파라미터가 없을 수도 있는 상황에서는 이 방식이 더 안전하다.
정리하면 아래와 같다.
- 문자열 내용 비교는
equals()를 사용한다.type은 처음 접속 시null일 수 있다."teamName".equals(type)처럼 상수 문자열을 앞에 두면NullPointerException을 피할 수 있다.
5. 팀명 요청과 팀원 요청의 데이터 흐름
우리 팀명버튼을 누르면/myTeam1?type=teamName요청이 들어온다.
그러면type값은teamName이 된다.
이때 아래 조건이 참이 된다.if ("teamName".equals(type)) { team.setTeamName("우주수석"); }그래서
TeamDTO의teamName에우주수석이 저장된다.
이 요청에서는 팀원 목록이 필요 없기 때문에teamMember는 저장하지 않는다.
반대로우리 팀원버튼을 누르면/myTeam1?type=teamMember요청이 들어온다.
그러면 아래 조건이 참이 된다.else if ("teamMember".equals(type)) { team.setTeamName("우주수석"); List<TeamMemberVO> members = new ArrayList<>(); members.add(new TeamMemberVO("이현석", "이언덕", "김치찌개")); members.add(new TeamMemberVO("김윤우", "유누", "마라탕")); members.add(new TeamMemberVO("강민주", "만두", "샤브샤브")); members.add(new TeamMemberVO("김민수", "라멘집아들", "라멘")); team.setTeamMember(members); }이 경우에는 팀원 소개 제목에 팀명도 같이 사용해야 하므로
teamName도 저장한다.
그리고 팀원 4명의 정보를TeamMemberVO객체로 만든 뒤members리스트에 추가한다.
마지막으로team.setTeamMember(members)를 실행해서 팀원 리스트를TeamDTO에 저장한다.
즉 흐름은 아래와 같다.
우리 팀명 클릭 → type=teamName → teamName 저장 → myteam으로 전달 → 팀명 문장 출력
우리 팀원 클릭 → type=teamMember → teamName 저장 → TeamMemberVO 4개 생성 → members 리스트에 저장 → TeamDTO의 teamMember에 저장 → myteam으로 전달 → 팀원 목록 출력
6. myTeamView.html에서 결과 출력하기
myTeamView.html에서는 컨트롤러가 전달한myteam객체를 사용한다.
팀명 요청일 때는 팀명 문장만 출력하고, 팀원 요청일 때는 팀원 목록을 출력해야 한다.
전체 코드는 아래와 같다.<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>우리팀 소개</h1> <hr/> <form method="get" action="/myTeam1"> <button type="submit" name="type" value="teamName">우리 팀명</button> <button type="submit" name="type" value="teamMember">우리 팀원</button> </form> <h2 th:if="${myteam.teamName != null and myteam.teamMember == null}"> 우리 팀의 팀명은 <span th:text="${myteam.teamName}">4조</span>입니다. </h2> <div th:if="${myteam.teamMember != null}"> <h2> <span th:text="${myteam.teamName}">4조</span>팀의 팀원들을 소개합니다. </h2> <ul> <li th:each="member : ${myteam.teamMember}"> <span th:text="${member.name}"></span> <span th:text="${member.nickName}"></span> <span th:text="${member.food}"></span> </li> </ul> </div> </body> </html>팀명 문장은 아래 조건을 만족할 때만 출력된다.
<h2 th:if="${myteam.teamName != null and myteam.teamMember == null}">이 조건은
teamName은 있고,teamMember는 없을 때만 참이다.
즉우리 팀명버튼을 눌렀을 때만 팀명 문장이 출력된다.
팀원 목록은 아래 조건을 만족할 때만 출력된다.<div th:if="${myteam.teamMember != null}">이 조건은 팀원 리스트가 있을 때만 참이다.
즉우리 팀원버튼을 눌렀을 때만 팀원 목록 영역이 출력된다.
팀원 목록 출력에는th:each를 사용했다.<li th:each="member : ${myteam.teamMember}"> <span th:text="${member.name}"></span> <span th:text="${member.nickName}"></span> <span th:text="${member.food}"></span> </li>
myteam.teamMember는List<TeamMemberVO>다.
따라서 리스트 안에 있는TeamMemberVO객체를 하나씩 꺼내야 한다.
th:each="member : ${myteam.teamMember}"는 리스트에서 팀원 객체를 하나씩 꺼내member라는 이름으로 사용하겠다는 의미다.
그래서 반복문 안에서는${member.name},${member.nickName},${member.food}로 각 팀원의 정보를 출력할 수 있다.
7. 트러블 슈팅: 팀원 버튼을 눌렀을 때 팀명 문장까지 같이 출력되는 문제
처음에는 팀명 출력 조건을 아래처럼 작성했다.
<h2 th:if="${myteam.teamName != null}"> 우리 팀의 팀명은 <span th:text="${myteam.teamName}">4조</span>입니다. </h2>이 조건은
teamName값만 있으면 무조건 참이 된다.
그런데 팀원 목록 화면에서도 제목에 팀명을 사용해야 해서 컨트롤러에서teamName을 같이 저장했다.
그 결과우리 팀원버튼을 눌렀을 때도teamName이 존재하게 되었고, 팀명 문장까지 같이 출력되는 문제가 생겼다.
처음 의도한 결과는 아래와 같았다.
우리 팀명버튼 클릭 시 팀명 문장만 출력우리 팀원버튼 클릭 시 팀원 목록만 출력그래서 팀명 문장 조건을 더 정확하게 바꿨다.
<h2 th:if="${myteam.teamName != null and myteam.teamMember == null}">이 조건은
teamName은 있지만teamMember는 없을 때만 참이다.
즉 팀명만 요청한 경우에만 팀명 문장이 출력된다.
팀원 요청에서는teamName도 있고teamMember도 있다.
따라서myteam.teamMember == null조건이 거짓이 되기 때문에 팀명 문장은 출력되지 않는다.
대신 팀원 목록 영역만 출력된다.
이 문제를 통해th:if는 단순히 값이 있는지만 보는 것이 아니라, 화면에서 어떤 상황을 구분해야 하는지에 맞춰 조건을 정확하게 작성해야 한다는 점을 확인했다.
최종적으로 필요한 파일은
TeamMemberVO.java,TeamDTO.java,MyTeamController1.java,myTeamView.html이다.
앞에서 단계별로 나누어 확인한 코드를 실제 실행 가능한 흐름으로 다시 정리하면 아래와 같다.TeamMemberVO.java
package com.example.springlab.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @Getter @ToString @AllArgsConstructor public class TeamMemberVO { private String name; private String nickName; private String food; }
TeamMemberVO는 팀원 한 명의 정보를 담는 객체다.
@AllArgsConstructor가 생성자를 만들어주기 때문에 팀원 정보를 한 번에 넣어 객체를 생성할 수 있다.
TeamDTO.java
package com.example.springlab.domain; import lombok.Getter; import lombok.Setter; import lombok.ToString; import java.util.List; @Getter @Setter @ToString public class TeamDTO { private String teamName; private List<TeamMemberVO> teamMember; }
TeamDTO는 화면으로 전달할 팀 전체 정보를 담는 객체다.
teamName에는 팀명이 들어가고,teamMember에는 여러 명의 팀원 정보를 담은 리스트가 들어간다.
MyTeamController1.java
package com.example.springlab.controller; import com.example.springlab.domain.TeamDTO; import com.example.springlab.domain.TeamMemberVO; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; import java.util.List; @Controller public class MyTeamController1 { @GetMapping("/myTeam1") public ModelAndView myTeam(@RequestParam(value = "type", required = false) String type) { ModelAndView mav = new ModelAndView(); TeamDTO team = new TeamDTO(); if ("teamName".equals(type)) { team.setTeamName("우주수석"); } else if ("teamMember".equals(type)) { team.setTeamName("우주수석"); List<TeamMemberVO> members = new ArrayList<>(); members.add(new TeamMemberVO("이현석", "이언덕", "김치찌개")); members.add(new TeamMemberVO("김윤우", "유누", "마라탕")); members.add(new TeamMemberVO("강민주", "만두", "샤브샤브")); members.add(new TeamMemberVO("김민수", "라멘집아들", "라멘")); team.setTeamMember(members); } mav.addObject("myteam", team); mav.setViewName("myTeamView"); return mav; } }컨트롤러는
type값에 따라 팀명 요청과 팀원 요청을 구분한다.
구분한 결과를TeamDTO에 저장하고,myteam이라는 이름으로 화면에 전달한다.
myTeamView.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>우리팀 소개</h1> <hr/> <form method="get" action="/myTeam1"> <button type="submit" name="type" value="teamName">우리 팀명</button> <button type="submit" name="type" value="teamMember">우리 팀원</button> </form> <h2 th:if="${myteam.teamName != null and myteam.teamMember == null}"> 우리 팀의 팀명은 <span th:text="${myteam.teamName}">4조</span>입니다. </h2> <div th:if="${myteam.teamMember != null}"> <h2> <span th:text="${myteam.teamName}">4조</span>팀의 팀원들을 소개합니다. </h2> <ul> <li th:each="member : ${myteam.teamMember}"> <span th:text="${member.name}"></span> <span th:text="${member.nickName}"></span> <span th:text="${member.food}"></span> </li> </ul> </div> </body> </html>
myTeamView.html은 버튼을 통해 요청을 보내고, 컨트롤러에서 전달한myteam객체를 조건에 따라 출력한다.
팀명만 있을 때는 팀명 문장을 출력하고, 팀원 리스트가 있을 때는 반복문으로 팀원 목록을 출력한다.
처음
/myTeam1에 접속하면 아직type값이 없기 때문에 버튼만 보인다.
이때TeamDTO객체는 만들어지지만,teamName과teamMember에는 값이 들어가지 않는다.
우리 팀명버튼을 클릭하면/myTeam1?type=teamName요청이 전송된다.
컨트롤러는type값이teamName인 것을 확인하고,TeamDTO에 팀명우주수석을 저장한다.
그 결과 화면에는우리 팀의 팀명은 우주수석입니다.가 출력된다.
우리 팀원버튼을 클릭하면/myTeam1?type=teamMember요청이 전송된다.
컨트롤러는 팀명과 팀원 리스트를TeamDTO에 저장한다.
그 결과 화면에는우주수석팀의 팀원들을 소개합니다.문장과 함께 팀원 목록이 출력된다.
실행 결과 흐름은 아래처럼 정리할 수 있다.
- 처음 접속: 버튼만 출력
우리 팀명클릭: 팀명 문장 출력우리 팀원클릭: 팀원 소개 제목과 팀원 목록 출력이 실행 결과를 통해 요청 파라미터 값에 따라 서로 다른 데이터를 만들어 화면에 전달할 수 있다는 점을 확인했다.
이번 실습에서는
Spring MVC에서 요청 파라미터를 기준으로 다른 데이터를 만들고, 그 결과를 객체에 담아 화면으로 전달하는 흐름을 확인했다.
특히 여러 명의 팀원 정보를 다룰 때 왜List가 필요한지, 문자열 비교에서 왜equals()를 사용해야 하는지 정리할 수 있었다.
DTO는 데이터를 전달하기 위한 객체다.VO는 특정 값 묶음을 표현하는 객체다.- 이번 실습에서
TeamDTO는 팀 전체 응답 데이터를 화면으로 전달하는 객체다.- 이번 실습에서
TeamMemberVO는 팀원 한 명의 정보를 담는 객체다.- 팀원 한 명의 정보는
TeamMemberVO객체 하나로 표현할 수 있다.- 팀원 여러 명의 정보는
TeamMemberVO객체 여러 개가 필요하다.- 여러 객체를 하나로 묶어 관리하려면
List를 사용할 수 있다.List<TeamMemberVO>는TeamMemberVO객체만 담는 팀원 목록이다.ArrayList는 실제로 데이터를 순서대로 저장하는List구현체다.List<TeamMemberVO> members = new ArrayList<>();는 컨트롤러에서 팀원 객체들을 담을 빈 목록을 준비하는 코드다.private List<TeamMemberVO> teamMember;는TeamDTO안에 팀원 목록을 저장할 멤버변수를 선언한 것이다.team.setTeamMember(members)를 해야 컨트롤러에서 만든 팀원 목록이TeamDTO안에 저장된다.button의name과value를 사용하면 어떤 버튼을 눌렀는지 요청 파라미터로 전달할 수 있다.@RequestParam(value = "type", required = false)를 사용하면 요청 파라미터가 없어도 처음 화면을 보여줄 수 있다.- 문자열 비교에서는
==가 아니라equals()를 사용한다."teamName".equals(type)처럼 작성하면type이null이어도 오류가 나지 않는다.ModelAndView는 화면에 전달할 데이터와 이동할 화면 이름을 함께 담을 수 있다.mav.addObject("myteam", team)을 사용하면 화면에서myteam이라는 이름으로 객체를 사용할 수 있다.th:if를 사용하면 조건에 따라 특정 태그를 출력하거나 숨길 수 있다.th:each를 사용하면 리스트에 들어 있는 객체를 하나씩 꺼내 반복 출력할 수 있다.이번 실습의 핵심은 팀원 정보를 단순 문자열로 여러 개 흩어놓는 것이 아니라,
TeamMemberVO객체로 만들고, 그 객체들을List에 담은 뒤,TeamDTO를 통해 화면으로 전달하는 흐름이다.
이 흐름을 이해하면 컨트롤러에서 만든 여러 개의 데이터를 화면에서 반복 출력하는 구조를 더 쉽게 이해할 수 있다.