이 글은 코드로 배우는 스프링 웹 프로젝트(남가람북스, 구멍가게 코딩단)을 읽고 공부한 내용을 바탕으로 정리한 글입니다.
Persistence Tier(영속 계층, 데이터 계층)는 데이터를 어떤 방식으로 보관하고, 사용하는가에 대한 설계가 들어가는 계층입니다. 이번 글에서는 MyBatis를 사용하여 게시판 데이터에 대한 persistence 계층의 CRUD를 구현해보겠습니다. MyBatis를 사용한 persistence 계층의 작업은 항상 다음과 같은 순서로 진행합니다.
1. 테이블의 칼럼 구조를 반영하는 VO(Value Object) 클래스 생성
2. MyBatis의 Mapper 인터페이스의 작성과 XML 처리
3. 작성한 Mapper 인터페이스 테스트
0. MyBatis와 스프링 연동
MyBatis와 스프링 연동은 다음 글을 참고해주세요!
1. 테이블 생성 + 더미 데이터 생성
우선, 제대로 동작한느지 테스트하기 위해 테이블을 생성하고 더미 데이터를 추가하겠습니다. PK는 bno
칼럼으로 지정했고, mysql의 auto_increment를 적용했습니다.
CREATE TABLE tbl_board (
bno int NOT NULL AUTO_INCREMENT,
title varchar(200) NOT NULL,
content varchar(2000) not null,
writer varchar(50) not null,
regdate date default (current_date),
updatedate date default (current_date),
PRIMARY KEY (bno)
);
insert into tbl_board (title, content, writer)
values ('test_title0', 'test_content0', 'test_writer0'),
('test_title1', 'test_content1', 'test_writer1'),
('test_title2', 'test_content2', 'test_writer2'),
('test_title3', 'test_content3', 'test_writer3'),
('test_title4', 'test_content4', 'test_writer4');
2. VO 클래스 작성
VO 클래스는 테이블 설계를 기준으로 작성합니다. 생성한 테이블은 아래와 같이 생겼습니다.
이를 참고하여 BoardVO
클래스를 다음과 작성합니다. BoardVO
클래스는 domain
패키지 아래에 작성해줍니다.
import lombok.Data;
import java.util.Date;
@Data
public class BoardVO {
private int bno;
private String title;
private String content;
private String writer;
private Date regdate;
private Date updatedate;
}
3. Mapper 인터페이스와 Mapper XML
MyBatis는 SQL을 처리하는데 어노테이션이나 XML을 이용할 수 있습니다. 간단한 경우에는 어노테이션을 이용하는 것이 좋고, 복잡한 SQL문(ex. 상황에 따라 다른 SQL문이 처리되어야 하는 경우)라면, Mapper XML을 작성하는 것이 좋습니다. 우선, Mapper 인터페이스에서 어노테이션을 이용하는 방법부터 해보겠습니다. mapper
패키지 아래에 BoardMapper
인터페이스를 작성합니다.
import com.hellomygreenworld.ex02.domain.BoardVO;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface BoardMapper {
@Select("select * from tbl_board where bno > 0")
List<BoardVO> getList();
}
mybatis가 Mapper 인터페이스가 작성된 mapper 패키지를 스캔하도록 applicationContext에 다음 코드를 작성해주어야 합니다.
<mybatis-spring:scan base-package="com.hellomygreenworld.ex02.mapper"/>
BoardMapperTests
클래스로 테스트해보도록 하겠습니다. (src/test/java/com/hellomygreenworld/mapper
)
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = {@Autowired})
private BoardMapper mapper;
@Test
public void testGetList() {
log.info(mapper.getList());
mapper.getList().forEach(board -> log.info(board));
}
}
4. Mapper XML
다음으로는 좀 더 복잡한 SQL문을 위해서 Mapper XML 파일을 작성해보겠습니다. Mapper XML 파일은 src/main/resources
내에 패캐지와 동일한 단계의 폴더(저의 경우는 src/main/resources/com/hellomygreenworld/ex02/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">
<mapper namespace="com.hellomygreenworld.ex02.mapper.BoardMapper">
<select id="getList" resultType="com.hellomygreenworld.ex02.domain.BoardVO">
<![CDATA[
select * from tbl_board where bno > 0
]]>
</select>
</mapper>
<mapper>
의namespace
속성값은 반드시 Mapper 인터페이스와 동일한 이름으로 작성합니다.<select>
태그의id
속성값은 메서드의 이름과 일치하게 작성합니다.resultType
속성의 값은 select 쿼리의 결과를 특정 클래스의 객체로 만들기 위해서 설정합니다.- SQL 문 안에 있는 부등호가 XML의 태그를 닫는 꺽세로 취급되지 않기 위해
<![CDATA[ ]]>
로 감싸줍니다. (SQL문에 부등호가 없다면 SQL문 그대로 작성해주면 됩니다.)
위와 같이 XML 파일을 작성하고, BoardMapper 인터페이스에서 @Select 어노테이션을 지워도 똑같은 테스트 결과를 볼 수 있습니다.여기까지 persistence 계층의 구현을 위한 준비를 마쳤습니다. 이제 본격적으로 persistnece 계층의 CRUD를 구현해보겠습니다.
5. Create(insert
) 처리
tlb_board
테이블은 PK가 bno
칼럼이고, auto_increment로 자동으로 데이터가 추가될 때 번호가 만들어집니다. 이렇게 자동으로 PK값이 정해지는 경우는 insert를 두가지 방식으로 처리할 수 있습니다.
- insert만 처리되고 생성된 PK 값을 알 필요가 없는 경우
- insert가 처리되고 생성된 PK 값을 알아야 하는 경우
이를 위해 insert를 하는 메서드는 2개를 만들겠습니다.
public interface BoardMapper {
public List<BoardVO> getList();
public void insert(BoardVO boardVO);
public void insertSelectKey(BoardVO boardVO);
}
insert()
메서드는 insert만 처리해주는 메서드이고 insertSelectKey()
메서드는 insert 처리 후, auto_increment값을 얻어올 수 있습니다. 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="com.hellomygreenworld.ex02.mapper.BoardMapper">
<select id="getList" resultType="com.hellomygreenworld.ex02.domain.BoardVO">
<![CDATA[
select * from tbl_board where bno > 0
]]>
</select>
<insert id="insert">
insert into tbl_board (title, content, writer)
values (#{title}, #{content}, #{writer})
</insert>
<insert id="insertSelectKey" useGeneratedKeys="true" keyProperty="bno">
insert into tbl_board (title, content, writer)
values (#{title}, #{content}, #{writer})
</insert>
</mapper>
insertSelectKey는 useGenerateKeys와 keyProperty 속성을 지정하여 PK값을 얻어오도록 작성하였습니다. (오라클은 @SelectKey
라는 MyBatis의 어노테이션을 이용합니다. 애초에 오라클은 auto_increment가 아닌, 시퀀스를 이용해서 처리하기 때문에 mysql과 처리 방식이 다소 다릅니다..!)
테스트 코드는 다음과 같이 작성하였습니다.
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = {@Autowired})
private BoardMapper mapper;
@Test
public void testInsert() {
BoardVO boardVO = new BoardVO();
boardVO.setTitle("inserted title");
boardVO.setContent("inserted content");
boardVO.setWriter("inserted writer");
mapper.insert(boardVO);
log.info(boardVO);
}
@Test
public void testInsertSelectKey() {
BoardVO boardVO = new BoardVO();
boardVO.setTitle("inserted title select key");
boardVO.setContent("inserted content select key");
boardVO.setWriter("inserted writer select key");
mapper.insertSelectKey(boardVO);
log.info(boardVO);
}
}
insert테스트와 insertSelectedKey테스트를 각각 확인해보면 로그가 다른 것을 확인할 수 있습니다. testInsertSelectKey는 generated_key를 알려줍니다.
6. Read(select
) 처리
앞서 insert 처리를 해보았는데 이제부터 select, update, delete 같은 패턴으로 작성하면 됩니다. Mapper 인터페이스와 Mapper XML 그리고 테스트 코드를 차례로 작성해보겠습니다.
데이터 조회(read) 작업은 PK 값을 파라미터로 넘겨주어 처리합니다.
public interface BoardMapper {
...
public BoardVO read(int bno);
}
<?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="com.hellomygreenworld.ex02.mapper.BoardMapper">
...
<select id="read" resultType="com.hellomygreenworld.ex02.domain.BoardVO">
select * from tbl_board where bno = #{bno}
</select>
</mapper>
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = {@Autowired})
private BoardMapper mapper;
@Test
public void testRead() {
BoardVO boardVO = mapper.read(5);
log.info(mapper);
}
}
7. Delete 처리
데이터 삭제도 PK 값을 이용해서 처리합니다. insert, delete, update 같은 DML 작업은 몇 건의 데이터가 처리되었는지를 반환할 수 있습니다. delete의 리턴값을 int로 하여 이를 확인해보겠습니다.
public interface BoardMapper {
...
public int delete(int bno);
}
<?xml version="1.0" encoding="UTF-8"?>
http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hellomygreenworld.ex02.mapper.BoardMapper">
...
<delete id="delete">
delete from tbl_board where bno = #{bno}
</delete>
</mapper>
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = {@Autowired})
private BoardMapper mapper;
@Test
public void testDelete() {
log.info("DELETE COUNT: " + mapper.delete(8));
}
}
8. Update 처리
public interface BoardMapper {
...
public int update(BoardVO boardVO);
}
<?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="com.hellomygreenworld.ex02.mapper.BoardMapper">
...
<update id="update">
update tbl_board
set title = #{title},
content = #{content},
writer = #{writer},
updateDate = current_time
where bno = #{bno}
</update>
</mapper>
@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-config/applicationContext.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = {@Autowired})
private BoardMapper mapper;
@Test
public void testUpdate() {
BoardVO boardVO = new BoardVO();
boardVO.setBno(5);
boardVO.setTitle("updated title");
boardVO.setContent("updated content");
boardVO.setWriter("updated writer");
int count = mapper.update(boardVO);
log.info("UPDATE COUNT: " + count);
}
}
'Java > Spring' 카테고리의 다른 글
[Spring MVC] Presentation Tier 구현 (0) | 2023.01.12 |
---|---|
[Spring MVC] Business Tier 구현 (0) | 2023.01.11 |
[Spring boot] Maria DB 설정 (0) | 2023.01.06 |
[Spring MVC] 컨트롤러의 Exception 처리 (0) | 2023.01.03 |
[Spring MVC] 파일업로드 처리 (0) | 2023.01.03 |
댓글