Spring - 3 (테이블 생성 / CRUD 테스트 / Service구현)
게시물의 정보를 담을 테이블을 구축해준다 (계정 admin)
create table tbl_board (
bno number(10,0),--글번호
title varchar2(200) not null,--글제목
content varchar2(2000) not null,--글내용
writer varchar2(50) not null,--작성자
regdate date default sysdate, --작성일
updatedate date default sysdate --수정일
);
create sequence seq_board;
alter table tbl_board add constraint pk_board
primary key (bno);
-- bno 를 primary key 로 제약조건 추가.
// 아래 5번 반복 입력. (더미 데이터 생성)
insert into tbl_board(bno,title,content,writer)
values (seq_board.nextval, '테스트 제목', '테스트 내용', 'user00');
commit;
로직을 가지지않고 게시물 1개의 정보가 담길 BoardVO 클래스 생성
src/main/java -> com.review.domian 아래에 생성
package com.review.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;
}
com.review.mapper 패키지를 생성하고 BoardMapper 인터페이스를 만들어준다.
package com.review.mapper;
import java.util.List;
import com.review.domain.BoardVO;
public interface BoardMapper {
public List<BoardVO> getList();
public int insert(BoardVO board);
public void insertSelectKey(BoardVO board);
public BoardVO read(long l);
public int delete(long l);
public int update(BoardVO board);
}
쿼리 조작을 위한 mapper.xml 생성 (src/main/resources -> com -> review -> mapper)에서 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">
<!-- getTime 메소드를 이용하여 오라클에서 시간을 가쟈왔던 것처럼,
getList 메소드를 이용하여 tbl-board에서 레코드의 값을 가져와 보기.
순서를 정리하면, 테스트 클래스에서 메소드 호출 >> 매핑된 xml에서 해당 마이바티스 쿼리 구동
>> 결과를 리턴 받아서, 테스트 창에 출력.
kr.icia.mapper.BoardMapper : 생성될 인터페이스명,
kr.icia.domain.BoardVO : 검색결과를 담을 value object.
boardVO 객체 1개당 게시물 1개 저장.
-->
<mapper namespace="com.review.mapper.BoardMapper">
<select id="getList" resultType="com.review.domain.BoardVO">
<![CDATA[
select * from tbl_board where bno > 0
]]>
<!-- xml에서 <> 에대한 잘못된 해석을 방지 -->
</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">
select seq_board.nextval from dual
</selectKey>
insert into tbl_board(bno, title, content, writer)
values(#{bno}, #{title}, #{content}, #{writer})
</insert>
<select id="read" resultType="com.review.domain.BoardVO">
select * from tbl_board where bno=#{bno}
</select>
<delete id="delete">
delete tbl_board where bno=#{bno}
</delete>
<update id="update">
update tbl_board set
title=#{title},
content=#{content},
writer=#{writer},
updateDate=sysdate
where bno=#{bno}
</update>
</mapper>
Mybatis 사용시 쿼리문에 문자열 비교연산자나 부등호를 처리할 때가있다.
그러면 < 와 같은 기호를 괄호인지 아니면 비교연산자 인지 확인이 되지않는다.
이외에도 특수문자 사용하는데 제한이있다. 이럴때 사용한것이 <![CDATA{}>이다.
CDATA 안에 들어가는 문장을 문자열로 인식하게 한다.
namespace는 현재 mapper의 위치와 일치해야한다.
resultType은 이 쿼리가 실행되고 반환값이 존재할때 사용하는 파라미터이다. 타입을 지정해주면 지정타입을 반환한다.
seletKey는 SQL 수행작업 중 insert된 이후에 알 수 있는 값 또는,
생성된 값을 바로 가져와서 select 쿼리를 보내야 하는 경우가 있다.
주로 생성하고 난 후의 인덱스(번호)를 가져와 작업해야 하는 상황에서 많이 사용한다
selectKey는 DB에 명령을 한번만 보내며, 우선 입력한 값의 결과값을 다음 쿼리로 바로 return 시켜주는 것이다.
즉, selectKey에서 증가된 번호값을 바로 가져와서 insert해준다는것이다.
이제 해당 쿼리들이 제대로 잘 동작하는지 Test를 해본다.
src/test/java -> com.review.mapper 패키지에서 BoardMapperTests 클래스를 생성한다.
package com.review.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 com.review.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 testInert() {
// BoardVO board = new BoardVO();
// board.setTitle("새로 작성하는 글");
// board.setContent("새로 작성하는 내용");
// board.setWriter("새로운 작성자");
//
// mapper.insert(board);
// log.info(board);
// }
// @Test
// public void testInserSelectKey() {
// BoardVO board = new BoardVO();
// board.setTitle("새로 작성하는 글SelectKey");
// board.setContent("새로 작성하는 내용SelectKey");
// board.setWriter("새로운 작성자SelectKey");
//
// mapper.insertSelectKey(board);
// log.info(board);
// }
// @Test
// public void testRead() {
// BoardVO board = mapper.read(5L);
// // L은 bno가 long타입이라는 것을 알림.
// log.info(board);
// }
// @Test
// public void testDelete() {
// log.info("delete cnt: " + mapper.delete(22L));
// }
@Test
public void testUpdate() {
BoardVO board = new BoardVO();
board.setBno(21L);
board.setTitle("수정된 제목");
board.setContent("수정된 내용");
board.setWriter("user00");
int count = mapper.update(board);
log.info("update cnt : " + count);
}
}
@RunWith
RunWith(SpringJUnit4ClassRunner.class)는 말 그대로 SpringJUnit4ClassRunner.class를 실행한다는 것이고, 이 클래스는 내부적으로 스프링 컨테이너를 생성해준다.
@ContextConfiguration
생성된 스프링 컨테이너에 스프링 빈을 추가하기 위해서는 application-context.xml 파일과 같은 설정 파일을 읽어야 하는데, 이런 설정파일을 로드하는 어노테이션이 ContextConfiguration이다.
만약 스프링 컨테이너가 필요 없다면, 즉, 스프링 빈 팩토리에서 빈을 로드하는 것이 아닌, 직접 new로 객체를 생성해가며 테스트 코드를 작성할 것이라면 위의 어노테이션을 제거해도 된다.
먼저 testGetList의 테스트 결과
testInsert의 테스트결과
testInserSelectKey의 테스트 결과
testRead의 테스트 결과
testDelete의 테스트 결과
testUpdate의 테스트 결과
이제 서비스(비즈니스 로직) 계층을 만들어준다.
Service의 역할은 BoardVO가 DB에서 받아온 데이터를 전달받아 가공하는 것이다.
컨트롤러는 서비스에게 특정 업무를 요청, 서비스는 업무를 요청하며 필요한 자료를 DAO에게 요청하거나 업무를 통해 나온 자료를 DAO를 통해 저장한다.
Controller는 단순히 요청을 받아 해당 요청에 맞는 Service에 데이터를 주입하는 역할.
Service는 자신이 수행해야 할 서비스를 진행할 뿐이다.
해당 패키지안에 서비스 인터페이스를 생성한다.
package com.review.service;
import java.util.List;
import com.review.domain.BoardVO;
public interface BoardService {
public void register(BoardVO board);// 등록.
public BoardVO get(Long bno);// 읽기
public boolean modify(BoardVO board);// 수정
public boolean remove(Long bno);// 삭제
public List<BoardVO> getList();// 목록
}
그리고 서비스 인터페이스를 상속받는 BoardServiceImp 클래스를 생성한다.
package com.review.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.review.domain.BoardVO;
import com.review.mapper.BoardMapper;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j // lombok 로그 이용.
@Service // 이 클래스가 서비스 계층을 맡는다고 알림.
@AllArgsConstructor // 모든 매개변수에 대한 생성자 생성.(생성자 여러개 아님)
public class BoardServiceImp implements BoardService {
@Setter(onMethod_ = @Autowired)
private BoardMapper mapper;
@Override
public void register(BoardVO board) {
log.info("register....." + board);
mapper.insertSelectKey(board);
}
@Override
public BoardVO get(Long bno) {
log.info("get....." + bno);
return mapper.read(bno);
}
@Override
public boolean modify(BoardVO board) {
log.info("modify...... " + board);
return mapper.update(board)==1;
}
@Override
public boolean remove(Long bno) {
log.info("remove......" + bno);
return (mapper.delete(bno))==1;
}
@Override
public List<BoardVO> getList() {
log.info("getList.....0");
return mapper.getList();
}
}