코드로 배우는 스프링 웹 프로젝트 P3: Chap08. 영속/비즈니스 계층의 CRUD 구현
코드로 배우는 스프링 웹 프로젝트 - 개정판
코드로 배우는 스프링 웹 프로젝트 - Part3: Chap08. 영속/비즈니스 계층의 CRUD 구현
2019년 7월 10일 인쇄판
Chapter08. 영속/비즈니스 계층의 CRUD 구현
영속 계층의 작업은 다음 같은 순서로 진행된다
- 테이블의 칼럼 구조를 반영하는 VO 클래스 생성
- MyBatis의 Mapper 인터페이스의 작성, XML 처리
- 작성한 Mapper 인터페이스의 테스트
8.1 영속 계층의 구현 준비
거의 모든 웹 앱의 최종 목적은 DB에 데이터를 기록하거나 가져오는 것이 목적이므로 개발 시 어느 정도의 설계가 진행되면 DB 관련 작업을 하게 된다.
8.1.1 VO 클래스의 작성
테이블 설계를 기준으로 작성하면 된다.
org.zerock.domain 패키지 생성, BoardVO 클래스 정의
package org.zerock.domain;
import java.util.Date;
import lombok.Data;
@Data
public class BoardVO {
private Long bno;
private String title;
private String content;
private String writer;
private Date regdate;
private Date updateDate;
}
8.1.2 Mapper 인터페이스와 Mapper XML
MyBatis 는 SQL을 처리하는데 어노테이션이나 XML을 이용할 수 있다.
간단한 SQL이라면 어노테이션이 무난하지만, SQL문이 복잡한 경우 그다지 유용한 방법은 아니다.
XML의 경우 단순 텍스트를 수정하는 과정만으로 처리가 끝나지만, 어노테이션의 경우 코드를 수정하고 다시 빌드하는 등의 유지 보수성이 떨어지는 이유로 기피하는 경우도 있다.
Mapper 인터페이스
root-context.xml은 Part1에서 org.zerock.mapper 패키지를 스캔하도록 이미 설정해본 적이 있다.
</bean>
<mybatis-spring:scan base-package="org.zerock.mapper"/>
</beans>
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
}
BoardMapper 인터페이스 작성 시 이미 작성된 BoardVO 클래스를 적극적으로 활용해 필요한 SQL을 어노테이션 속성값으로 처리할 수 있다.
(SQL 작성 시 ;를 쓰면 안 된다)
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
}
이 때 sql developer에서 썼던 내용을 "커밋"해 주어야 스프링에서도 결과가 반영된다.
BoardMapperTests 클래스는 스프링을 이용해 BoardMapper 인터페이스의 구현체를 주입받아 동작하게 된다.
testGetList()의 결과는 다음과 같다.
Mapper XML 파일
BoardMapperTests를 이용해 테스트가 완료되었다면 src/main/resources 내에 패키지와 동일한 org/zerock/mapper 단계 폴더 생성, XML 작성
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.zerock.mapper.BoardMapper">
<select id="getList" resultType="org.zerock.domain.BoardVO">
<![CDATA[
select * from tbl_board where bno>0
]]>
</select>
</mapper>
mapper의 namespace 속성은 Mapper 인터페이스와 동일하게 작성해야 한다.
XML에 사용한 CDATA 부분은 XML에서 부등호를 사용하기 위해 사용된다.
BoardMapper 인터페이스에선 SQL을 제거한다.
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
//@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
}
테스트 시 아까와 동일한 결과가 나와야 한다.
8.2 영속 영역의 CRUD 구현
영속 영역은 기본적으로 CRUD 작업을 하기 때문에 테이블과 VO 등 약간의 준비만으로 비즈니스 로직과 무관하게 CRUD 작업을 작성할 수 있다.
MyBatis는 내부적으로 JDBC의 PreparedStatement를 활용하고 필요한 파라미터를 처리하는 ? 에 대한 치환은 #{속성}을 이용해 처리한다.
8.2.1 create(insert) 처리
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
//@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.zerock.mapper.BoardMapper">
<select id="getList" resultType="org.zerock.domain.BoardVO">
<![CDATA[
select * from tbl_board where bno>0
]]>
</select>
<insert id="insert">
insert into tbl_board(bno, title, content, writer)
values (seq_board.nextval, #{title}, #{content}, #{writer})
</insert>
<insert id="insertSelectKey">
<selectKey keyProperty="bno" order="BEFORE" resultType="long">
select seq_board.nextval from dual
</selectKey>
insert into tbl_board(bno, title, content, writer)
values(#{bno}, #{title}, #{content}, #{writer})
</insert>
</mapper>
위는 xml
BoardMApper의 insert는 단순히 시퀀스 다음 값으로 insert할 때 사용된다.
insert 문은 몇 건의 데이터가 변경되었는지만을 알려주므로 추가 데이터의 PK는 알 수 없지만 1번의 SQL 처리만으로 작업을 완료할 수 있다.
insertSelectKey는 @SelectKey라는 MyBatis의 어노테이션을 사용한다.
주로 PK 값을 미리(before) SQL문을 통해 처리하고 특정 이름으로 결과를 보관하는 방식이다. @Insert 시 SQL문을 보면 #{bno}처럼 이미 처리된 결과를 이용하는 것을 볼 수 있다.
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글");
board.setContent("새로 작성하는 내용");
board.setWriter("newbie");
mapper.insert(board);
log.info(board);
}
테스트 코드 실행 결과는 다음과 같다. BoardVO의 toString() 결과 bno 값이 null로 비어있는 걸 확인할 수 있다.
@Test
public void testInsertSelectKey() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 select key");
board.setContent("새로 작성하는 내용 select key");
board.setWriter("newbie");
mapper.insertSelectKey(board);
log.info(board);
}
select seq_board.nextval from dual 쿼리가 먼저 실행되고 여기서 생성된 결과로 bno값을 처리한다.
8.2.2 read(select) 처리
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
//@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
}
<select id="read" resultType="org.zerock.domain.BoardVO">
select * from tbl_board where bno = #{bno}
</select>
</mapper>
MyBatis는 Mapper 인터페이스의 리턴 타입에 맞게 select의 결과를 처리하기 때문에 tbl_board 모든 칼럼은 BoardVO의 bno, title... 등의 속성값으로 처리된다. 좀 더 엄밀하게 말하면 MyBatis는 bno 칼럼이 존재하면 인스턴스의 setBno()를 호출한다. MyBatis의 모든 파라미터와 리턴 타입의 처리는 get 파라미터명, set 칼럼명의 규칙으로 호출된다.
다만 속성이 1개만 존재하면 별도의 get 파라미터명을 사용하지 않고 처리할 수 있다.
@Test
public void testRead() {
BoardVO board = mapper.read(8L);
log.info(board);
}
8.2.3 delete 처리
등록, 삭제, 수정과 같은 DML 작업은 몇 건의 데이터가 삭제되었는지를 반환할 수 있다.
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
//@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
public int delete(Long bno);
}
<delete id="delete">
delete from tbl_board where bno = #{bno}
</delete>
@Test
public void testDelete( ) {
log.info("DELETE COUNT : " + mapper.delete(3L));
}
8.2.4 update 처리
package org.zerock.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Select;
import org.zerock.domain.BoardVO;
public interface BoardMapper {
//@Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
public void insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
public int delete(Long bno);
public int update(BoardVO board);
}
<update id="update">
update tbl_board
set title = #{title},
content = #{content},
writer = #{writer},
updateDate = sysdate
where bno= #{bno}
</update>
@Test
public void testUpdate() {
BoardVO board = new BoardVO();
board.setBno(7L);
board.setTitle("수정된 제목");
board.setContent("수정된 내용");
board.setWriter("user00");
int count = mapper.update(board);
log.info("UPDATE COUNT : " + count);
}