본문 바로가기

Programming/Framework

DAY 139. TDD 방식으로 게시판 필터 기능 만들기

 

웹 페이지에서 흔하게 볼 수 있는 형식 중 하나인 필터 기능을 TDD 방식으로 구현해보자.

체크되는 필터에 해당하는 내용만 게시판 글로 볼 수 있는 기능이다.

 

1. 기존 DB에 TYPE 컬럼 추가

ALTER TABLE BOARD ADD TYPE VARCHAR2(2) DEFAULT 'B1';


-- 위 쿼리문을 지우는 쿼리문
ALTER TABLE BOARD DROP COLUMN TYPE;

 

- 데이터 베이스에 BOARD 테이블이 이미 생성되어 있는 경우, 필터에 사용할 컬럼을 추가하자.

- 데이터 베이스 준비(구분할 수 있도록 적정량의 TYPE 컬럼 값을 B1, B2, B3, ...여러 개 준비)

 

 

체크박스를 사용한다고 가정, 체크 박스 필터에 걸리는 내용만 조회하도록 한다.

이 때, <checkbox name="XXX""> 같은 name 속성을 갖지만, 체크된 각각의 value는 다르다.

체크박스에서 서버로 실제 파라미터를 넘길 때는 TYPE 컬럼을 사용할 것이다.

하나일 경우도 있지만, 2개 이상의 TYPE이 체크될 수도 있다.

 

cf. 서블릿의 경우,

String[] XXX = request.getParameter("XXX"); 혹은 String[] XXX = request.getParameterValues("XXX");로 가져올 수 있고, 넘어오는 값은 {"B1", "B2", "B3", ...} 혹은 {"B1"} 이런 식이 될 것이다.

 

 

2. BoardServiceTest.java에 @Test 생성

@Test
public void findAllTest() {
String[] filters = new String[] {"B2", "B3"};  
// BY request.getParameterValues("filter");
// 체크 박스의 name 속성을 filter로 바꿨다.
// 실제 서버에서 체크 박스의 value 값을 꺼내온다.  
// filter라는 이름의 배열로 가져와서
// -> 리스트 객체 변환 -> 서비스에 넘기게 될 것이다.

List<Board> list = null;
// Board 형식의 List를 얻어오자.

list = service.findAll(filters);
// service의 findAll 메소드의 매개 값으로 filters 배열을 넘겨줌으로써
// list의 전체 조회 결과는 filter를 만족하는 결과만 나온다.

assertThat(list).isNotNull();
// list의 결과는 null이 아닐 것이다.
}

 

3. BoardService.java에 filters 배열을 받는 findAll() 메소드 생성

public List<Board> findAll(String[] filters) {
    List<Board> list = null; 
    SqlSession session = getSession();

    list = dao.findAll(session, filters);

    session.close();

    return list;
}

 

- 기존에 findAll() 메소드가 있어도 매개값이 다르기 때문에 가능하다 -> 오버로딩

 

4. BoardDao.java에 session과 filters 배열을 받는 findAll() 메소드 생성

public List<Board> findAll(SqlSession session, String[] filters) {

    Map<String, Object> map = new HashMap<>();

    map.put("filters", filters);

    return session.selectList("boardMapper.selectBoardListByFilters", map);
}

 

※※※※※ 아래 주의 사항을 읽고 와서 해결해야 하는 부분(미리 해놨으면 주의 사항에 해당되지 않을 부분) ※※※※※

 

- 기존에는 변수명으로 접근했지만,

list나 배열 타입은 파라미터로 전달하게 되면 내부적으로는 map 객체로 변환이 된다.

 

  key value
리스트 list collection 혹은 list obejct
배열 array array object

 

이런식으로 바뀐다.


- mapper의 쿼리문에서는 list 타입은 list로, 배열은 array 라는 이름으로 사용할 수 있는 것이다.

 

- 내부적으로 map 형태로 변환되기 때문에 파라미터로 문자열이나, 객체를 넘길 때에도

key 값은 변수명, value는 해당하는 객체가 된다.


- 그래도 굳이 굳이 filters라는 이름을 그대로 쓰고 싶다면 내부적으로 map 타입으로 변환되지 않도록

아예  map객체로 만들어주면 된다.

/*
* List 타입이나 Array 타입의 데이터를 실제 쿼리문 수행시킬 때 파라미터로 전달하면 내부적으로는 Map으로 타입이 변환되어서 저장되기 때문에
* Mapper에서는 list나 array라는 이름으로 사용해야 한다.

Dao.java
session.selectList("boardMapper.selectBoardListByFilters", filters);

Mapper.xml
<if test="array != null">
*   ...
</if>

* 만약에, filters 라는 이름을 Mapper에서 사용하고 싶다면 Map map 객체를 생성하고 -> map에 담아서 파라미터로 전달한다.

* Mapper에서는 key 값인 "filters"를 가지고 배열 객체인 filters에 접근할 수 있게 되는 것이다.
*/

 

 

 

5. Board.java 클래스에 type 필드 추가

private String type;

 

- 이제 쿼리문을 가지고 값을 가져올 것인데, 하나의 행을 Board 타입의 객체에 매핑해 주기 때문에 같은 이름의 필드가 필요하다.

 

 

6. board-mapper.xml에 쿼리문 작성하기

<!-- 기본 쿼리 -->
<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>


<select id="selectBoardListByFilters" parameterType="map" resultMap="boardListResultMap">
      <include refid="boardListSql" />
      <if test="filters != null">
      <!-- 
      AND B.TYPE IN ('B2', 'B3')

      위 결과를 만들기 위해서 foreach 태그를 사용한다.
      - collection 속성 : 파라미터로 넘어온 배열이나 list를 지정한다.
      - item : 배열이나 list의 각 요소들의 값이 들어가는 변수이다. (속성명은 임의로 작성)
      - index : 반복 횟수(0 부터 시작 한다.)
      - open : foreach 반복 시작 전에 출력할 문자열을 지정한다.
      - close : 반복 종료 전에 출력할 문자열을 지정한다.
      - separator : 반복할 때마다 반복을 구분할 구분자를 지정한다.
      -->
      AND B.TYPE IN 
      <foreach collection="filters" item="filter" open="(" separator="," close=")" >
      #{filter}
      </foreach>	
      </if>
</select>

 

- 자바 클래스의 필드명과, 오라클 DB의 컬럼명이 100% 동일하지 않기 때문에, 명시적 매핑을 해주는 resultMap을 사용할 것이다.

 

- 사전에 미리 board-mapper.xml 파일에 <resultMap> 으로 만들어 두었다.

 

- 조회된 결과를 resultMap에 있는 매핑 정보를 보고 매핑해 줄 것이다.

 

- 이 때, 사전에 만들어 두었던 <resultMap> 안에 방금 추가한 TYPE 컬럼에 대한 명시가 없어도, 자동 매핑으로 값이 들어가게 된다. 그래도 명시적으로 추가해주는게 좋기 때문에 type에 대한 것도 추가 기입한다.

 

- filters 배열의 결과에 null이 있을 수도 있기 때문에 그것에 대한 처리를 한다. 

<if test="filters != null"></if> 부분

null이 아니면 AND B.TYPE IN ('B2', 'B3') 쿼리가 포함될 것이다.

 

 

※※※※※※ 여기에서 주의 사항 ※※※※※

임의로 지정했던 String[] filters는 사실 이런 오류를 발생시킨다.

 

org.apache.ibatis.exceptions.PersistenceException:

### Error querying database. Cause : org.apache.ibatis.binding.BindingException: 

Parameter 'filters' not found. Available parameters are [array]

 

- 이것은 파라미터의 이름으로 'filters' 라는 것을 찾을 수가 없어서 그런 것이다.

- 따라서, <if test="filters != null"></if>를  <if test="array != null"></if>로 바꾼다.