Part 8. 영속/비즈니스 계층의 CRUD 구현

8.2 영속 영역의 CRUD 구현

  • 웹 프로젝트 구조에서 마지막 영역이 영속 영역이지만, 실제로 구현을 가장 먼저 할 수 있는 영역도 영속 영역이다.
  • 영속 영역은 기본적으로 CRUD 작업을 하기 때문에 테이블과 VO(DTO) 등 약간의 준비만으로 비즈니스 로직과 무관하게 CRUD 작업을 작성할 수 있다.
  • MyBatis는 내부적으로 JDBC의 PreparedStatement를 활용하고 필요한 파라미터를 처리하는 '?'에 대한 치환은 '#{속성}'을 이용해 처리한다.

8.2.1 create(insert) 처리

  • tbl_board 테이블은 PK 칼럼으로 bno를 이용하고, 시퀀스를 이용해 자동으로 데이터가 추가될 때 번호가 만들어지는 방식을 사용한다.
  • 이처럼 자동으로 PK 값이 정해지는 경우에는 다음과 같은 2가지 방식으로 처리할 수 있다.
    • insert만 처리되고 생성된 PK 값을 알 필요가 없는 경우
    • insert문이 실행되고 생성된 PK 값을 알아야 하는 경우
  • BoanrdMapper 인터페이스는 위의 상황들을 고려해서 다음과 같이 메서드를 추가 선언한다.
< org.zerock.mapper.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 board);
	public void insertSelectKey(BoardVO board);
}
  • BoardMapper.xml은 다음과 같이 내용을 추가한다.
< BoardMapper.xml >
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//myabatis.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">
		selectseq_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()에 대한 테스트 코드를 src/test/java 내에 BoardMapperTests 클래스에 새로운 메서드로 작성해보면 다음과 같이 작성할 수 있다.
< 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)를 작성한 이유는 Lombook이 만들어주는 toString()을 이용해서 bno 멤버 변수(인스턴스 변수)의 값을 알아보기 위함이다.
  • testInsert()의 실행결과 일부는 다음과 같다.
INFO : jdbc.audit - 1. PreparedStatement.close() returned 
INFO : jdbc.audit - 1. Connection.clearWarnings() returned 
INFO : org.zerock.mapper.BoardMapperTests - BoardVO(bno=null, title=새로 작성하는 글, content=새로 작성하는 내용, writer=newbie, regdate=null, updateDate=null)
  • 테스트 결과의 마지막을 살펴보면 BoardVo() 클래스의 toString()의 결과가 출력되는 것을 볼 수 있는데, bno의 값이 null로 비어 있는 것을 확인할 수 있다.
  • @SelectKey를 이용하는 경우 테스트 코드는 다음과 같다.
< BoardMapperTests 클래스 >
	@Test
		public void testInsertSelectKey() {
		BoardVO board = new BoardVO();
		board.setTitle("새로 작성하는 글 select key");
		board.setContent("새로 작성하는 내용 select key");
		board.setWriter("newbie");		
		mapper.insert(board);		
		log.info(board);
	}
  • testInsertSelectKey()의 테스트 결과의 일부는 다음과 같다.
INFO : jdbc.sqlonly - insert into tbl_board (bno,title,content,writer) values (seq_board.nextval, '새로 작성하는 글 select 
key', '새로 작성하는 내용 select key', 'newbie') 
INFO : jdbc.sqltiming - insert into tbl_board (bno,title,content,writer) values (seq_board.nextval, '새로 작성하는 글 select 
key', '새로 작성하는 내용 select key', 'newbie') 
 {executed in 4 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 - BoardVO(bno=null, title=새로 작성하는 글 select key, content=새로 작성하는 내용 select key, writer=newbie, regdate=null, updateDate=null)
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned 
INFO : jdbc.audit - 1. Connection.prepareStatement(insert into tbl_board (bno,title,content,writer)
	values (seq_board.nextval, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@3dffc764
INFO : jdbc.audit - 1. PreparedStatement.setString(1, "새로 작성하는 글") returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "새로 작성하는 내용") returned 
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "newbie") returned 
INFO : jdbc.sqlonly - insert into tbl_board (bno,title,content,writer) values (seq_board.nextval, '새로 작성하는 글', '새로 
작성하는 내용', 'newbie') 
INFO : jdbc.sqltiming - insert into tbl_board (bno,title,content,writer) values (seq_board.nextval, '새로 작성하는 글', '새로 
작성하는 내용', 'newbie') 
  • 실행되는 결과를 살펴보면 'select seq_board.nextval from dual'과 같은 쿼리가 먼저 실행되고 여기서 생성된 결과를 이용해 bno 값으로 처리되는 것을 볼 수 있다.
  • BoardMapper의 insertSelectKey()의 @Insert 문의 SQL을 보면 'insert int tbl_board(bno,title,content,writer)values(#{bno},#{title},#{content},#{writer})'와 같이 파라미터로 전달되는 BoardVO의 bno 값을 사용하게 되어 있다.
  • 테스트 코드의 마지막 부분을 보면 BoardVO 객체의 bno 값이 이전과 달리 지정된 것을 볼 수 있다.(시퀀스의 값이므로 현재 테스트하는 환경마다 다른 값이 나온다. 시퀀스 값은 중복 없는 값을 위한 것일 뿐 다른 의미는 없다.)
  • @SelectKey를 이용하는 방식은 SQL을 한 번 더 실행하는 부담이 있기는 하지만 자동으로 추가되는 PK 값을 확인해야 하는 상황에서는 유용하게 사용될 수 있다.

8.2.2 read(select) 처리

  • insert가 된 데이터를 조회하는 작업은 PK를 이용해 처리하므로 BoardMapper의 파라미터 역시 BoardVO 클래스의 bno 타입 정보를 이용해 처리한다.
< org.zerock.mapper.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 board);	
	public void insertSelectKey(BoardVO board);	
	public BoardVO read(Long bno);	
}
< BoardMapper.xml에 추가되는 <select> >
<select id="read" resultType="org.zerock.domain.BoardVO">
	select * from tbl_board where bno = #{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.add()를 호출할 경우에는 현재 테이블에 있는 데이터의 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=Wed Aug 09 11:34:00 KST 2023, updateDate=Wed Aug 09 11:34:00 KST 2023)
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true

8.2.3 delete 처리

  • 특정한 데이터를 삭제하는 작업 역시 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 board);	
	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 2 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

8.2.4 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 board);	
	// 게시물 등록
	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);		
	}
  • 만일 데이터베이스에 5번 글이 존재했다면 다음과 같은 로그들이 출력될 것이다.
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 2 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 - UPDATE COUNT: 1
profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글