스프링 레거시(Spring legacy) - 매퍼 작성
매퍼(mapper) 작성
매퍼(mapper)는 sqlSession을 통해서 repository에 해당하는 DAO에 SQL 쿼리문을 연결하여 실행합니다.
우선 매퍼를 작성하기 전에 테이블을 생성하겠습니다. 레거시에서는 DDL을 자동으로 작성해주지 않아서 수동으로 테이블을 만들어야합니다. DBeaver를 통해서 스키마를 생성해주세요.
테이블은 이전에 작성한 ER모델을 참고하여 작성하겠습니다.
게시글을 저장하는 board, 댓글을 저장하는 comments, 게시글을 분류하는 category, 인증을 위한 member, 인가권한을 확인하기 위한 member_group로 구성합니다.
CREATE TABLE board (
document_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
category_id INT NOT NULL,
is_notice CHAR(1) DEFAULT 'N',
title VARCHAR(250) NOT NULL,
content CLOB NOT NULL,
like_count INT DEFAULT 0,
dislike_count INT DEFAULT 0,
read_count INT DEFAULT 0,
member_id INT NOT NULL,
create_date DATE DEFAULT SYSDATE,
update_date DATE DEFAULT SYSDATE
);
CREATE TABLE comments (
comment_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
document_id INT NOT NULL,
content CLOB,
like_count INT DEFAULT 0,
dislike_count INT DEFAULT 0,
member_id INT NOT NULL,
create_date DATE DEFAULT SYSDATE,
update_date DATE DEFAULT SYSDATE
);
CREATE TABLE category (
category_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
category VARCHAR(80) UNIQUE NOT NULL,
create_date DATE DEFAULT SYSDATE,
update_date DATE DEFAULT SYSDATE
);
CREATE TABLE MEMBER (
member_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
password VARCHAR(60) NOT NULL,
email VARCHAR(250) UNIQUE,
name VARCHAR(40) NOT NULL,
nick_name VARCHAR(40) UNIQUE NOT NULL,
group_id INT NOT NULL,
create_date DATE DEFAULT SYSDATE,
last_login DATE DEFAULT SYSDATE
);
CREATE TABLE member_group (
group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
group_name VARCHAR(80) UNIQUE NOT NULL,
create_date DATE DEFAULT SYSDATE,
update_date DATE DEFAULT SYSDATE
);
ALTER TABLE board ADD CONSTRAINT fk_category_id FOREIGN KEY(category_id) REFERENCES category(category_id);
ALTER TABLE board ADD CONSTRAINT fk_member_id FOREIGN KEY(member_id) REFERENCES member(member_id);
ALTER TABLE comments ADD CONSTRAINT fk_document_id FOREIGN KEY(document_id) REFERENCES board(document_id);
ALTER TABLE comments ADD CONSTRAINT fk_member_id_2 FOREIGN KEY(member_id) REFERENCES member(member_id);
ALTER TABLE member ADD CONSTRAINT fk_group_id_2 FOREIGN KEY(group_id) REFERENCES member_group(group_id);
이제 매퍼를 작성하겠습니다.
매퍼를 작성하기 전에 DBeaver를 통해 미리 쿼리를 작성해보도록 하겠습니다. 외래키를 통해 제약조건을 설정했기때문에 우선 그룹을 생성합니다.
Admin 그룹 생성
INSERT INTO member_group (group_name) VALUES ('Admin');
쿼리문을 토대로 groupMapper를 생성합니다. 저번 글에서 생성한 mappers 패키지 아래 groupMapper.xml
파일을 만들고 다음과 같이 CRUD 쿼리를 작성하세요.
groupMapper.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="kro.rubisco.dao.GroupDAO">
<insert id="create">
insert into member_group (group_name) values (#{groupName})
</insert>
<select id="read" resultType="GroupDTO">
select * from member_group where group_id = #{groupId}
</select>
<update id="update">
update member_group set group_name=#{groupName}, update_date=SYSDATE
where group_id = #{groupId}
</update>
<delete id="delete"> delete from member_group where group_id = #{groupId} </delete>
<select id="listAll" resultType="GroupDTO">
<![CDATA[ select * from member_group where group_id > 0 order by group_id desc ]]>
</select>
</mapper>
JPA에서는 레포지토리를 통해 SQL문 없이도 DB연결이 쉬웠지만, Mybatis는 SQL문을 직접 적어야합니다.
우선 mapper
태그에 namespace
속성을 통해 DAO
에 연결해줍니다. DAO는 Data Access Object의 약자로, 데이터베이스와 연결되는 객체입니다. JPA에서 Repository와 같은 역할을 합니다. DAO가 mapper에 연결되면서 SQL문을 실행시키고, select문의 경우 Entity를 생성합니다. mapper 태그 하위 태그에는 DML이 들어가며, id 속성값은 DAO의 메소드명이 됩니다.
resultType
속성은 Entity를 DTO
에 연결해줍니다. DTO는 Data Transfer Object의 약자로, 데이터를 전송해주는 객체입니다. JPA를 사용할때는 DTO를 작성하지 않고 Entity 클래스를 DTO 대신 사용 했습니다.
DTO 객체의 멤버변수는 #{멤버변수}
로 표시합니다. listAll
메소드에서 <![CDATA[...]]>
라고 표시한 것은 꺽쇠 표시(>) 때문입니다. 해당 태그로 감싸지 않으면 꺽쇠가 태그를 내타내는 기호인지 부등호를 나타내는 기호인지 알 수 없기때문에 오류를 나타냅니다.
자바에서는 관례적으로 변수명을 낙타표기법(Camel Case)
으로 나타냅니다. table과 column을 스네이크 표기법(Snake Case)으로 만들었기 때문에 DTO 변수의 매핑을 위해 이를 변환할 필요가 있습니다.
mybatis-config.xml
파일의 configuration 태그 아래에 다음과 같이 settings 태그를 추가합니다.
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 카멜 케이스 매핑 설정 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 쿼리 결과 필드가 null인 경우, 누락되지 않도록 설정 -->
<setting name="callSettersOnNulls" value="true"/>
<!-- 쿼리에 보내는 파라미터가 null인 경우, 오류가 발생하는 것을 방지 -->
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
<typeAliases>
<package name="kro.rubisco.dto"/>
</typeAliases>
</configuration>
이제 나머지 매퍼도 만들어줍니다. memberMapper부터 만들겠습니다.
memberMapper.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="kro.rubisco.dao.MemberDAO">
<resultMap id="getMember" type="MemberDTO">
<association property="group" column="group_id" select="getGroup" />
</resultMap>
<insert id="create">
insert into member (password, email, name, nick_name, group_id)
values (#{password}, #{email}, #{name}, #{nickName}, #{groupId})
</insert>
<select id="read" resultMap="getMember">
select * from member where member_id = #{memberId}
</select>
<select id="getGroup" resultType="GroupDTO">
select * from member_group where group_id=#{groupId}
</select>
<update id="update">
update member
set password=#{password},
email=#{email},
name=#{name},
nick_name= #{nickName},
where member_id = #{memberId}
</update>
<delete id="delete"> delete from member where member_id = #{memberId} </delete>
<select id="listAll" resultMap="getMember">
<![CDATA[ select * from member where m.member_id > 0 order by m.member_id desc ]]>
</select>
</mapper>
resultMap
이라는 태그가 추가되었습니다. select문의 경우 검색된 entity를 resultType
에 지정된 타입의 객체에 담게 되는데, colmun은 원시값을 가지기 때문에 속성값이 참조값을 가진다면 매핑하지 못합니다. 그러므로 연관관계를 통해 entity 자체를 객체의 속성에 담기 위해서는 resultMap
을 설정하여 매핑정보를 입력해주면 됩니다.
resultMap 태그에 id
를 설정하고 type
속성에 매핑되는 객체 타입을 입력합니다. 하위 태그인 association은 1:N 연관관계를 설정할 수 있습니다. property
속성에는 resultMap의 type에서 연관관계를 맺을 매개변수명을 입력합니다. join 쿼리를 통해 매핑해주거나 select 속성을 사용하여 매핑해줄 수 있는데 저는 후자의 방법으로 매핑했습니다.
아래 코드로 예를 들어보겠습니다.
resultMap 설정
<resultMap id="getMember" type="MemberDTO">
<association property="group" column="group_id" javaType="GroupDTO" select="getGroup" />
</resultMap>
<select id="read" resultMap="getMember">
select * from member where member_id = #{memberId}
</select>
<select id="getGroup" resultType="GroupDTO">
select * from member_group where group_id=#{groupId}
</select>
id가 read인 select 태그에 resultType 속성 대신에 resultMap
속성을 추가합니다. 해당 select문을 실행시켜 출력된 entity는 resultMap의 type
속성에 입력한 MemberDTO
타입에 매핑됩니다. 이때 association
태그의 select
속성에 입력된 getGroup
이라는 id를 가진 select문을 함께 실행시켜 property
에 입력된 group
이라는 매개변수의 타입인 groupDTO
에 매핑됩니다. column
에 입력된 값은 getGroup select문의 매개변수로 전달됩니다.
동일한 방식으로 각각의 mapper를 만들어줍시다.
categoryMapper.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="kro.rubisco.dao.CategoryDAO">
<insert id="create">
insert into category (category) values (#{category})
</insert>
<select id="read" resultType="CategoryDTO">
select * from category where category_id = #{categoryId}
</select>
<update id="update">
update category set category=#{category}, update_date=SYSDATE
where category_id = #{categoryId}
</update>
<delete id="delete"> delete from category where category_id = #{categoryId} </delete>
<select id="listAll" resultType="CategoryDTO">
<![CDATA[ select * from category where category_id > 0 order by category_id desc ]]>
</select>
</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="kro.rubisco.dao.BoardDAO">
<resultMap id="getBoard" type="BoardDTO">
<id property="documentId" column="document_id" />
<result property="isNotice" column="is_notice" typeHandler="kro.rubisco.config.YNTypeHandler" />
<association property="category" column="category_id" select="getCategory" />
<association property="member" column="member_id" select="getMember" />
<association property="group" column="group_id" select="getGroup" />
<collection property="commentList" column="document_id" select="getComments" />
</resultMap>
<resultMap id="getBoardList" type="BoardDTO">
<id property="documentId" column="document_id" />
<result property="isNotice" column="is_notice" typeHandler="kro.rubisco.config.YNTypeHandler" />
<association property="category" column="category_id" select="getCategory" />
<association property="member" column="member_id" select="getMember" />
<association property="group" column="group_id" select="getGroup" />
</resultMap>
<select id="getCategory" resultType="CategoryDTO">
select * from category where category_id=#{categoryId}
</select>
<select id="getMember" resultType="MemberDTO">
select * from member where member_id=#{memberId}
</select>
<select id="getGroup" resultType="GroupDTO">
select * from member_group where group_id=#{groupId}
</select>
<select id="getComments" resultType="CommentDTO">
select * from comments where document_id=#{documentId}
</select>
<insert id="create">
insert into board (category_id, is_notice, title, content, member_id)
values (#{categoryId}, #{isNotice, typeHandler=kro.rubisco.config.YNTypeHandler}, #{title}, #{content}, #{memberId})
<selectKey keyProperty="documentId" order="AFTER">
select max(document_id) as document_id from board
</selectKey>
</insert>
<select id="read" resultMap="getBoard">
select * from board where document_id = #{documentId}
</select>
<update id="update">
update board
set category_id=#{categoryId},
is_notice=#{isNotice, typeHandler=kro.rubisco.config.YNTypeHandler},
title=#{title},
content=#{content},
update_date=SYSDATE
where document_id = #{documentId}
</update>
<delete id="delete"> delete from board where document_id = #{documentId} </delete>
<select id="listAll" resultMap="getBoardList">
<![CDATA[ select * from board where document_id > 0 order by document_id desc ]]>
</select>
</mapper>
commentMapper.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="kro.rubisco.dao.CommentDAO">
<resultMap id="getComment" type="CommentDTO">
<association property="board" column="document_id" select="getBoard" />
<association property="member" column="member_id" select="getMember" />
</resultMap>
<select id="getBoard" resultType="BoardDTO">
select * from board where document_id=#{documentId}
</select>
<select id="getMember" resultType="MemberDTO">
select * from member where member_id=#{memberId}
</select>
<insert id="create">
insert into comments (document_id, content, member_id)
values (#{documentId}, #{content}, #{memberId})
</insert>
<select id="read" resultMap="getComment">
select * from comments where cmt.comment_id = #{commentId}
</select>
<update id="update">
update comments
set document_id=#{documentId},
content=#{content},
update_date=SYSDATE
where comment_id = #{commentId}
</update>
<delete id="delete"> delete from comments where comment_id = #{commentId} </delete>
<select id="listAll" resultMap="getComment">
<![CDATA[ select * from comments where comment_id > 0 order by comment_id desc ]]>
</select>
</mapper>