[spring] 영속/비즈니스 계층의 CRUD 구현
업데이트:
영속 계층 작업 순서
- 테이블의 컬럼 구조를 반영하는 VO(Value Object) 클래스의 생성
- MyBatis의 Mapper 인터페이스의 작성/XML 처리
- 작성한 Mapper 인터페이스의 테스트
영속 계층의 구현 준비
VO 클래스의 작성
VO 클래스를 생성하는 작업은 테이블 설계
기준으로 작성하면 된다.
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;
}
Mapper 인터페이스와 Mapper XML
Mapper 인터페이스
- root-context.xml scan 경로 확인
<mybatis-spring:scan base-package="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();
}
BoardMapper 인터페이스를 작성할 때는 이미 작성된 BoardVO
클래스를 적극적으로 활용해서 필요한 SQL을 어노테이션의 속성값으로 처리할 수 있다. SQL 뒤에 ‘WHERE bno > 0’과 같은 조건은 테이블을 검색하는테 bno라는 컬럼 조건을 주어서 Primary Key를 이용하도록 유도하는 조건이다.
위에서 어노테이션으로 작성한 구문을 SQL Developer에서 확인한다.
BoardMapper 인터페이스의 테스트를 위해 다음 경로에 BoardMapperTests 클래스를 추가한다
package org.zerock.mapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = @Autowired)
private BoardMapper mapper;
@Test
public void testGetList() {
mapper.getList().forEach(board -> log.info(board));
}
}
BoardMapperTests 클래스는 스프링을 이용해서 BoardMapper 인터페이스의 구현체를 주입받아 동작하게 된다. testGetList()의 결과는 SQL Developer에서 실행된 것과 동일해야 정상적으로 동작한 것이다.
Mapper XML 파일
아래의 경로에 BoardMapper.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">
<!-- namespace 속성값은 Mapper 인터페이스와 동일하게 주어야 함. -->
<!--
- select의 id 속성값은 메서드의 이름과 일치하게 작성
- resultType 속성 값은 select 쿼리의 결과를 특정 클래스의 객체로 만들기 위해 설정 -->
<select id="getList" resultType="org.zerock.domain.BoardVO">
<![CDATA[
SELECT * FROM tbl_board WHERE bno > 0
]]>
<!-- XML에서 부등호를 사용하려면 CDATA 사용 -->
</select>
</mapper>
XML로 SQL문을 다시 작성했으니 아까 작성했던 BoardMapper 인터페이스에 SQL을 주석처리한다.
// BoardMapper.java
public interface BoardMapper {
//@Select("SELECT * FROM tbl_board WHERE bno > 0")
public List<BoardVO> getList();
}
인터페이스 수정 후 다시 테스트 코드를 실행해본다.
영속 영역의 CRUD 구현
MyBatis는 내부적으로 JDBC 의 PreparedStatement를 활용하고 필요한 파라미터를 처리하는 ?
에 대한 치환은 #{속성}
을 이용해서 처리한다.
CREATE(INSERT) 처리
tbl_board 테이블은 PK 컬럼으로 bno를 이용하고, 시퀀스를 이용해서 자동으로 데이터가 추가될 때 번호가 만들어지는 방식을 사용한다. 이처럼 자동으로 PK 값이 정해지는 경우에는 다음과 같은 2가지 방식으로 처리할 수 있다.
- INSERT만 처리되고 PK 값을 알 필요가 없는 경우
- INSERT만 실행되고 PK 값을 알아야 하는 경우
// 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에 추가-->
<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 (seq_board.NEXTVAL, #{title}, #{content}, #{writer})
</insert>
- insert()
- 단순히 시퀀스의 다음 값을 구해서 insert 할 때 사용
- PK 값을 알 순 없지만 1번의 SQL 처리만으로 작업 완료
- insertSelectKey()
- @SelectKey라는 MyBatis의 어노테이션 이용
- @SelectKey는 주로 PK 값을 미리(before) SQL을 통해서 처리해 두고 특정한 이름으로 결과를 보관
테스트
// BoardMapperTests 클래스
@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로 비어있다.
@SelectKey를 이용하는 경우 테스트 코드
// BoardMapperTests 일부
@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);
}
이렇게 @SelectKey를 이용하는 방식은 SQL을 한 번 더 실행하는 부담이 있기는 하지만 자동으로 추가되는 PK 값을 확인해야 하는 상황에서는 유용하게 사용될 수 있다.
READ(SELECT) 처리
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 id="read" resultType="org.zerock.domain.BoardVO">
SELECT * FROM tbl_board WHERE bno = #{bno}
</select>
MyBatis는 bno라는 컬럼이 존재하면 인스턴스의 ‘setBno()’를 호출한다. MyBatis의 모든 파라미터와 리턴 타입의 처리는 get 파라미터명(), set 파라미터명()의 규칙으로 호출된다. 다만 위와 같이 ‘#{속성}’이 1개만 존재하는 경우에는 별도의 get 파라미터명()을 사용하지 않고 처리된다.
테스트
// BoardMapperTests 클래스에 추가
@Test
public void testRead() {
// 존재하는 게시물 번호로 테스트
BoardVO board = mapper.read(5L);
log.info(board);
}
DELETE 처리
등록, 삭제, 수정과 같은 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);
}
<!-- BoradMapper.xml 추가-->
<delete id="delete">
DELETE FROM tbl_board WHERE bno = #{bno}
</delete>
delete()의 메서드 리턴 타입은 int
로 지정해준다. 만일 정상적으로 데이터가 삭제되면 삭제된 행의 개수만큼 리턴해준다. 만약 해당 번호의 게시물이 없다면 ‘0’이 출력된다.
// BoardMapperTests 일부
@Test
public void testDelete() {
log.info("DELETE COUNT : " + mapper.delete(3L));
}
성공 시 다음과 같은 로그가 출력된다.
UPDATE 처리
업데이트할 때는 최종 수정시간을 데이터베이스 내 현재 시간으로 수정한다.
// BoardMapper 인터페이스 추가
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>
테스트
// 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);
}
업데이트를 성공적으로 마쳤으면 다음과 같은 로그가 출력된다.
- 참고 : 코드로 배우는 스프링 웹 프로젝트
댓글남기기