본문 바로가기

학원/복기

[MyBatis] 조인예제(3) / collection 엘리먼트, autoMapping 속성

[MYREPLY 테이블]

 

[MYUSER 테이블]

[MYCOMMENT] 테이블

 


이번에는 MYCOMMENT 테이블과 MYREPLY 테이블을 조인할 것이다

 

게시글번호를 전달받아 MYCOMMENT 테이블에 저장된 게시글정보(1개)와 MYREPLY 테이블에 저장된 댓글정보(0개 이상)를 검색하여 MyCommentReply 객체로 제공하는 엘리먼트를 등록할 것이다.

 

1:N 관계의 테이블 조인에서는 OUTER JOIN을 사용하여 검색해야 한다.

해당 예제에서는  LEFT OUTER JOIN을 사용해야한다. 그래야만 댓글이 없어도 게시물이 출력되기 때문이다 

 

List 객체를 생성하기 위해서는 collection 엘리먼트를 사용해야 한다. 

 

Collection 

 

collection은 1:N 관계의 테이블 조인에서 0개의 검색행을 List 객체로 생성하여 resultMap 엘리먼트의 type 속성값으로 설정된 클래스의 객체 필드에 저장되도록 매핑 처리하는 엘리먼트이다.

→ id 엘리먼트 또는 result 엘리먼트를 하위 엘리먼트로 사용하여 collection 엘리먼트로 생성될  List 객체의 요소(객체) 필드에 검색행의 컬럼값이 저장되도록 매핑 처리한다.

 - property 속성 : collection 엘리먼트로 생성된 List 객체가 저장될 클래스의 필드명을 속성값으로 설정한다.

 - ofType 속성 : List 객체의 요소로 저장될 객체의 Java 자료형을 속성값으로 설정한다.

  → Java 자료형 대신 typeAlias로 설정된 별칭 사용이 가능하다.

 

 

1:1 관계의 테이블 조인에서는 association, 1:N 관계의 테이블 조인에서는 collection 엘리먼트를 사용하면 된다.

 

[MyCommentMapper.xml]

	<resultMap type="MyCommentReply" id="myCommentReplyResultMap">
		<association property="comment" javaType="MyComment1">
			<id column="comment_no" property="commentNo"/>
			<result column="comment_id" property="commentId"/>
			<result column="comment_content" property="commentContent"/>
			<result column="comment_date" property="commentDate"/>
		</association>
		
		<collection property="replyList" ofType="MyReply">
			<id column="reply_no" property="replyNo"/>
			<result column="reply_id" property="replyId"/>
			<result column="reply_content" property="replyContent"/>
			<result column="reply_date" property="replyDate"/>
			<result column="reply_comment_no" property="replyCommentNo"/>
		</collection>
	</resultMap>
	
	<!-- 게시글번호를 전달받아 MYCOMMENT 테이블에 저장된 게시글정보(1개)와 MYREPLY 테이블에 저장된 
	댓글정보(0개 이상)를 검색하여 MyCommentReply 객체로 제공하는 엘리먼트 -->
	<!-- => 1:N 관계의 테이블 조인에서는 OUTER JOIN을 사용하여 검색 -->
	<select id="selectCommentReply" parameterType="int" resultMap="myCommentReplyResultMap">
		<!-- 댓글이 없는 게시글을 무조건 검색하기 위해 LEFT OUTER JOIN 사용 -->
		select comment_no, comment_id, comment_content, comment_date, reply_no, reply_id
			,reply_content,  reply_date, reply_comment_no from mycomment left join myreply 
			on comment_no=reply_comment_no where comment_no=#{commentNo} order by reply_no desc
	</select>

 


resultMap 엘리먼트의 하위 엘리먼트는 작성 순서가 존재한다.

→ constructor, id, result, association, collection, discriminator

 

문제점) resultMap 엘리먼트의 첫번째 하위 엘리먼트로 association 엘리먼트를 사용한 경우 내부적으로 selectOne() 메소드를 사용하여 하나의 검색행을 Java 객체로 생성하여 제공한다.

→ SELECT 명령으로 다수의 행이 검색된 경우 TooManyResultsException이 발생된다.

 

해결법) 다수의 행이 검색될 경우 resultMap 엘리먼트의 첫번째 자식 엘리먼트로 association 엘리먼트가 아닌 다른 엘리먼트를 사용해야 한다. 

 

첫번째 자식 엘리먼트로 id 엘리먼트를 사용해 보겠다.

	<resultMap type="MyCommentReply" id="myCommentReplyResultMap">
		<id column="comment_no" property="commentNo"/>
		
		<association property="comment" javaType="MyComment1">
			//...
           	 	//...
	</resultMap>

 

property 속성에 값을 주기 위해서 DTO 클래스에 필드를 새로 생성해주자

public class MyCommentReply {
	private int commentNo;
	
	//...
    	//...
}

 

인터페이스 매퍼

public interface MyCommentMapper {
	//...
    	//...
	MyCommentReply selectCommentReply(int commentNo);
}

 

DAO

	public MyCommentReply selectCommentReply(int commentNo) {
		SqlSession sqlSession=getSqlSessionFactory().openSession(true);
		try {
			return sqlSession.getMapper(MyCommentMapper.class).selectCommentReply(commentNo);
		} finally {
			sqlSession.close();
		}
	}

 

jsp

 

[commentUserListSelect2.jsp]

<%@page import="xyz.itwill.dao.MyCommentDAO"%>
<%@page import="xyz.itwill.dto.MyCommentUser2"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	List<MyCommentUser2> commentUserList=MyCommentDAO.getDAO().selectCommentUserList2();
%>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MYBATIS</title>
<style type="text/css">
table {
	border: 1px solid black;
	border-collapse: collapse;
}

td {
	border: 1px solid black;
	text-align: center;
	padding: 3px;
}

.no { width: 100px; }
.name { width: 150px; }
.content { width: 250px; }
.date { width: 200px; }
</style>
</head>
<body>
	<h1>게시글 목록</h1>
	<hr>
	<table>
		<tr>
			<td class="no">게시글번호</td>
			<td class="name">게시글작성자</td>
			<td class="content">게시글내용</td>
			<td class="date">게시글작성일</td>
		</tr>
		<% for(MyCommentUser2 commentUser : commentUserList) { %>
		<tr>
			<td><%=commentUser.getComment().getCommentNo() %></td>
			<td><%=commentUser.getUser().getUserName()%>[<%=commentUser.getUser().getUserId() %>]</td>
			<td>
				<a href="commentReplySelect2.jsp?commentNo=<%=commentUser.getComment().getCommentNo()%>">
					<%=commentUser.getComment().getCommentContent() %>
				</a>
			</td>
			<td><%=commentUser.getComment().getCommentDate() %></td>
		</tr>
		<% } %>
	</table>
</body>
</html>

 

[commentReplySelect2.jsp]

<%@page import="xyz.itwill.dto.MyReply"%>
<%@page import="xyz.itwill.dto.MyCommentReply"%>
<%@page import="java.util.List"%>
<%@page import="xyz.itwill.dao.MyCommentDAO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	if(request.getParameter("commentNo")==null) {//전달값(게시글번호)이 없는 경우
		//게시글목록을 출력하는 페이지로 이동
		response.sendRedirect("commentUserListSelect2.jsp");
		return;
	}

	//전달값(게시글번호)을 반환받아 저장
	int commentNo=Integer.parseInt(request.getParameter("commentNo"));
	
	//게시글번호를 전달받아 MYCOMMENT 테이블에 저장된 게시글과 MYREPLY 테이블에 저장된 
	//댓글목록을 검색하여 DTO 객체로 반환하는 DAO 클래스의 메소드 호출
	MyCommentReply commentReply=MyCommentDAO.getDAO().selectCommentReply(commentNo);
%>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MYBATIS</title>
<style type="text/css">
table {
	border: 1px solid black;
	border-collapse: collapse;
}

td {
	border: 1px solid black;
	text-align: center;
	padding: 3px;
}

.no { width: 100px; }
.name { width: 150px; }
.content { width: 300px; }
.date { width: 200px; }
.comment { width: 100px; }
</style>
</head>
<body>
	<h1>게시글과 댓글목록</h1>
	<hr>
	<%-- 게시글 출력 --%>
	<table>
		<tr>
			<td width="200">게시글번호</td>
			<td width="300"><%=commentReply.getComment().getCommentNo() %></td>
		</tr>
		<tr>
			<td width="200">게시글작성자</td>
			<td width="300"><%=commentReply.getComment().getCommentId() %></td>
		</tr>
		<tr>
			<td width="200">게시글내용</td>
			<td width="300"><%=commentReply.getComment().getCommentContent() %></td>
		</tr>
		<tr>
			<td width="200">게시글작성일</td>
			<td width="300"><%=commentReply.getComment().getCommentDate() %></td>
		</tr>
	</table>
	<br>
	
	<%-- 댓글목록 출력 --%>
	<table>
		<tr>
			<td class="no">댓글번호</td>
			<td class="name">댓글작성자</td>
			<td class="content">댓글내용</td>
			<td class="date">댓글작성일</td>
			<td class="comment">게시글번호</td>
		</tr>
		<% if(commentReply.getReplyList().isEmpty()) { %>
			<tr>
				<td colspan="5">댓글이 존재하지 않습니다.</td>
			</tr>
		<% } else { %>
			<% for(MyReply reply : commentReply.getReplyList()) { %>
			<tr>
				<td><%=reply.getReplyNo() %></td>
				<td><%=reply.getReplyId() %></td>
				<td><%=reply.getReplyContent() %></td>
				<td><%=reply.getReplyDate() %></td>
				<td><%=reply.getReplyCommentNo() %></td>
			</tr>
			<% } %>
		<% } %>
	</table>
</body>
</html>

 


테이블을 추가로 조인해보자.

 

 

DTO

 

MYCOMMENT 테이블과 MYUSER 테이블 및 MYREPLY 테이블(MYUSER)의 컬럼값을 저장하기 위한 클래스[MyCommentReplyUser] 선언 

package xyz.itwill.dto;

import java.util.List;

//MYCOMMENT 테이블과 MYUSER 테이블 및 MYREPLY 테이블(MYUSER 테이블)의 컬럼값을 저장하기 위한 클래스
public class MyCommentReplyUser {
	//MYCOMMENT 테이블(게시글)의 검색결과를 컬럼값으로 제공받아 저장하기 위한 필드 - 검색행 : 1개
	private int commentNo;
	private String commentId;
	private String commentContent;
	private String commentDate;
	
	//MYUSER 테이블(게시글에 대한 회원정보)의 검색결과를 객체로 제공받아 저장하기 위한 필드 - 검색행 : 1개
	private MyUser user;
	
	//MYREPLY 테이블(댓글정보)와 MYUSER(댓글에 대한 회원정보)의 검색결과를 요소로 저장된 List 객체로
	//제공받아 저장하기 위한 필드 - 검색행 : 0개 이상
	private List<MyReplyUser> replyUserList;
	
	public MyCommentReplyUser() {
		// TODO Auto-generated constructor stub
	}

	public int getCommentNo() {
		return commentNo;
	}

	public void setCommentNo(int commentNo) {
		this.commentNo = commentNo;
	}

	public String getCommentId() {
		return commentId;
	}

	public void setCommentId(String commentId) {
		this.commentId = commentId;
	}

	public String getCommentContent() {
		return commentContent;
	}

	public void setCommentContent(String commentContent) {
		this.commentContent = commentContent;
	}

	public String getCommentDate() {
		return commentDate;
	}

	public void setCommentDate(String commentDate) {
		this.commentDate = commentDate;
	}

	public MyUser getUser() {
		return user;
	}

	public void setUser(MyUser user) {
		this.user = user;
	}

	public List<MyReplyUser> getReplyUserList() {
		return replyUserList;
	}

	public void setReplyUserList(List<MyReplyUser> replyUserList) {
		this.replyUserList = replyUserList;
	}
}

 

xml 매퍼

 

[MyCommentMapper.xml]

 

게시글번호를 전달받아 MYCOMMENT 테이블에 저장된 게시글정보(1개)와 MYREPLY 테이블에 저장된 댓글정보(0개 이상)를 검색하여 MyCommentReplyUser 객체로 제공하는 엘리먼트를 등록한다.

 

	<resultMap type="MyCommentReplyUser" id="myCommentReplyUserResultMap">
		<id column="comment_no" property="commentNo"/>
		<result column="comment_id" property="commentId"/>
		<result column="comment_content" property="commentContent"/>
		<result column="comment_date" property="commentDate"/>
		
		<association property="user" javaType="MyUser">
			<id column="user_id" property="userId"/>
			<result column="user_name" property="userName"/>
		</association>
		
		<collection property="replyUserList" ofType="MyReplyUser">
			<association property="reply" javaType="MyReply">
				<id column="reply_no" property="replyNo"/>
				<result column="reply_id" property="replyId"/>
				<result column="reply_content" property="replyContent"/>
				<result column="reply_date" property="replyDate"/>
				<result column="reply_comment_no" property="replyCommentNo"/>
			</association>
			<association property="user" javaType="MyUser">
				<id column="reply_user_id" property="userId"/>
				<result column="reply_user_name" property="userName"/>
			</association>
		</collection>
	</resultMap>
	
	<!-- 게시글번호를 전달받아 MYCOMMENT 테이블에 저장된 게시글정보(1개)와 MYREPLY 테이블에 
	저장된 댓글정보(0개 이상)를 검색하여 MyCommentReplyUser 객체로 제공하는 엘리먼트를 등록 -->
	<select id="selectCommentReplyUser" parameterType="int" resultMap="myCommentReplyUserResultMap">
		select comment_no, comment_id, comment_content, comment_date, user_name
			, reply_no, comment_id, comment_content, reply_date, reply_comment_no, reply_user_id
			, reply_user_name from mycomment join myuser on comment_id=user_id left join 
			(select reply_no, reply_id, reply_content, reply_date, reply_comment_no
			, user_id reply_user_id, user_name reply_user_name from myreply join myuser 
			on reply_id=user_id) on comment_no=reply_comment_no where comment_no=#{commentNo}
			order by reply_no desc
	</select>

 

 

인터페이스 매퍼

public interface MyCommentMapper {
	//...
    //...
	MyCommentReplyUser selectCommentReplyUser(int commentNo);
}

DAO

	public MyCommentReplyUser selectCommentReplyUser(int commentNo) {
		SqlSession sqlSession=getSqlSessionFactory().openSession(true);
		try {
			return sqlSession.getMapper(MyCommentMapper.class).selectCommentReplyUser(commentNo);
		} finally {
			sqlSession.close();
		}
	}


JSP

[commentUserListSelect2.jsp]

<%@page import="xyz.itwill.dao.MyCommentDAO"%>
<%@page import="xyz.itwill.dto.MyCommentUser2"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	List<MyCommentUser2> commentUserList=MyCommentDAO.getDAO().selectCommentUserList2();
%>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MYBATIS</title>
<style type="text/css">
table {
	border: 1px solid black;
	border-collapse: collapse;
}

td {
	border: 1px solid black;
	text-align: center;
	padding: 3px;
}

.no { width: 100px; }
.name { width: 150px; }
.content { width: 250px; }
.date { width: 200px; }
</style>
</head>
<body>
	<h1>게시글 목록</h1>
	<hr>
	<table>
		<tr>
			<td class="no">게시글번호</td>
			<td class="name">게시글작성자</td>
			<td class="content">게시글내용</td>
			<td class="date">게시글작성일</td>
		</tr>
		<% for(MyCommentUser2 commentUser : commentUserList) { %>
		<tr>
			<td><%=commentUser.getComment().getCommentNo() %></td>
			<%-- <td><%=commentUser.getUser().getUserName()%>[<%=commentUser.getComment().getCommentId() %>]</td> --%>
			<td><%=commentUser.getUser().getUserName()%>[<%=commentUser.getUser().getUserId() %>]</td>
			<%-- <td><%=commentUser.getComment().getCommentContent() %></td> --%>
			<td>
				<%-- <a href="commentReplySelect1.jsp?commentNo=<%=commentUser.getComment().getCommentNo()%>"> --%>
				<%-- <a href="commentReplySelect2.jsp?commentNo=<%=commentUser.getComment().getCommentNo()%>"> --%>
				<a href="commentReplyUserSelect.jsp?commentNo=<%=commentUser.getComment().getCommentNo()%>">
					<%=commentUser.getComment().getCommentContent() %>
				</a>
			</td>
			<td><%=commentUser.getComment().getCommentDate() %></td>
		</tr>
		<% } %>
	</table>
</body>
</html>

[commentReplyUserSelect.jsp]

<%@page import="xyz.itwill.dto.MyReplyUser"%>
<%@page import="xyz.itwill.dao.MyCommentDAO"%>
<%@page import="xyz.itwill.dto.MyCommentReplyUser"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	if(request.getParameter("commentNo")==null) {//전달값(게시글번호)이 없는 경우
		//게시글목록을 출력하는 페이지로 이동
		response.sendRedirect("commentUserListSelect2.jsp");
		return;
	}

	//전달값(게시글번호)을 반환받아 저장
	int commentNo=Integer.parseInt(request.getParameter("commentNo"));
	
	//게시글번호를 전달받아 MYCOMMENT 테이블에 저장된 게시글과 MYREPLY 테이블에 저장된 
	//댓글목록을 검색하여 DTO 객체로 반환하는 DAO 클래스의 메소드 호출
	MyCommentReplyUser commentReplyUser=MyCommentDAO.getDAO().selectCommentReplyUser(commentNo);
%>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MYBATIS</title>
<style type="text/css">
table {
	border: 1px solid black;
	border-collapse: collapse;
}

td {
	border: 1px solid black;
	text-align: center;
	padding: 3px;
}

.no { width: 100px; }
.name { width: 150px; }
.content { width: 300px; }
.date { width: 200px; }
.comment { width: 100px; }
</style>
</head>
<body>
	<h1>게시글과 댓글목록</h1>
	<hr>
	<%-- 게시글 출력 --%>
	<table>
		<tr>
			<td width="200">게시글번호</td>
			<td width="300"><%=commentReplyUser.getCommentNo() %></td>
		</tr>
		<tr>
			<td width="200">게시글작성자</td>
			<td width="300"><%=commentReplyUser.getUser().getUserName()%>[<%=commentReplyUser.getCommentId() %>]</td>
		</tr>
		<tr>
			<td width="200">게시글내용</td>
			<td width="300"><%=commentReplyUser.getCommentContent() %></td>
		</tr>
		<tr>
			<td width="200">게시글작성일</td>
			<td width="300"><%=commentReplyUser.getCommentDate() %></td>
		</tr>
	</table>
	<br>
	
	<%-- 댓글목록 출력 --%>
	<table>
		<tr>
			<td class="no">댓글번호</td>
			<td class="name">댓글작성자</td>
			<td class="content">댓글내용</td>
			<td class="date">댓글작성일</td>
			<td class="comment">게시글번호</td>
		</tr>
		<% if(commentReplyUser.getReplyUserList().isEmpty()) { %>
			<tr>
				<td colspan="5">댓글이 존재하지 않습니다.</td>
			</tr>
		<% } else { %>
			<% for(MyReplyUser replyUser : commentReplyUser.getReplyUserList()) { %>
			<tr>
				<td><%=replyUser.getReply().getReplyNo() %></td>
				<td><%=replyUser.getUser().getUserName() %>[<%=replyUser.getReply().getReplyId() %>]</td>
				<td><%=replyUser.getReply().getReplyContent() %></td>
				<td><%=replyUser.getReply().getReplyDate() %></td>
				<td><%=replyUser.getReply().getReplyCommentNo() %></td>
			</tr>
			<% } %>
		<% } %>
	</table>
</body>
</html>

더 쉬운 방법

 

autoMapping 속성

 

resultMap 엘리먼트의 autoMapping 속성을 사용하여 더 쉽게 작성할 수 있다.

위의 예제에서 작성했던 하나의 select를 두개로 분리시켜 볼것이다.

 

autoMapping 속성 : false 또는 true 중 하나를 속성값으로 설정한다.

autoMapping 속성값을 [true]로 설정한 경우 검색행의 컬럼과 필드명이 같은 경우 자동 매핑 처리한다.

 

association 엘리먼트에도 autoMapping 속성 사용 가능

 

xml 매퍼 

 

[MyCommentMapper.xml]

	<resultMap type="MyCommentReplyUser" id="myCommentReplyUserResultMap" autoMapping="true">
		<id column="comment_no" property="commentNo"/>
		<association property="user" javaType="MyUser" autoMapping="true"/>
		<collection property="replyUserList" select="selectReplyUser" column="comment_no"/>
	</resultMap>
	
	<resultMap type="MyReplyUser" id="myReplyUserResultMapper">
		<association property="reply" javaType="MyReply" autoMapping="true"/>
		<association property="user" javaType="MyUser" autoMapping="true"/>
	</resultMap>
	
	<select id="selectReplyUser" parameterType="int" resultMap="myReplyUserResultMapper">
		select reply_no, reply_id, reply_content, reply_date, reply_comment_no, user_id
			, user_name from myreply join myuser on reply_id=user_id where 
			reply_comment_no=#{replyCommentNo} order by reply_no desc
	</select>
	
	<select id="selectCommentReplyUser" parameterType="int" resultMap="myCommentReplyUserResultMap">
		select comment_no, comment_id, comment_content, comment_date, user_id, user_name
			, reply_no, reply_id, reply_content, reply_date, reply_comment_no from
			mycomment join myuser on comment_id=user_id left join myreply on 
			comment_no=reply_comment_no where comment_no=#{commentNo} order by reply_no desc
	</select>