[Spring] 영속 영역의 CRUD 구현
업데이트:
영속 영역의 CRUD 구현
웹 프로젝트 구조에서 마지막 영역이 영속 영역이지만, 실제로 구현을 가장 먼저 할 수 있는 영역도 영속 영역이다.
영속 영역은 기본적으로 CRUD 작업을 하기 때문에 테이블과 VO(DTO) 등 약간의 준비만으로도 비즈니스 로직과 무관하게 CRUD 작업을 작성할 수 있다.
MyBatis는 내부적으로 JDBC의 PreparedStatement를 활용하고 필요한 파라미터를 처리하는 ?
에 대한 치환은 #{속성}
을 이용해서 처리한다.
create(insert) 처리
tbl_board 테이블은 PK 칼럼으로 bno를 이용하고, 시퀀스를 이용해서 자동으로 데이터가 추가될 때 번호가 만들어지는 방식을 사용한다.
이처럼 자동으로 PK 값이 정해지는 경우에는 2가지 방식으로 처리할 수 있다.
- insert만 처리되고 생성된 PK 값을 알 필요가 없는 경우
- insert문이 실행되고 생성된 PK 값을 알아야 하는 경우
BoardMapper 인터페이스에는 위의 상황들을 고려해서 메서드를 추가 선언한다.
BoardMapper 인터페이스
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 baord);
public void insertSelectKey(BoardVO board);
}
BoardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "thhp://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>
BoardMapper의 insert()는 단순히 시퀀스의 다음 값을 구해서 insert 할 때 사용한다.
insert문은 몇 건의 데이터가 변경되었는지만을 알려주기 때문에 추가된 데이터의 PK 값을 알 수 없지만, 1번의 SQL 처리만으로 작업이 완료되는 장점이 있다.
insertSelectKey()는 @SelectKey라는 MyBatis의 어노테이션을 이용한다.
@SelectKey는 주로 PK 값을 미리(before) SQL을 통해서 처리해 두고 특정한 이름으로 결과를 보관하는 방식이다.
@Insert 할 때 SQL문을 보면 #{bno}와 같이 이미 처리된 결과를 이용하는 것을 볼 수 있다.
우선 insert()에 대한 테스트 메서드를 작성한다.
BoardMapperTests 클래스
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글");
board.setContent("새로 작성하는 내용");
board.setWriter("newbie");
mapper.insert(board);
log.info(board);
}
테스트 코드 마지막에 log.info(board)를 작성한 이유는 Lombok이 만들어주는 toString()을 이용해서 bno 멤버 변수(인스턴스 변수)의 값을 알아보기 위함이다.
실행 결과
INFO : org.zerock.mapper.BoardMapperTests - BoardVO(bno=null, title=새로 작성하는 글, content=새로 작성하는 내용, writer=newbie, regdate=null, updateDate=null)
테스트 결과를 살펴보면 BoardVO 클래스의 toString()의 결과가 출력되는 것을 볼 수 있는데, bno의 값이 null로 비어 있는 것을 확인할 수 있다.
@SelectKey를 이용한 테스트에 대한 메서드를 작성한다.
BoardMapperTest 클래스
@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);
}
실행 결과
INFO : jdbc.sqlonly - select seq_board.nextval from dual
...생략...
INFO : jdbc.audit - 1. Connection.prepareStatement(insert into tbl_board (bno,title,content,writer)
values (?, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@33e01298
...생략...
INFO : jdbc.sqltiming - insert into tbl_board (bno,title,content,writer) values (24, '새로 작성하는 글 select key', '새로 작성하는
내용 select key', 'newbie')
실행되는 결과를 살펴보면 ‘select seq_board.nextval from dual’과 같은 쿼리가 먼저 실행되고 여기서 생성된 결과를 이용해서 bno 값으로 처리되는 것을 볼 수 있다.
BoardMapper의 insertSelectKey()의 @Insert 문의 SQL을 보면 ‘insert into tbl_board (bno, title,content, writer) value(#{bno},#{title}, #{content}, #{writer})’와 같이 파라미터로 전달되는 BoardVO의 bno 값을 사용하게 되어 있다.
테스트 코드의 마지막 부분을 보면 BoardVO 객체의 bno값이 이전과 달리 지정된 겂을 볼 수 있다.
@SelectKey를 이용하는 방식은 SQL을 한 번 더 실행하는 부담이 있기는 하지만 자동으로 추가되는 PK 값을 확인해야 하는 상황에서는 유용하게 사용될 수 있다.
read(select) 처리
insert가 된 데이터를 조회하는 작업은 PK를 이용해서 처리하므로 BoardMapper의 파라미터 역시 BoardVO 클래스의 bno 타입 정보를 이용해서 처리한다.
BoardMapper 인터페이스
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 baord);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
}
BoardMapper.xml
<select id="read" resultType="org.zerock.domain.BoardVO">
select * from tbl_board where bon = #{bno}
</select>
MyBatis는 Mapper 인터페이스의 리턴 타입에 맞게 select의 결과를 처리하기 때문에 tbl_board의 모든 칼럼은 BoardVO의 ‘bno,title,content,writer,regdate,updateDate’ 속성값으로 처리된다.
좀 더 엄밀하게 말하면 MyBatis는 bno라는 칼럼이 존재하면 인스턴스의 ‘setBno()’롤 호출하게 된다.
MyBatis의 모든 파라미터와 리턴 타입의 처리는 get 파라미터명(), set 칼럼명()의 규칙으로 호출된다.
다만 위와 같이 #{속성}이 1개만 존재하는 경우에는 별도의 get 파라미터명()을 사용하지 않고 처리된다.
현재 테이블에 존재하는 데이터의 bno 칼럼의 값을 이용해서 테스트 코드를 통해 확인한다.
BoardMapperTests 클래스
@Test
public void testRead() {
BoardVO board = mapper.read(5L);
log.info(board);
}
mapper.read()를 호출할 경우에는 현재 테이블에 있는 데이터의 bno 값이 존재하는지 여부를 반드시 확인해야 한다.
실행 결과
INFO : jdbc.audit - 1. PreparedStatement.close() returned
INFO : jdbc.audit - 1. Connection.clearWarnings() returned
INFO : org.zerock.mapper.BoardMapperTests - BoardVO(bno=5, title=테스트 제목, content=테스트 내용, writer=user00, regdate=Fri Jun 04 23:43:24 KST 2021, updateDate=Fri Jun 04 23:43:24 KST 2021)
delect 처리
특정한 데이터를 삭제하는 작업 역시 PK 값을 이용해서 처리하므로 조회하는 작업과 유사하게 처리된다.
등록, 삭제, 수정과 같은 DML 작업은 ‘몇 건의 데이터가 삭제(혹은 수정)되었는지’를 반환할 수 있다.
BoardMapper 인터페이스
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 baord);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
public int delete(Long bno);
}
BoardMapper.xml
<delete id="delete">
delete from tbl_board where bno = #{bno}
</delete>
delete()의 메서드 리턴 타입은 int로 지정해서 만일 정상적으로 데이터가 삭제되면 1 이상의 값을 가지도록 작성한다.
테스트 코드는 현재 테이블에 존재하는 번호의 데이터를 삭제해 보고 ‘1’이라는 값이 출력되는지 확인한다.
만약 해당 번호의 게시물이 없다면 ‘0’이 출력된다.
BoardMapperTests 클래스
@Test
public void testDelete() {
log.info("DELETE COUNT : " + mapper.delete(3L));
}
testDelete()의 경우 3번 데이터가 존재했다면 다음과 같은 로그가 기록된다.
실행 결과
INFO : jdbc.sqltiming - delete from tbl_board where bno = 3
{executed in 8 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned false
INFO : jdbc.audit - 1. PreparedStatement.getUpdateCount() returned 1
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned
INFO : jdbc.audit - 1. Connection.clearWarnings() returned
INFO : org.zerock.mapper.BoardMapperTests - DELETE COUNT : 1
update 처리
마지막으로 update를 처리한다. 게시물의 업데이트는 제목, 내용, 작성자를 수정한다고 가정한다.
업데이트할 때는 최종 수정시간을 데이터베이스 내 현재 시간으로 수정한다.
Update는 delete와 마찬가지로 ‘몇 개의 데이터가 수정되었는가’를 처리할 수 있게 int 타입으로 메서드를 설계할 수 있다.
BoardMapper 인터페이스
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 baord);
public void insertSelectKey(BoardVO board);
public BoardVO read(Long bno);
public int delete(Long bno);
public int update(BoardVO board);
}
BoardMapper.xml
<update id="update">
update tbl_board
set title = #{title},
content=#{content},
writer = #{writer},
updateDate = sysdate
where bno = #{bno}
</update>
SQL에서 주의 깊게 봐야 하는 부분은 update 칼럼이 최종 수정시간을 의미하는 칼럼이기 때문에 현재 시간으로 변경해 주고 있다는 점과, regdate 칼럼은 최초 생성 시간이므로 건드리지 않는다는 점이다.
#{title}과 같은 부분은 파라미터로 전달된 BoardVO 객체의 getTitle()과 같은 메서드들을 호출해서 파라미터들이 처리된다.
테스트 코드는 read()를 이용해서 가져온 BoardVO 객체의 일부를 수정하는 방식이나 직접 BoardVO 객체를 생성해서 처리할 수 있다.
BoardMapperTests 클래스
@Test
public void testUpdate() {
BoardVO board = new BoardVO();
// 실행 전 존재하는 번호인지 확인할 것
board.setBno(5L);
board.setTitle("수정된 제목");
board.setContent("수정된 내용");
board.setWriter("user00");
int count = mapper.update(board);
log.info("UPDATE COUNT : " + count);
}
실행 결과
INFO : jdbc.sqlonly - update tbl_board set title = '수정된 제목', content='수정된 내용', writer = 'user00', updateDate = sysdate
where bno = 5
INFO : jdbc.sqltiming - update tbl_board set title = '수정된 제목', content='수정된 내용', writer = 'user00', updateDate = sysdate
where bno = 5
{executed in 8 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned false
INFO : jdbc.audit - 1. PreparedStatement.getUpdateCount() returned 1
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned
관련 서적 : 코드로 배우는 스프링 웹 프로젝트
공유하기
Twitter Google+ LinkedIn
댓글남기기