일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- JavaScript 내장객체
- 자바스크립트
- javaprogramming
- web
- javascript
- 프로그레시브웹앱
- progressive web app
- 서브쿼리
- mybatis
- 생활코딩
- HTML
- tdd
- CSS
- 오라클
- Oracle
- framework
- springaop
- PWA
- SpringMVC
- sqldeveloper
- 자바프로그래밍
- 프레임워크
- 국비지원
- 스프링
- maven
- js
- sql
- TIL
- TodayILearned
- 메이븐
- Today
- Total
1cm
자바 프로그래밍_Day_97_Framework : MyBatis 동적 SQL 본문
2022. 01. 04
페이징 처리, 동적 SQL 실습
동적 SQL 실습
> board-mapper.xml
<resultMap type="com.kh.mybatis.member.model.vo.Member" id="boardListResultMap">
<id property="no" column="NO"/>
<result property="title" column="TITLE"/>
<result property="writerId" column="ID"/>
<result property="readCount" column="READCOUNT"/>
<result property="originalFileName" column="ORIGINAL_FILENAME"/>
<result property="renamedFileName" column="RENAMED_FILENAME"/>
<result property="content" column="CONTENT"/>
<result property="createDate" column="CREATE_DATE"/>
<result property="modifyDate" column="MODIFY_DATE"/>
</resultMap>
mapper 내의 resultMap의 type에 풀 패키지 명을 적어주는 것은 비효율 적이므로 mybatis-config.xml -> <typeAliases>내에서 별칭을 부여해준다.
<!-- typeAliases : Mybatis에서 사용하는 데이터 타입에 별칭 부여 -->
<!-- VO/DTO 객체의 타입에 별칭을 선언하는 영역 -->
<typeAliases>
<typeAlias type="com.kh.mybatis.member.model.vo.Member" alias="Member"/>
<typeAlias type="com.kh.mybatis.board.model.vo.Board" alias="Board"/>
</typeAliases>
별칭을 부여해준 뒤 다시 board-mapper.xml로 돌아와서 resultMap의 type을 별칭으로 적어주면 자동으로 적용이 된다.
<mapper namespace="boardMapper">
<resultMap type="Board" id="boardListResultMap">
<id property="no" column="NO"/>
<result property="title" column="TITLE"/>
<result property="writerId" column="ID"/>
<result property="readCount" column="READCOUNT"/>
<result property="originalFileName" column="ORIGINAL_FILENAME"/>
<result property="renamedFileName" column="RENAMED_FILENAME"/>
<result property="content" column="CONTENT"/>
<result property="createDate" column="CREATE_DATE"/>
<result property="modifyDate" column="MODIFY_DATE"/>
</resultMap>
resultMap 내부 작성
id : 테이블에 저장, 조회된 pk값을 객체 특정 필드에 매핑해줄 때 사용.
id-property : Board 타입으로 만들어지는 객체의 필드명 (Board.java에서 가져와서 적어줌 - 아래 이미지)
id-column : 조회된 결과에서 pk에 해당하는 컬럼명
그리고 아래에 select - resultType에 resultMap의 id값을 적어주어 매핑이 되게 한다.
<select id="selectAll" resultType="boardListResultMap">
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
</select>
> BoardServiceTest.java
@Test
// Board 객체를 요소로 담고있는 list를 반환해주는 함수
public void findAllTest() {
List<Board> list = null;
// 각각 파라미터를 받는 것 외에는 Null 처리를 한다고 가정
// title을 받을 때 writer, content는 null로 출력
String writer = null;
String title = null;
String content = "테스트";
list = service.findAll(writer, title, content);
System.out.println(list);
assertThat(list).isNotNull();
}
기존에 getParameter로 파라미터 값을 불러왔다면,
밑의 코드처럼 (list = service.findAll(writer, title, content);로 값을 불러오게 함 -> 파라미터 타입도 알아서 넘어가긴 한다.
package com.kh.mybatis.board.model.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.kh.mybatis.board.model.dao.BoardDao;
import com.kh.mybatis.board.model.vo.Board;
import com.kh.mybatis.common.SqlSessionTemplate;
public class BoardService {
// Dao 객체에게 데이터 조회해달라고 요청
private BoardDao dao = new BoardDao();
public List<Board> findAll(String writer, String title, String content) {
// Board객체를 가지는 list를 만들겠다- 선언
List<Board> list = null;
SqlSession session = SqlSessionTemplate.getSession();
// dao에게서 리스트를 받을 것임
list = dao.findAll(session, writer, title, content);
session.close();
return list;
}
}
그리고 BoardService.java에 list = dao.findAll(session) 뒤에 writer, title, content를 추가해준 뒤,
에러 삭제를 위해 BoardDao.java - method에 파라미터 writer, title, content를 추가시켜줌
public class BoardDao {
public List<Board> findAll(SqlSession session, String writer, String title, String content) {
return session.selectList("boardMapper.selectAll");
}
}
board-mapper.xml select구문에 AND M.ID LIKE = '%' || #{writer} || '%' 추가시켜준다.
위의 방법은 한 개의 값을 넘겨주는 방법인데,
만약 여러 개의 값을 한번에 넘겨주고 싶을 경우 collection객체 중 map에 여러 값을 담아서 넘겨줄 수 있다.
BoardDao.java에 map객체 생성
public class BoardDao {
public List<Board> findAll(SqlSession session, String writer, String title, String content) {
// map으로 여러 개의 키 값을 담아줄 수 있다.
Map<String, String> map = new HashMap<>();
map.put("writer", writer);
map.put("title", title);
map.put("content", content);
return session.selectList("boardMapper.selectAll", map);
}
}
board-mapper.xml select구문에 if 구문을 추가시켜준다.
WHERE 구문 밑에 아래 if 구문들을 추가시켜준다.
각각의 값들이 Null이 아니면 각각의 쿼리문들이 추가된다.
<!-- 1. 다중 <if>를 활용한 검색 기능 구현 -->
<!-- 검색하는 내용만 포함되면 조회가능 -->
<if test="writer != null">
<!-- writer가 Null이 아니면 아래 쿼리문 추가 시키는 구문, null이면 동작하지 않음 -->
AND M.ID LIKE '%' || #{writer} || '%'
</if>
<if test="title != null">
<!-- title 값이 Null이 아니면 아래 쿼리문 추가 시키는 구문, null이면 동작하지 않음 -->
AND B.TITLE LIKE '%' || #{title} || '%'
</if>
<if test="content != null">
<!-- content 값이 Null이 아니면 아래 쿼리문 추가 시키는 구문, null이면 동작하지 않음 -->
AND B.CONTENT LIKE '%' || #{content} || '%'
</if>
board-mapper.xml -> select 구문에 parameterType을 추가시켜준다
-> 알아서 처리해주긴 하지만 오류가 날 수도 있을 확률이 크기 때문에 적어주는 것이 좋다.
<select id="selectAll" parameterType="map" resultType="boardListResultMap">
- <where>와 다중 <if>를 활용한 검색 기능 구현해보기
select문 안에 쿼리문 + <where> + 다중<if>구문
<select id="selectAll" parameterType="map" resultType="boardListResultMap">
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
<where>
<if test="writer != null">
M.ID LIKE '%' || #{writer} || '%'
</if>
<if test="title != null">
AND B.TITLE LIKE '%' || #{title} || '%'
</if>
<if test="content != null">
AND B.CONTENT LIKE '%' || #{content} || '%'
</if>
AND B.STATUS = 'Y'
</where>
</select>
첫 번째 조건 만족 시 해당 쿼리문 실행,
두 번째 조건 부터 만족 시 where 구문의 속성 덕분에 AND / OR 이 삭제 된다.
다 만족하지 않을 경우 AND B.STATUS='Y'가 바로 온다.
@Test
// Board 객체를 요소로 담고있는 list를 반환해주는 함수
public void findAllTest() {
List<Board> list = null;
// 각각 파라미터를 받는 것 외에는 Null 처리를 한다고 가정
// title을 받을 때 writer, content는 null로 출력
// 전부 null일 경우 다중<if>구문 에서 그다음에 있는 B.STATUS='Y'로 넘어가서 전체 데이터가 조회된다.
String writer = null;
String title = null;
String content = null;
list = service.findAll(writer, title, content);
System.out.println(list);
assertThat(list).isNotNull();
}
String content = null; 부분은 다시 String content = "테스트"; 로 변경해줬음
writer=""; 일 경우 전체 게시물 조회됨, 값이 만약 "xx"이런식으로 있을 경우 그 값만 들어있는 걸 조회해줌
<trim>과 다중<if>를 활용한 검색 기능 구현
<!--
3.<trim>과 다중<if>를 활용한 검색 기능 구현
-->
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="writer != null">
M.ID LIKE '%' || #{writer} || '%'
</if>
<if test="title != null">
AND B.TITLE LIKE '%' || #{title} || '%'
</if>
<if test="content != null">
AND B.CONTENT LIKE '%' || #{content} || '%'
</if>
AND B.STATUS = 'Y'
</trim>
각각의 쿼리문 중 만족하는 쿼리문의 시작 부분에 WHERE이 붙고 AND나 OR로 시작을 하는 경우 AND, OR을 삭제 후 쿼리문을 실행 시켜 주고, 만약 모든 쿼리문이 만족하지 않으면 WHERE 뒤에 AND B.STATUS='Y'가 오게 되는데, 이 때 AND는 자동으로 삭제된다.
<choose>, <when>, <otherwise>를 활용한 검색 기능 구현
만약 넘어가는 값이 writer, title, content 세 가지 다 들어오는 경우 다중 if문을 사용해주는 것이 효율적이다.
<choose> 구문 안에 <when> 구문을 넣어주면 되는데 여러 개가 와도 상관 없다.
첫 번째 when절 만족 시 1번 when절만 수행되고 나머지는 수행되지 않음, 그러나 첫 번째 when 절 만족하지 않을 시 그 다음 when절로 넘어가게 되고 나머지 when절은 수행되지 않는다.
고로 결론은 만족하는 when 구문 제외하고는 수행이 되지 않는다는.
만약 모든 when 절이 만족하지 않는 경우 포함되어야 할 쿼리가 있다면 <when>구문 뒤에 <otherwise> 구문을 추가시켜준다.
<!--
4.<choose>, <when>, <otherwise>를 활용한 검색 기능 구현
-->
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
<choose>
<when test="writer != null">
<!-- wrtier가 있으면 넘겨주는 값으로 해당하는 데이터만 조회되게 해준다. -->
AND M.ID LIKE '%' || #{writer} || '%'
</when>
<when test="title != null">
AND B.TITLE LIKE '%' || #{title} || '%'
</when>
<when test="content != null">
AND B.CONTENT LIKE '%' || #{content} || '%'
</when>
<!--
<otherwise>
위의 조건 중 하나도 만족하지 않는 경우 포함될 쿼리 작성
</otherwise>
-->
</choose>
choose 구문은 <when> 내에 있는 조건식이 하나이면, 다음 <when>구문은 탐색하지 않고 하나의 만족하는 쿼리문만 실행
다중 if문은 여러 개의 조건을 만족하게 하고 싶다! 싶을 때 사용. -> 각 <if>태그가 가지고 있는 쿼리문 여러 개 실행 가능
CsvSource 내의 null을 위와같이 입력해주면 문자열 그대로 인식하게 된다.
value와 nullValues를 활용해서 null값으로 들어갈 수 있게 해준다.
@ParameterizedTest
@CsvSource(
value = {
"is, null, null, 1",
"null, 22, null, 2",
"null, null, 테스트, 1",
"null, null, null, 157"},
nullValues = "null"
)
// Board 객체를 요소로 담고있는 list를 반환해주는 함수
public void findAllTest(String writer, String title, String content, int expected) {
List<Board> list = null;
list = service.findAll(writer, title, content);
// 각각 파라미터를 받는 것 외에는 Null 처리를 한다고 가정
// title을 받을 때 writer, content는 null로 출력
// 전부 null일 경우 다중<if>구문 에서 그다음에 있는 B.STATUS='Y'로 넘어가서 전체 데이터 조회된다.
// String writer = null;
// String title = null;
// String content = "테스트";
// findAllTest()의 파라미터 값으로 보내준 뒤 없애도 된다.
assertThat(list).isNotNull();
assertThat(list.size()).isPositive().isEqualTo(expected);
}
필터 관련
- SQL DEVELOPER에서 아래 쿼리문 WEB 내에서 실행시켜줌
BOARD 테이블에 들어간 것을 확인할 수 있다.
ex.
b1 : 식당에 대한 게시글
b2 : 숙박업체에 대한 게시글
b3 : 관광지에 대한 게시글
임의로 구분지어줌
체크박스 경우 name은 동일, value는 다르다.
// {"B1", "B3"}
// {"B1"}
String[] btype = request.getParameterValues("btype");
위 방식처럼 데이터를 받아서 조회하게끔 가정한다.
> BoardServiceTest.java
@Test
public void findAllTest() {
// 배열로 데이터 가져오기
// filters에는 B1, B2 값을 가지고 있다고 가정할 수 있음
String[] filters = new String[] {"B1", "B2"}; // request.getParameterValues("filter");
List<Board> list = null;
// 리스트 얻어오기 (from. service 객체)
list = service.findAll(filters);
assertThat(list).isNotNull();
}
findAll : ctrl + 1 -> dao 생성
해당하는 쿼리문이 없어서 테스트 진행 시 에러 발생함
-> board-mapper.xml에서 selectBoardListByFilters라는 id를 가지고 있는 select문 만들어주기
기존 쿼리문에 B.TYPE, AND B.TYPE IN('B2', 'B3'); 구문 추가시켜줌
<select id="selectBoardListByFilters">
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.TYPE,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
AND B.TYPE IN ('B2', 'B3')
</select>
매핑을 위해 Board.java에 type을 추가시켜준다. -> type 값을 조회했을 때 객체 매핑을 위함
그리고 조회된 결과를 resultMap의 매핑 정보를 보고 매핑해줄 수 있도록 id값을 적어준다.
그리고 type에 대한 매핑은 Board.java에서 자동으로 해주지만, result에도 명시적으로 추가를 시켜준다(유지보수 용이)
if test = "filters != null"을 array로 바꿔준다
-> list, 배열 타입 같은 경우 파라미터로 전달하면 내부적으로는 map 객체로 변환이 되어 넘어간다.
-> key : list / value : object
-> key : array / value : filter
-> 내부적으로 key값은 변수명이 되고, value는 해당하는 오브젝트로 map타입으로 저장이 된다.
만약 filters로 쓰고 싶다면? -> 내부적으로 map타입으로 변환이 되지 않게 BoardDao에서 Map객체 생성 후 filters라는 이름으로 넘겨준다.
BoardDao.java
public List<Board> findAll(SqlSession session, String[] filters) {
/*
* List 타입이나 Array 타입의 데이터를 파라미터로 전달하면 내부적으로는 Map으로 타입이 변환되어서 저장되기 때문에
* Mapper에서는 list나 array라는 이름으로 사용해야 한다.
*
* Dao.java
* session.selectList("boardMapper.selectBoardListByFilters", filters);
*
* Mapper.xml
* <if test="array != null">
* ...
* </if>
*
* 만약에 filters라는 이름을 Mapper에서 사용하고 싶으면 Map 객체에 담아서 파라미터로 전달한다.(아래 내용)
*/
Map<String, Object> map = new HashMap<>();
map.put("filters", filters);
return session.selectList("boardMapper.selectBoardListByFilters", map);
}
그리고 나서 foreach 구문 추가시켜준다.
<select id="selectBoardListByFilters" resultMap="boardListResultMap">
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.TYPE,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
<!-- filters가 null인지 확인 후 null이 아니면If내의 구문 실행 -->
<if test="filters != null">
AND B.TYPE IN
<foreach collection="filters" item="filter" open="(" separator="," close=")">
#{filter}
</foreach>
</if>
</select>
여러 개의 필터를 선택했을 때, 필터를 만족하는 애들만 조회할 수 있게끔 하는 쿼리문 작성
collection : 배열
item : 배열의 요소들 (filter 말고 다른 이름으로 적어도 됨)
현재는 값이 두개 있기 때문에 두번 반복, 반복할 때마다 필터의 값이 item에 들어가서 출력을 해줄 것임
- 특정 게시글 검색
board-mapper.xml에 <sql>로 중복되는 쿼리 제거해주기
<mapper namespace="boardMapper">
<sql id="boardListSql">
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.TYPE,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
</sql>
...
그 다음 <include refid="boardListSql" /> 을 기존 쿼리문 대신에 넣어준다.
중복되는 쿼리문이 사라져서 코드가 훨씬 짧아짐!
단점은 유지보수 시 가독성이 좀 떨어질 수도 있다.
foreach 태그의 활용 정리
<select id="selectBoardListByFilters" resultMap="boardListResultMap">
<include refid="boardListSql" />
<!-- filters가 null인지 확인 후 null이 아니면If내의 구문 실행 -->
<if test="filters != null">
<!--
AND B.TYPE IN ('B2', 'B3')
위 결과를 만들기 위해서 foreach 태그를 사용한다.
- collection : 파라미터로 넘어온 배열, 리스트를 지정한다.
- item : 배열, 리스트의 각 요소들의 값이 들어가는 변수이다.
- index : 반복 횟수 (0부터 시작한다.)
- open : 반복문 시작 전 출력할 문자열을 지정한다.
- close : 반복문 종료 전에 출력할 문자열을 지정한다.
- separator : 반복할 때마다 반복을 구분할 구분자를 지정한다.
-->
AND B.TYPE IN
<foreach collection="filters" item="filter" open="(" separator="," close=")">
#{filter}
</foreach>
</if>
</select>
페이징 처리해주기
클래스 생성 후 이전에 pageInfo 사용했던 거 복붙해준다.
public class PageInfo {
private int currentPage;
private int pageLimit;
private int listCount;
private int listLimit;
/**
* @param currentPage 현재 페이지
* @param pageLimit 한 페이지에 보이는 페이지의 수
* @param listCount 전체 리스트의 수
* @param listLimit 한 페이지에 표시될 리스트의 수
*/
public PageInfo(int currentPage, int pageLimit, int listCount, int listLimit) {
this.currentPage = currentPage;
this.pageLimit = pageLimit;
this.listCount = listCount;
this.listLimit = listLimit;
}
/**
* @return 전체 페이지 중 가장 마지막 페이지
*/
public int getMaxPage() {
/*
listCount = 100, listLimit = 10
100 / 10 = 10.0 => 10페이지
101 / 10 = 10.1 => 11페이지
103 / 10 = 10.3 => 11페이지
109 / 10 = 10.9 => 11페이지
110 / 10 = 11.0 => 11페이지
111 / 10 = 11.1 => 12페이지
*/
return (int) Math.ceil((double) this.listCount / this.listLimit);
}
/**
*
* @return 페이징 된 페이지 중 시작 페이지
*/
public int getStartPage() {
/*
< 1 2 3 4 5 6 7 8 9 10 >
< 11 12 13 14 15 16 17 18 19 20 >
< 21 22 23 24 25 26 27 28 29 30 >
1, 11, 21, 31, .... => (10 * n) + 1 (n >= 0)
1 ~ 10 : n = 0
11 ~ 20 : n = 1
21 ~ 30 : n = 2
31 ~ 40 : n = 3
....
n = (currentPage - 1) / pageLimit
(10 * ((currentPage - 1) / pageLimit)) + 1 (n >= 0)
*/
return (this.pageLimit * ((this.currentPage - 1) / this.pageLimit)) + 1;
}
/**
*
* @return 페이징 된 페이지 중 마지막 페이지
*/
public int getEndPage() {
// 10, 20, 30, 40, ....
int endPage = this.getStartPage() + this.pageLimit - 1;
return endPage > this.getMaxPage() ? this.getMaxPage() : endPage;
}
/**
*
* @return 현재 페이지
*/
public int getCurrentPage() {
return this.currentPage;
}
/**
*
* @return 이전 페이지
*/
public int getPrevPage() {
int prevPage = this.getCurrentPage() - 1;
return prevPage < 1 ? 1 : prevPage;
}
/**
*
* @return 다음 페이지
*/
public int getNextPage() {
int nextPage = this.getCurrentPage() + 1;
return nextPage > this.getMaxPage() ? this.getMaxPage() : nextPage;
}
/**
*
* @return 페이지의 시작 리스트
*/
public int getStartList() {
return (this.getCurrentPage() - 1) * this.listLimit + 1;
}
/**
*
* @return 페이지의 마지막 리스트
*/
public int getEndList() {
int endList = this.getStartList() + this.listLimit - 1;
return endList > this.listCount ? this.listCount : endList;
}
}
그 다음 패키지 생성
SqlSessionTemplate 패키지 명에 common.뒤에 template를 붙여주면 에러가 뜨는데
ctrl+1 눌러서 Move ~~ 선택해준다.
common밑에 template 폴더가 만들어지면서 SqlSessionTemplate가 이동한 것을 확인할 수 있다.
그 다음 MemberService, BoardService에서 에러들이 발생하게 되는데
맥 사용자 기준 command + shift + o(알파벳) 을 입력해주면 import문을 다시 만들어준다. (BoardService 기준)
MemberService에서는 수기로 수정해줬다
AppTest.java에서도 import 수정 처리 해주고 테스트 진행
게시글 갯수 조회
@Test
public void getBoardCountTest() {
int count = 0;
String[] filters = new String[] {"B2", "B3"};
// 현재 게시글의 갯수 가져오기
count = service.getBoardCount(filters);
// 게시글의 숫자는 양수이면서, 0보다 크다는 것이라고 가정
assertThat(count).isPositive().isGreaterThan(0);
}
getBoardCount에서 발생하는 에러 ctrl+1눌러서 해결
BoardService.java에서 getBoardCount 메소드 생성 후 임시로 코드 작성
import 구문 수정
기존 SqlSessionTemplate.getSession(); 으로 되어 있던 것 오른쪽처럼 수정
public int getBoardCount(String[] filters) {
// count라는 값을 반환
int count = 0;
SqlSession session = getSession();
// dao에서 게시글 갯수 구해오기
count = dao.getBoardCount(session, filters);
// 조회되는 값이 있으면 session close
session.close();
return count;
}
BoardService.java -> getBoardCount에서 게시글 갯수 구해와서 조회할 수 있게 코드 작성 후, 에러 발생하는 부분 추가 메소드 생성
테스트를 돌리면 Dao에서 0을 리턴하고 있어서 return 1로 변경 후 테스트 진행하면 통과하는 것을 확인할 수 있음
리턴 값 변경 해주기
String[] 대신 List<String>으로 변경해주면 BoardService.java쪽에서 에러가 발생하게 되는데,
이 경우 BoardService 메소드로 넘어와서 filters를 list로 변경해주면, list로 나오는 결과값을 Dao에게 전달해준다.
테스트 진행 시 에러 발생하게 되는데, 쿼리문이 없어서 그런것 -> 쿼리문 생성 (board-mapper.xml)
board-mapper.xml로 넘어와서 selectBoardListByFilters의 파라미터 타입을 Map으로 설정 후 새로운 select문 생성
<select id="getBoardCountByFilters" parameterType="map" resultType="_int">
SELECT COUNT(*)
FROM BOARD
WHERE STATUS = 'Y'
AND TYPE IN ('B2', 'B3')
<if test="list != null">
AND B.TYPE IN
<foreach collection="filters" item="filter" open="(" separator="," close=")">
#{filter}
</foreach>
</if>
</select>
전체 실습 코드
> Board-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Mapper 설정 파일임을 선언 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="boardMapper">
<sql id="boardListSql">
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.TYPE,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
</sql>
<resultMap type="Board" id="boardListResultMap">
<id property="no" column="NO"/>
<result property="title" column="TITLE"/>
<result property="writerId" column="ID"/>
<result property="readCount" column="READCOUNT"/>
<result property="originalFileName" column="ORIGINAL_FILENAME"/>
<result property="renamedFileName" column="RENAMED_FILENAME"/>
<result property="content" column="CONTENT"/>
<result property="type" column="TYPE"/>
<result property="createDate" column="CREATE_DATE"/>
<result property="modifyDate" column="MODIFY_DATE"/>
</resultMap>
<select id="selectAll" parameterType="map" resultType="boardListResultMap">
<!-- 1. 다중 <if>를 활용한 검색 기능 구현
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
WHERE B.STATUS = 'Y'
<if test="writer != null">
<!-- writer가 Null이 아니면 아래 쿼리문 추가 시키는 구문, null이면 동작하지 않음 -->
<!-- 검색하는 내용만 포함되면 조회가능 -->
AND M.ID LIKE '%' || #{writer} || '%'
</if>
<if test="title != null">
<!-- title 값이 Null이 아니면 아래 쿼리문 추가 시키는 구문, null이면 동작하지 않음 -->
AND B.TITLE LIKE '%' || #{title} || '%'
</if>
<if test="content != null">
<!-- content 값이 Null이 아니면 아래 쿼리문 추가 시키는 구문, null이면 동작하지 않음 -->
AND B.CONTENT LIKE '%' || #{content} || '%'
</if>
-->
<!--
SELECT B.NO,
B.TITLE,
M.ID,
B.READCOUNT,
B.ORIGINAL_FILENAME,
B.RENAMED_FILENAME,
B.CONTENT,
B.CREATE_DATE,
B.MODIFY_DATE
FROM BOARD B
JOIN MEMBER M ON(B.WRITER_NO = M.NO)
2. <where>와 다중 <if>를 활용한 검색 기능 구현
<where>
<if test="writer != null">
M.ID LIKE '%' || #{writer} || '%'
</if>
<if test="title != null">
AND B.TITLE LIKE '%' || #{title} || '%'
</if>
<if test="content != null">
AND B.CONTENT LIKE '%' || #{content} || '%'
</if>
AND B.STATUS = 'Y'
</where>
-->
<!--
3.<trim>과 다중<if>를 활용한 검색 기능 구현
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="writer != null">
M.ID LIKE '%' || #{writer} || '%'
</if>
<if test="title != null">
AND B.TITLE LIKE '%' || #{title} || '%'
</if>
<if test="content != null">
AND B.CONTENT LIKE '%' || #{content} || '%'
</if>
AND B.STATUS = 'Y'
</trim>
-->
<!--
4.<choose>, <when>, <otherwise>를 활용한 검색 기능 구현
-->
<include refid="boardListSql" />
<choose>
<when test="writer != null">
<!-- wrtier가 있으면 넘겨주는 값으로 해당하는 데이터만 조회되게 해준다. -->
AND M.ID LIKE '%' || #{writer} || '%'
</when>
<when test="title != null">
AND B.TITLE LIKE '%' || #{title} || '%'
</when>
<when test="content != null">
AND B.CONTENT LIKE '%' || #{content} || '%'
</when>
<!--
<otherwise>
위의 조건 중 하나도 만족하지 않는 경우 포함될 쿼리 작성
</otherwise>
-->
</choose>
</select>
<select id="selectBoardListByFilters" parameterType="map" resultMap="boardListResultMap">
<include refid="boardListSql" />
<!-- filters가 null인지 확인 후 null이 아니면If내의 구문 실행 -->
<if test="filters != null">
<!--
AND B.TYPE IN ('B2', 'B3')
위 결과를 만들기 위해서 foreach 태그를 사용한다.
- collection : 파라미터로 넘어온 배열, 리스트를 지정한다.
- item : 배열, 리스트의 각 요소들의 값이 들어가는 변수이다.
- index : 반복 횟수 (0부터 시작한다.)
- open : 반복문 시작 전 출력할 문자열을 지정한다.
- close : 반복문 종료 전에 출력할 문자열을 지정한다.
- separator : 반복할 때마다 반복을 구분할 구분자를 지정한다.
-->
AND B.TYPE IN
<foreach collection="filters" item="filter" open="(" separator="," close=")">
#{filter}
</foreach>
</if>
</select>
<select id="getBoardCountByFilters" parameterType="map" resultType="_int">
SELECT COUNT(*)
FROM BOARD
WHERE STATUS = 'Y'
AND TYPE IN ('B2', 'B3')
<if test="list != null">
AND B.TYPE IN
<foreach collection="filters" item="filter" open="(" separator="," close=")">
#{filter}
</foreach>
</if>
</select>
</mapper>
> BoardServiceTest.java
package com.kh.mybatis.board.model.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import com.kh.mybatis.board.model.vo.Board;
class BoardServiceTest {
private BoardService service;
@BeforeEach
public void setUp() throws Exception {
// @BeforeEach : 아래 테스트 케이스들 작성 시 한번씩 수행
// 그럴때마다 새로운 service 객체를 생성 후 넘겨주기
service = new BoardService();
}
@Test
@Disabled
public void nothing() {
}
@Test
@Disabled
public void create() {
// 정상적으로 setUp()에서 객체가 만들어져서 대입이 됐는지 확인 후 @Disabled
assertThat(service).isNotNull();
}
@ParameterizedTest
@CsvSource(
value = {
"is, null, null, 1",
"null, 22, null, 2",
"null, null, 테스트, 1",
"null, null, null, 157"},
nullValues = "null"
)
// Board 객체를 요소로 담고있는 list를 반환해주는 함수
public void findAllTest(String writer, String title, String content, int expected) {
List<Board> list = null;
list = service.findAll(writer, title, content);
// 각각 파라미터를 받는 것 외에는 Null 처리를 한다고 가정
// title을 받을 때 writer, content는 null로 출력
// 전부 null일 경우 다중<if>구문 에서 그다음에 있는 B.STATUS='Y'로 넘어가서 전체 데이터 조회된다.
// String writer = null;
// String title = null;
// String content = "테스트";
// findAllTest()의 파라미터 값으로 보내준 뒤 없애도 된다.
assertThat(list).isNotNull();
assertThat(list.size()).isPositive().isEqualTo(expected);
}
@Test
public void getBoardCountTest() {
int count = 0;
String[] filters = new String[] {"B2", "B3"};
// 현재 게시글의 갯수 가져오기
count = service.getBoardCount(filters);
// 게시글의 숫자는 양수이면서, 0보다 크다는 것이라고 가정
assertThat(count).isPositive().isGreaterThan(0);
}
@Test
public void findAllTest() {
// 배열로 데이터 가져오기
// filters에는 B2, B3 값을 가지고 있다고 가정할 수 있음
String[] filters = new String[] {"B2", "B3"}; // request.getParameterValues("filter");
List<Board> list = null;
// 리스트 얻어오기 (from. service 객체)
list = service.findAll(filters);
//
System.out.println(list);
assertThat(list).isNotNull();
}
}
> BoardDao.java
package com.kh.mybatis.board.model.dao;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import com.kh.mybatis.board.model.vo.Board;
public class BoardDao {
public int getBoardCount(SqlSession session, List<String> filters) {
return session.selectOne("boardMapper.getBoardCountByFilters", filters);
}
public List<Board> findAll(SqlSession session, String writer, String title, String content) {
// map으로 여러 개의 키 값을 담아줄 수 있다.
Map<String, String> map = new HashMap<>();
map.put("writer", writer);
map.put("title", title);
map.put("content", content);
return session.selectList("boardMapper.selectAll", map);
}
public List<Board> findAll(SqlSession session, String[] filters) {
/*
* List 타입이나 Array 타입의 데이터를 파라미터로 전달하면 내부적으로는 Map으로 타입이 변환되어서 저장되기 때문에
* Mapper에서는 list(또는 collection)나 array라는 이름으로 사용해야 한다.
*
* Dao.java
* session.selectList("boardMapper.selectBoardListByFilters", filters);
*
* Mapper.xml
* <if test="array != null">
* ...
* </if>
*
* 만약에 filters라는 이름을 Mapper에서 사용하고 싶으면 Map 객체에 담아서 파라미터로 전달한다.(아래 내용)
*/
Map<String, Object> map = new HashMap<>();
map.put("filters", filters);
return session.selectList("boardMapper.selectBoardListByFilters", map);
}
}
> BoardService.java
package com.kh.mybatis.board.model.service;
import java.util.Arrays;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.kh.mybatis.board.model.dao.BoardDao;
import com.kh.mybatis.board.model.vo.Board;
import static com.kh.mybatis.common.template.SqlSessionTemplate.getSession;
public class BoardService {
// Dao 객체에게 데이터 조회해달라고 요청
private BoardDao dao = new BoardDao();
public int getBoardCount(String[] filters) {
// count라는 값을 반환
int count = 0;
SqlSession session = getSession();
// dao에서 게시글 갯수 구해오기
count = dao.getBoardCount(session, Arrays.asList(filters));
// 조회되는 값이 있으면 session close
session.close();
return count;
}
public List<Board> findAll(String writer, String title, String content) {
// Board객체를 가지는 list를 만들겠다- 선언
List<Board> list = null;
SqlSession session = getSession();
// dao에게서 리스트를 받을 것임
list = dao.findAll(session, writer, title, content);
session.close();
return list;
}
public List<Board> findAll(String[] filters) {
List<Board> list = null;
SqlSession session = getSession();
// dao에게 findAll 요청 하면서 매개값 session, filter 넘겨줌
list = dao.findAll(session, filters);
return list;
}
}
> SqlSessionTemplate.java
package com.kh.mybatis.common.template;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class SqlSessionTemplate {
public static SqlSession getSession() {
InputStream is = null;
SqlSessionFactoryBuilder builder = null;
SqlSessionFactory factory = null;
SqlSession session = null;
try {
// Resources는 Mybatis에서 제공하는 유틸 클래스로
// 클래스 패스로부터 자원(Resource)를 쉽게 읽어오는 메소드를 제공한다.
is = Resources.getResourceAsStream("mybatis-config.xml");
builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
// factory = builder.build(is, "kh");
// true : 오토커밋 활성, false : 오토커밋 비활성
session = factory.openSession(false);
} catch (IOException e) {
e.printStackTrace();
}
return session;
}
}
> Board.java
package com.kh.mybatis.board.model.vo;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Board {
private int no;
private int rowNum;
private int writerNo;
private String writerId;
private String title;
private String content;
private String type;
private String originalFileName;
private String renamedFileName;
private int readCount;
private String status;
// private List<Reply> replies;
private Date createDate;
private Date modifyDate;
}
> PageInfo.java
package com.kh.mybatis.common.util;
public class PageInfo {
private int currentPage;
private int pageLimit;
private int listCount;
private int listLimit;
/**
* @param currentPage 현재 페이지
* @param pageLimit 한 페이지에 보이는 페이지의 수
* @param listCount 전체 리스트의 수
* @param listLimit 한 페이지에 표시될 리스트의 수
*/
public PageInfo(int currentPage, int pageLimit, int listCount, int listLimit) {
this.currentPage = currentPage;
this.pageLimit = pageLimit;
this.listCount = listCount;
this.listLimit = listLimit;
}
/**
* @return 전체 페이지 중 가장 마지막 페이지
*/
public int getMaxPage() {
/*
listCount = 100, listLimit = 10
100 / 10 = 10.0 => 10페이지
101 / 10 = 10.1 => 11페이지
103 / 10 = 10.3 => 11페이지
109 / 10 = 10.9 => 11페이지
110 / 10 = 11.0 => 11페이지
111 / 10 = 11.1 => 12페이지
*/
return (int) Math.ceil((double) this.listCount / this.listLimit);
}
/**
*
* @return 페이징 된 페이지 중 시작 페이지
*/
public int getStartPage() {
/*
< 1 2 3 4 5 6 7 8 9 10 >
< 11 12 13 14 15 16 17 18 19 20 >
< 21 22 23 24 25 26 27 28 29 30 >
1, 11, 21, 31, .... => (10 * n) + 1 (n >= 0)
1 ~ 10 : n = 0
11 ~ 20 : n = 1
21 ~ 30 : n = 2
31 ~ 40 : n = 3
....
n = (currentPage - 1) / pageLimit
(10 * ((currentPage - 1) / pageLimit)) + 1 (n >= 0)
*/
return (this.pageLimit * ((this.currentPage - 1) / this.pageLimit)) + 1;
}
/**
*
* @return 페이징 된 페이지 중 마지막 페이지
*/
public int getEndPage() {
// 10, 20, 30, 40, ....
int endPage = this.getStartPage() + this.pageLimit - 1;
return endPage > this.getMaxPage() ? this.getMaxPage() : endPage;
}
/**
*
* @return 현재 페이지
*/
public int getCurrentPage() {
return this.currentPage;
}
/**
*
* @return 이전 페이지
*/
public int getPrevPage() {
int prevPage = this.getCurrentPage() - 1;
return prevPage < 1 ? 1 : prevPage;
}
/**
*
* @return 다음 페이지
*/
public int getNextPage() {
int nextPage = this.getCurrentPage() + 1;
return nextPage > this.getMaxPage() ? this.getMaxPage() : nextPage;
}
/**
*
* @return 페이지의 시작 리스트
*/
public int getStartList() {
return (this.getCurrentPage() - 1) * this.listLimit + 1;
}
/**
*
* @return 페이지의 마지막 리스트
*/
public int getEndList() {
int endList = this.getStartList() + this.listLimit - 1;
return endList > this.listCount ? this.listCount : endList;
}
}
'국비지원_Java > Java Programming_2' 카테고리의 다른 글
자바 프로그래밍_Day_99_MyBatis 게시글 등록 / 수정 / 삭제 (0) | 2022.01.16 |
---|---|
자바 프로그래밍_Day_98_Framework : MyBatis 동적 쿼리 / 게시글 조회 (0) | 2022.01.12 |
자바 프로그래밍_Day_96_Framework : MyBatis / 동적 쿼리 (0) | 2022.01.06 |
자바 프로그래밍_Day_95_Framework:MyBatis (0) | 2022.01.04 |
자바 프로그래밍_Day_94_Framework:MyBatis/TDD (0) | 2022.01.02 |