[spring] 영속/비즈니스 계층의 CRUD 구현

업데이트:



영속 계층 작업 순서

  • 테이블의 컬럼 구조를 반영하는 VO(Value Object) 클래스의 생성
  • MyBatis의 Mapper 인터페이스의 작성/XML 처리
  • 작성한 Mapper 인터페이스의 테스트



영속 계층의 구현 준비

VO 클래스의 작성

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 인터페이스 생성
    vo2
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에서 확인한다.
vo3



BoardMapper 인터페이스의 테스트를 위해 다음 경로에 BoardMapperTests 클래스를 추가한다
vo4

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에서 실행된 것과 동일해야 정상적으로 동작한 것이다.

vo5




Mapper XML 파일

아래의 경로에 BoardMapper.xml 파일을 생성한다.
vo6

<?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();
}


인터페이스 수정 후 다시 테스트 코드를 실행해본다.

vo7





영속 영역의 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);
}

실행 결과
vo8

테스트 마지막을 살펴보면 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);
}

vo9


이렇게 @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);
}

vo10






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));
}

성공 시 다음과 같은 로그가 출력된다.

vo11




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);
}


업데이트를 성공적으로 마쳤으면 다음과 같은 로그가 출력된다.

vo12







  • 참고 : 코드로 배우는 스프링 웹 프로젝트

태그:

카테고리:

업데이트:

댓글남기기