개발/[Spring] 블로그 만들기

[코드로 배우는 스프링 웹 프로젝트] 8강. 영속(Peristence) / 비즈니스(Business) 계층의 CRUD 구현하기

ee2ee2 2021. 12. 22. 18:28
728x90
반응형

해당 프로젝트는 코드로 배우는 스프링 웹 프로젝트(개정판)을 기반으로 진행됩니다.


이번 시간에는 코드를 이용해서 CRUD 작업을 진행해본다.

Persistence 계층의 작업은 다음 순서로 진행한다.

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

 


1. Persistence(영속) 계층의 구현 준비

 

1) VO 클래스의 작성

<현재 테이블의 구성>

 

프로젝트에 org.zerock.domain 패키지를 생성하고, BoardVO 클래스를 정의한다.

BoardVO.java

: 생성한 테이블과 동일한 타입/명칭으로 정의한다.

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 cdate;
	private Date udate;
}

2) Mapper 인터페이스와 Mapper XML

: 우선, Mapper 패키지를 스캔(조사)할 수 있도록 root-context.xml을 아래와 같이 수정한다.

root-context.xml 일부

<mybatis-spring:scan base-package="org.zerock.mapper"/>

 

Mapper 인터페이스 작성하기

: 프로젝트에 org.zerock.mapper 패키지를 생성하고, BoardMapper 인터페이스를 정의한다.

 

BoardMapper.java

package org.zerock.mapper;

import java.util.List;

import org.zerock.domain.BoardVO;

public interface BoardMapper {
	
	//게시글 전체 조회
	public List<BoardVO> getList();
 
}

 

Mapper XML 파일 작성하기

: src/main/resources 내에 패키지와 동일한 org/zerock/mapper 단계의 폴더를 생성하고 BoardMapper.xml 파일을 작성한다. (폴더는 한 번에 생성하지 말고 하나씩 생성하도록 하자.)

 

BoardMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper 
PUBLIC "-//mtbatis.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 인터페이스와 동일한 이름을 지정해야 한다.
-. <select> 태그의 id 속성 값 메소드의 이름과 일치해야 한다.
-. resultType 속성의 값은 select 쿼리의 결과를 특정 클래스의 객체로 만들기 위해서 설정한다.
-. XML에 사용한 CDATA 부분은 XML에서 부등호를 사용하기 위해 사용한다.


테스트 수행하기

: org.zerock.mapper.BoardMapperTests 클래스

 

 

BoardMapperTests.java

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 org.zerock.domain.BoardVO;

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

 

테스트 결과

작성 쿼리 정상 수행
모든 리스트 정상 조회 완료

 


2. Persistence(영속) 영역의 CRUD 구현

: MyBatis는 내부적으로 JDBC의 PreparedStatement를 활용하고 필요한 파라미터를 처리하는 '?'에 대한 치환은 '#{속성}'을 이용해서 처리한다. 

 

BoardMapper.java

package org.zerock.mapper;

import java.util.List;

import org.zerock.domain.BoardVO;

public interface BoardMapper {
	
	//게시글 전체 조회
	public List<BoardVO> getList();
	
	//게시글 삽입 : insert 처리 후 생성된 PK값을 알 필요 없는 경우
	public void insert(BoardVO board);
	
	//게시글 삽입 : insert 처리 후 생성된 PK값을 알아야 할 경우
	public void insertSelectKey(BoardVO board);
	
	//게시글 읽기 : 게시글 번호를 통해서 1개의 글 읽어오기
	public BoardVO read(Long bno);
	
	//게시글 삭제 : 특정 게시글을 삭제하기
	//삭제하고자하는 게시글이 있다면 1, 없으면 0 반환
	public int delete(Long bno);
	
	//게시글 수정 : 특정 게시글 수정하기
	//수정하고자하는 게시글이 있다면 1, 없으면 0 반환
	public int update(BoardVO board);
}

 

BoardMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper 
PUBLIC "-//mtbatis.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 처리 후 생성된 PK값을 알 필요 없는 경우 -->
<insert id="insert">
	insert into tbl_board (bno, title, content, writer)
	values (seq_board.nextval, #{title}, #{content}, #{writer})
</insert>

<!-- 게시글 삽입 : insert 처리 후 생성된 PK값을 알아야 할 경우 -->
<insert id="insertSelectKey">
	<!-- @SelectKey라는 MyBatis의 어노테이션을 이용한다. 
		미리(Before) SQL을 통해 처리해주고 특정한 이름으로 결과를 보관하는 방식 -->
	<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>

<!-- 게시글 읽기 : 게시글 번호를 통해서 1개의 글 읽어오기 -->
<select id ="read" resultType="org.zerock.domain.BoardVO">
	select * from tbl_board where bno = #{bno}
</select>


<!-- 게시글 삭제 : 특정 게시글을 삭제하기 -->
<delete id="delete">
	delete from tbl_board where bno = #{bno}
</delete>

<!-- 게시글 수정 : 특정 게시글 수정하기 -->
<update id="update">
	update tbl_board 
	set title=#{title}, 
	content=#{content}, 
	writer=#{writer},
	udate=sysdate
	where bno=#{bno}
</update>

</mapper>

 

BoardMapperTests.java (테스트 코드)

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 org.zerock.domain.BoardVO;

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));
	}
	
	@Test
	public void testInsert() {
		BoardVO board = new BoardVO();
		board.setTitle("새로 작성하는 글 테스트");
		board.setContent("새로 작성하는 내용 테스트");
		board.setWriter("newbie");
		
		mapper.insert(board);
		
		log.info(board);
	}
	
	@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);
	}
	
	@Test
	public void testRead() {
		//존재하는 게시글 번호로 테스트 진행 필수
		BoardVO board = mapper.read(1L);
		
		log.info(board);
	}
	
	@Test 
	public void testDelete() {
		//존재하는 게시글 번호로 테스트 진행 필수
		//삭제하고자하는 게시글이 있다면 1, 없으면 0 반환
		log.info("DELETE COUNT: " + mapper.delete(22L));
	}
	
	@Test
	public void testUpdate() {
		BoardVO board = new BoardVO();
		board.setBno(29L);
		board.setTitle("수정한 글 입니다.");
		board.setContent("수정하는 내용 테스트");
		board.setWriter("user00");
		
		int count = mapper.update(board);
		log.info("UPDATE COUNT: " + count);
	}

}

 

테스트 수행 결과

public void insert(BoardVO board); 수헹


public void insertSelectKey(BoardVO board); 수행

: bno 값이 먼저 구해지고, insert 할 때 미리 구한 값을 이용하는 것을 볼 수 있음.


public BoardVO read(Long bno); 수행

1번 글 정상 조회


public int delete(Long bno); 수행

 

22번 글이 삭제됨을 확인


public int update(BoardVO board); 수행

내용 수정 정상 확인


 

다음 시간에는 Business 계층의 로직을 좀 더 구체화 해보도록 한다!