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

  • 예제는 단순한 하나의 테이블만을 이용하기 때문에 데이터베이스에 테이블, 시퀀스, 약간의 데이터들이 생성되었다면 코드를 이용해 데이터에 대한 CRUD(Create, Read, Update, Delete) 작업을 진행한다.
  • 영속 계층의 작업은 항상 다음과 같은 순서로 진행한다.
    • 테이블의 칼럼 구조를 반영하는 VO(Value Object) 클래스의 생성
    • MyBatis의 Mapper 인터페이스의 작성 / XML 처리
    • 작성한 Mapper 인터페이스의 테스트
  • 위의 과정 전에 먼저 JDBC 연결을 테스트하는 과정을 거치는 것이 좋지만, SQL Developer의 연결 자체가 이미 JDBC 연결을 이용하기 때문에 예제에서는 별도의 과정을 생략한다.

8.1 영속 계층의 구현 준비

  • 거의 모든 웹 어플리케이션의 최종 목적은 데이터베이스에 데이터를 기록하거나, 원하는 데이터를 가져오는 것이 목적이기 때문에 개발할 때 어느 정도의 설계가 진행되면 데이터 베이스 관련 작업을 한다.

8.1.1 VO 클래스의 작성

  • VO 클래스를 생성하는 작업은 테이블 설계를 기준으로 작성하면 된다.
  • 현재 tbl_board 테이블의 구성은 아래와 같다.
  • 프로젝트에 org.zerock.domain 패키지를 생성하고, BoardVO 클래스를 정의한다.
< BoardVO 클래스 >
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;
}
  • BoardVO 클래스는 Lombok을 이용해 생성자와 getter/setter, toString() 등을 만들어 내는 방식을 사용한다.
  • 이를 위해 @Data 어노테이션을 적용한다.

8.1.1 Mapper 인터페이스와 Mapper XML

  • PART 1에서 MyBatis는 SQL을 처리하는데 어노테이션이나 XML을 이용할 수 있다.
  • 간단한 SQL이라면 어노테이션을 이용해 처리하는 것이 무난하지만, SQL이 점점 복잡해지고 검색과 같이 상황에 따른 다른 SQL문이 처리되는 경우에는 어노테이션은 그다지 유용하지 못하다는 단점이 있다.
  • XML의 경우 단순 텍스트를 수정하는 과정만으로 처리가 끝나지만, 어노테이션의 경우 코드를 수정하고 다시 빌드 하는 등의 유지 보수성이 떨어지는 이유로 기피하는 경우도 종종 있다.

Mapper 인터페이스

  • root-context.xml은 PART 1에서 'org.zerock.mapper' 패키지를 스캔하도록 이미 설정해 본 적 있으므로 이를 활용해 프로젝트를 제작한다.
< root-context.xml >
	<mybatis-spring:scan base-package="org.zerock.mapper"/>
  • Mapper 인터페이스를 작성할 때는 리스트(select)와 등록(insert) 작업을 우선해서 작성한다.
  • org.zerock.mapper 패키지를 작성하고, BoardMapper 인터페이스를 추가한다.
< 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 작성시 반디시 ';'이 없도록 작성해야 한다.)
  • SQL 뒤에 'where bno > 0' 과 같은 조건은 테이블을 검색하는데 bno라는 칼럼 조건을 주어서 Primary key를 이용하다록 유도하는 조건이다.
  • SQL Devleper에서 먼저 실행한 결과는 아래와 같다.
  • SQL Developer에서 먼저 확인하는 이유는 1) SQL이 문제가 없이 실행 가능한지를 확인하기 위한 용도와, 2) 데이터베이스의 COMMIT을 하지 않았다면 나중에 테스트 결과가 달라지기 때문에 이를 먼저 비교할 수 있도록 하기 위함이다.
  • 작성된 BoardMapper 인터페이스를 테스트 할 수 있게 테스트 환경인 'src/test/java'에 'org.zerock.mapper'패키지를 작성하고 BoardMapperTests 클래스를 추가한다.
< org.zerock.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")
// Java Config
/// @ContextConfiguration(classes = {org.zerock.config.RootConfig.class} )
@Log4j
public class BoardMapperTests {
	@Setter(onMethod_ = @Autowired)
	private BoardMapper mapper;	
	@Test
	public void testGetList() {
		mapper.getList().forEach(board -> log.info(board));
	}
}
  • BoardMapperTests 클래스는 스프링을 이용해 BoardMapper 이터페이스의 구현체를 주입받아 동작하게 한다.
  • Java 설정 시에는 RootConfig 클래스를 이용해 스프링의 설정을 이용하고 있음을 명시한다.
  • testGetList()의 결과는 SQL Developer에서 실행된 것과 동일해야만 정상적으로 동작한 것이다.
  • 실행결과는 아래과 같다.
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultsettable - 
|----|-------|--------|-------|----------------------|----------------------|
|bno |title  |content |writer |regdate               |updatedate            |
|----|-------|--------|-------|----------------------|----------------------|
|1   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:55.0 |2023-08-09 11:33:55.0 |
|2   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:56.0 |2023-08-09 11:33:56.0 |
|3   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:57.0 |2023-08-09 11:33:57.0 |
|4   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:57.0 |2023-08-09 11:33:57.0 |
|5   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:34:00.0 |2023-08-09 11:34:00.0 |
|----|-------|--------|-------|----------------------|----------------------|
INFO : jdbc.resultset - 1. ResultSet.next() returned false
INFO : jdbc.resultset - 1. ResultSet.close() returned void
INFO : jdbc.audit - 1. Connection.getMetaData() returned oracle.jdbc.driver.OracleDatabaseMetaData@7c251f90
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned 
INFO : jdbc.audit - 1. Connection.clearWarnings() returned 

Mapper XML 파일

  • BoardMapperTests를 이용해 테스트가 완료되었다면 src/main/resources 내에 패키지와 동일한 org/zerock/mapper 단계의 폴더를 생성하고 XML 파일을 작성한다.
  • 파일의 폴더 구조나 이름은 무방하지만 패키지와 클래스 이름과 동일하게 해주면 나중에 혼란스러운 상황을 피할 수 있다.
  • 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>
</mapper>
  • XML을 작성할 때는 반드시 < mapper > 의 namespace 속성값을 Mapper 인터페이스와 동일한 이름을 주는 것에 주의하고, < select > 태그의 id 속성값은 메서드의 이름과 일치하게 작성한다.
  • resultType 속성의 값은 select 쿼리의 결과를 특정 클래스의 객체로 만들기 위해 설정한다.
  • XML에 사용한 CDATA 부분은 XML에서 부등호를 사용하기 위해 사용한다.
  • XML에 SQL문이 처리되었으니 BoardMapper 인터페이스에서 SQL은 제거한다.
< org.zerock.mapper.BoardMapper 인터페이스 - SQL 제거 >
public interface BoardMapper {
	//@Select("select * from tbl_board where bno > 0")
	public List<BoardVO> getList();
}
  • 인터페이스 수정 후에는 반드시 기존의 테스트 코드를 통해 기존과 동일하게 동작하는지 확인해야 한다.
INFO : jdbc.resultsettable - 
|----|-------|--------|-------|----------------------|----------------------|
|bno |title  |content |writer |regdate               |updatedate            |
|----|-------|--------|-------|----------------------|----------------------|
|1   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:55.0 |2023-08-09 11:33:55.0 |
|2   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:56.0 |2023-08-09 11:33:56.0 |
|3   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:57.0 |2023-08-09 11:33:57.0 |
|4   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:33:57.0 |2023-08-09 11:33:57.0 |
|5   |테스트 제목 |테스트 내용  |USER00 |2023-08-09 11:34:00.0 |2023-08-09 11:34:00.0 |
|----|-------|--------|-------|----------------------|----------------------|
INFO : jdbc.resultset - 1. ResultSet.next() returned false
INFO : jdbc.resultset - 1. ResultSet.close() returned void
profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글