본문 바로가기

국비교육/국비교육

day36 - 0915

VO 활용 2. 관리자 페이지에 재생수가 많은 순서로 Top 10 출력

- 2가지 방법이 있다

1. 재생수를 기준으로 내림차순 정렬한 전체 조회 결과 중 일부만 View에 표시

    (전체 조회 결과를 가져오기 때문에 성능이 떨어진다)

2. Top N Query를 사용

 

Top N Query를 사용하는 방법

MusicDao

public interface MusicDao {

	// 추상 메소드 - 음원 Top 10
	List<MusicDto> selectTopTen();
}

 

MusicDaoImpl

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;

	// 추상 메소드 오버라이딩 - 음원 Top 10
	@Override
	public List<MusicDto> selectTopTen() {
		String sql = "select * from ("
						+ "select TMP.*, rownum music_rate from ("
							+ "select * from music order by music_play desc" 
						+ ")TMP"
					+ ") where music_rate between 1 and 10";
		return jdbcTemplate.query(sql, mapper);
	}
}

 

AdminController

@Controller
@RequestMapping("/admin")
public class AdminController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 음원 Top 10 Mapping
	@GetMapping("/music/play")
	public String musicPlay(Model model) {
		// model에 음원 Top 10 조회 결과 첨부
		model.addAttribute("list", musicDao.selectTopTen());
		// 음원 현황 페이지(play.jsp)로 연결
		return "admin/music/play";
	}
}

 

 

VO 활용 2-1. 관리자 페이지에 재생수가 많은 순서로 Top N 출력

MusicDao

public interface MusicDao {

	// 추상 메소드 - 음원 Top N
	List<MusicDto> selectTopN(int limit);
}

 

MusicDaoImpl

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;

	// 추상 메소드 오버라이딩 - 음원 Top N
	@Override
	public List<MusicDto> selectTopN(int limit) {
		String sql = "select * from ("
						+ "select TMP.*, rownum music_rate from ("
							+ "select * from music order by music_play desc"
						+ ")TMP"
					+ ") where music_rate between 1 and ?";
		Object[] param = new Object[] {limit};
		return jdbcTemplate.query(sql, mapper, param);
	}
}

 

AdminController

@Controller
@RequestMapping("/admin")
public class AdminController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 음원 Top N Mapping
	@GetMapping("/music/play/{limit}")
	public String musicPlay(Model model, @PathVariable int limit) {
		// model에 음원 Top N 조회 결과 첨부
		model.addAttribute("list", musicDao.selectTopN(limit));
		// 음원 현황 페이지(play.jsp)로 연결
		return "admin/music/play";
	}
}

- @PathVariable 또는 @RequestParam으로 Top N 조회를 위한 매개변수 limit을 입력받을 수 있다

- @PathVariable을 사용할 때 Mapping 주소 끝에 /{경로변수}가 붙는다

 

VO 활용 2-2. 관리자 페이지에 재생수가 많은 순서로 Top N to N 출력

MusicDao

public interface MusicDao {

	// 추상 메소드 - 음원 Top N to N
	List<MusicDto> selectTopNtoN(int begin, int end);
}

 

MusicDaoImpl

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;

	// 추상 메소드 오버라이딩 - 음원 Top N to N
	@Override
	public List<MusicDto> selectTopNtoN(int begin, int end) {
		String sql = "select * from ("
						+ "select TMP.*, rownum music_rate from ("
							+ "select * from music order by music_play desc"
						+ ")TMP"
					+ ") where music_rate between ? and ?";
		Object[] param = new Object[] {begin, end};
		return jdbcTemplate.query(sql, mapper, param);
	}
}

 

AdminController

@Controller
@RequestMapping("/admin")
public class AdminController {

	// 의존성 주입	
	@Autowired
	private MusicDao musicDao;

	// 음원 Top N to N Mapping
	@GetMapping("/music/play2")
	public String musicPlay2(Model model, 
				@RequestParam(required = false, defaultValue = "1") int begin, 
				@RequestParam(required = false, defaultValue = "10") int end) {
		// model에 음원 Top N to N 조회 결과 첨부
		model.addAttribute("list", musicDao.selectTopNtoN(begin, end));
		// 음원 현황 페이지(play.jsp)로 연결
		return "admin/music/play";
	}
}

- @RequestParam의 필수 여부(required)와 입력하지 않았을 때의 기본값(defaultValue)를 설정할 수 있다

 

AdminController - 간단한 페이징 구현

@Controller
@RequestMapping("/admin")
public class AdminController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 음원 Top N to N Mapping - 페이징 테스트
	@GetMapping("/music/play3")
	public String musicPlay3(Model model, 
				@RequestParam(required = false, defaultValue = "1") int page, 
				@RequestParam(required = false, defaultValue = "10") int size) {
		// 페이지 번호(page)와 한 페이지당 표시 음원 수(size)에 대하여
		// 페이지의 마지막 음원 번호(end)는 페이지 번호(page) * 표시 음원 수(size)
		int end  = page * size;
		// 페이지의 처음 음원 번호(begin)은 페이지의 마지막 음원 번호(end) - [표시 음원 수(size) - 1]
		int begin = end - (size - 1);
		// model에 음원 Top N to N 조회 결과 첨부
		model.addAttribute("list", musicDao.selectTopNtoN(begin, end));
		// 음원 현황 페이지(play.jsp)로 연결
		return "admin/music/play";
	}
}

 

play.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
    
<jsp:include page = "/WEB-INF/views/template/adminHeader.jsp">
	<jsp:param name = "title" value = "인기 음원 Top ${list.size()}"/>
</jsp:include>

<h1>인기 음원 Top ${list.size()}</h1>

<a href = "/admin/music/play/3">Top 3</a>
<a href = "/admin/music/play/5">Top 5</a>
<a href = "/admin/music/play/10">Top 10</a>
<a href = "/admin/music/play/20">Top 20</a>

<table border = "1" width = "300">
	<thead>
		<tr>
			<th>순위</th>
			<th>음원명</th>
			<th>가수명</th>
			<th>재생수</th>
		</tr>
	</thead>
	<tbody align = "center">
		<c:forEach var = "musicDto" items = "${list}" varStatus="status">
			<tr>
				<td>${status.count}</td>
				<td>${musicDto.getMusicTitle()}</td>
				<td>${musicDto.getMusicArtist()}</td>
				<td>${musicDto.getMusicPlay()}</td>
			</tr>
		</c:forEach>
	</tbody>
</table>

<jsp:include page = "/WEB-INF/views/template/adminFooter.jsp"></jsp:include>

 

VO 활용 3. 관리자 페이지에 연도별 음원 발매 숫자 출력 (최신 연도순)

- 순위(rank), 년도(year), 해당 년도 음원 발매수(cnt)를 위한 VO 생성

 

MusicYearCountVO

public class MusicYearCountVO {

	// 필드
	private int rank;
	private int year;
	private int cnt;
	
	// 생성자
	public MusicYearCountVO() {
		super();
	}
	
	// getter & setter
	public int getRank() {
		return rank;
	}

	public void setRank(int rank) {
		this.rank = rank;
	}

	public int getYear() {
		return year;
	}

	public void setYear(int year) {
		this.year = year;
	}

	public int getCnt() {
		return cnt;
	}

	public void setCnt(int cnt) {
		this.cnt = cnt;
	}
}

 

MusicDao

public interface MusicDao {

	// 추상 메소드 - 연도별 발매수 + 순위
	List<MusicYearCountVO> releaseByYearWithRank();
}

 

MusicDaoImpl

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	// 추상 메소드 오버라이딩 - 연도별 발매수 + 순위
	@Override
	public List<MusicYearCountVO> releaseByYearWithRank() {
		String sql = "select TMP.*, rank() over(order by cnt desc) rank from ("
							+ "select "
								+ "extract(year from release_time) year, "
								+ "count(*) cnt "
							+ "from music "
							+ "group by extract(year from release_time) "
							+ "order by year desc "
						+ ")TMP";
		return jdbcTemplate.query(sql, countMapper);
	}
}

 

- SQL문에서 년도 추출 구문

1) to_char(release_title, 'yyyy')
2) extractor(year from release_title)

 

AdminController

@Controller
@RequestMapping("/admin")
public class AdminController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 음원 연도별 발매수 및 순위 Mapping
	@GetMapping("/music/release")
	public String musicRelease(Model model) {
		// model에 음원 연도별 발매수 + 순위 조회 결과 첨부
		model.addAttribute("list", musicDao.releaseByYearWithRank());
		// 음원 발매 현황 페이지(play.jsp)로 연결
		return "admin/music/release";
	}
}

 

JUnit

- Java용 단위 테스트 도구

- 단위 테스트(Unit Test) : 소스 코드의 특정 모듈(프로그램 내 하나의 기능)이 의도된 대로 정확히 작동하는지 검증

 

Test 생성

1) Test용 클래스 생성 후 클래스명 위에 @SpringBootTest 작성 - 통합 테스트 환경 설정

2) Test용 메소드를 작성한 후 메소드명 위에 @Test 작성 - 테스트 할 메소드임을 명시 (반환형은 반드시 void)

     - @Test가 여러 개일 경우 모든 Test가 성공해야 성공으로 표시된다 

3) 매번 Test용 메소드를 실행하기 전 실행해야 할 메소드명 위에 @BeforeEach 작성

    매번 Test용 메소드를 실행한 후 실행해야 할 메소드명 위에 @AfterEach 작성

 

** Assertions 클래스- Test의 결과를 주장(assert)하여 그 결과가 주장과 같으면 true, 다르면 false를 출력- Assertions 클래스 메소드의 일부

boolean assertEquals(자료형 expected, 자료형 actual) expected가 actual과 같으면 true를 출력
boolean assertNotEquals(자료형 expected, 자료형 actual) expected가 actual과 다르면 true를 출력
boolean assertNull(Object expected) expected가 null이면 true를 출력
boolean assertNotNull(Object expected) expected가 null이 아니면 true를 출력

 

ex) Pocketmon 입력(INSERT) 기능 테스트

@SpringBootTest
public class Test03 {

	// 의존성 주입
	@Autowired
	private PocketMonsterDao pocketMonsterDao;

	// 의존성 주입이 정상적으로 되었는지
	// - pocketMonsterDao가 null이 아니다
	@Test
	public void test01() {
		assertNotNull(pocketMonsterDao);
	}

	// 등록(INSERT) 실행
	@Test
	public void test02() {
		pocketMonsterDao.insert(60, "발챙이", "물");
	}
}

 

 

MockMvc 클래스

- 가짜 객체를 만들어 어플리케이션을 서버에 배포하지 않고도 Spring MVC의 동작을 재현할 수 있는 클래스

- 가짜의 HTTP 요청을 컨트롤러에 보내고 서버 내에서 컨트롤러를 실행하지 않고 컨트롤러의 동작을 테스트

- MockMvc 클래스 메소드의 일부

ResultActions perform(RequestBuilder requestBuilder) 요청을 수행하고 그 결과를 반환한다

 

MockMvcBuilders 클래스

- 주어진 WebApplcationContext를 매개변수로 하여 MockMvc의 인스턴스를 생성

- MockMvcBuilders 클래스 메소드의 일부

static DefaultMockMvcBuilder webAppContextSetup(WebApplicationContext context) MockMvc의 인스턴스 생성

 

WebApplicationContext 인터페이스

- DispatcherServlet이 직접 사용하는 Controller, Repository를 포함한 웹 관련 빈을 등록하는 데 사용

  (Test에서는 Controller를 등록하기 위해 사용하는 것 같다)

 

 

ex) 수동으로 환경 설정을 하여 Controller 테스트

@SpringBootTest
public class Test05 {

	// MockMvc의 인스턴스 생성
	private MockMvc mockMvc;
		
	// 의존성 주입
	@Autowired
	private WebApplicationContext ctx;
		
	@BeforeEach
	public void before() {
		// WebApplicationContext을 매개변수로 하여 MockMvc의 인스턴스 생성
		mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
	}
		
	@Test
	public void test() throws Exception {
		// Assertion 대신 MockMvc의 perform()의 결과를 반환하도록 한다
		mockMvc.perform(get("/pocketmon/insert"))	// get 요청 생성
				.andExpect(status().is(200))	// 상태가 200번대인 경우 true 반환
				.andDo(print())			// 그 결과를 출력(print)한다
				.andReturn();
	}
}

 

ex) 자동으로 환경 설정을 하여 Controller 테스트

@SpringBootTest
@AutoConfigureMockMvc	// MockMvc에 대한 자동 설정
public class Test06 {

	// 의존성 주입
	@Autowired
	private MockMvc mockMvc;
		
	@Test
	public void test() throws Exception {
		// Assertion 대신 MockMvc의 perform()의 결과를 반환하도록 한다
		mockMvc.perform(get("/pocketmon/insert"))		// get 요청 생성
				.andExpect(status().is2xxSuccessful())	// 상태가 2xx인 경우만 성공
				.andDo(print())				// 그 결과를 출력(print)한다
				.andReturn();
	}
}

 

Lombok

 

의존성 추가

1) 해당 project 우클릭 - spring - Add Starters 선택

 

2) lombok 검색 후 선택

 

3) pom.xml 선택

 

4) lombok이 추가되었는지 확인

 

주요 Annotation

@Getter getter 메소드 생성
@Setter setter 메소드 생성
@ToString toString 메소드 오버라이딩
@Data @Getter + @Setter + @ToString 
@NoArgsConstructor 기본 생성자 생성
@AllArgsConstructor 모든 필드를 매개변수로 하는 생성자 생성
@Builder Builder 패턴을 이용하기 위한 Annotation

- @NoArgsConstructor, @AllArgsConstructor, @Builder는 Builder 패턴을 사용하기 위해 쓰인다

 

생성자를 이용한 인스턴스 생성과 Builder 패턴을 이용한 인스턴스 생성 비교

@SpringBootTest
public class Test08 {

	@Test
	public void test() {
		// 기본 생성자를 이용한 인스턴스 생성
		Student s = new Student();
		s.setName("피카츄");
		s.setScore(50);
		
		// Builder를 이용한 인스턴스 생성
		Student a = Student.builder().name("피카츄").score(50).build();
	}
}

- 기본 생성자를 이용한 인스턴스 생성

1) 기본 생성자를 이용하여 인스턴스 생성

2) setter 메소드로 필드값을 설정

 

- Builder를 이용한 인스턴스 생성

- 클래스명.builder().필드1(세팅값).필드2(세팅값)...필드n(세팅값).build();

- 1줄의 코드로 인스턴스를 생성할 수 있다

 

'국비교육 > 국비교육' 카테고리의 다른 글

day38 - 0919  (0) 2022.09.20
day37 - 0916  (0) 2022.09.16
day35 - 0914  (0) 2022.09.14
day34 - 0913  (0) 2022.09.09
day33 - 0908  (0) 2022.09.08