클라우드 융합 Full-stack 웹 개발자 양성과정/Spring

MyBatis - 환경구성, 로그인, 회원가입기능, 게시글리스트 조회, 게시글 상세조회

thesunset 2022. 12. 17. 17:48

# 환경구성

1. 워크스페이스 만들기

   c드라이브에 

새로운 워크스페이스 생성

2.  DB 설정

- 관리자 계정에서 계정 만들기 

create user mybatis identified by mybatis;

grant resource, connect to mybatis;

- 계정에 접속해 테이블생성하기

슬랙-공지-Table Scripts.sql

3. mybatis.jar파일 다운로드 

 

=> lib폴더에 넣어주기

 

폴더 구성

# 회원가입

<index.jsp>

* 프레임워크
- 개발자가 보다 편리한 환경에서 개발할 수 있도록 제공하는 뼈대, 틀이라고 생각
- 소프트웨어 개발의 입장에서는 공통으로 사용하는 라이브러리/개발도구/인터페이스 등등

* 프레임워크의 필요성
- 웹 프로그램의 규모가 커지고 있음
=> 거대하고 복잡도가 높은 프로젝트를 완성시키기 위해 많은 사람들이 필요함
=> 개발자들이 "통일성"있게 "빠르고", "안정적"으로 개발하기 위한 한 가지 방법으로 프레임워크가 좋은 성과를 내고 있음
=> 생산성 향상에 큰 도움이 된다.

* 프레임워크의 특징
- 자유롭게 설계하고 코드를 짜는 것이 아니라 프레임워크가 제공하는 가이드대로 설계하고 코드를 짜야함(셋팅도)
- 개발할 수 있는 범위가 정해져있음
- 개발자를 위한 다양한 도구 / 플러그인들이 지원됨

* 프레임 워크의 장단점
> 장점 : 개발시간을 줄일 수 있음
오류로부터 자유로워질 수 있음

> 단점 : 너무 의존하다보면 개발능력이 떨어져서 프레임워크 없이 개발하는 것이 어려워짐
습득하는데까지 시간이 오래걸릴 수 있음

* 프레임워크의 종류 
영속성 : 데이터관련한(CRUD)기능들을 편리하게 작업할 수 있도록 하는 프레임워크
ex) MyBatis, Hibernate..
- 자바 : 웹 어플리케이션에 초점에 맞춰 필요한 요소들을 모듈화해서 제공하는 프레임워크
ex) Spring, Struts
- 화면 구현 : Front-end를 보다 쉽게 구현할 수 있게 틀을 제공해주는 프레임워크
ex) Bootstrap..
- 기능 및 지원 : 특정 기능이나 업무수행에 도움을 주는 기능을 제공해주는 프레임워크
ex) Log4j, JUnit... 

 

 

<index.jsp>

WEB-INF 파일 밑에 있는 구성들은 직접접근이 불가능 함

 

예 http://localhost:8004/mybatis/WEB-INF/views/main.jsp

와 같은 경로로 접속할 수 없음

 

그럼 어떻게 접근해야할까?

=> index.jsp에 main페이지를 포워딩 해주기

<jsp:forward page="WEB-INF/views/main.jsp"/>

 url은 그대로지면 실제 화면은 내가 포워딩한 페이지로 나옴

** 이전시간에 배웠던 포워딩 활용방법

<main.jsp>

<jsp:include page="common/menubar.jsp"/>

- menubar include해주기

** 절대경로/상대경로 다시 공부하기

 

<menubar.jsp>

<c:choose>
<c:when test="${empty loginUser}">
<!-- case1. 로그인 전 -->
    <form action="" method="post">
        <table>
            <tr>
                <td>아이디</td>
                <td><input type="text" name="userId" required/></td>
                <td rowspan="2"> <button type="submit" style="height: 60px;">로그인</button></td>
            </tr>
            <tr>
                <td>비밀번호</td>
                <td><input type="password" name="userPwd" required></td>
            </tr>
            <tr>
                <td colspan="3" align="center">
                    <a href="enrollForm.me">회원가입</a>
                </td>
            </tr>
        </table>
    </form>
   </c:when>

    <c:otherwise>
<!-- case2. 로그인 후 -->
        <table>
        <tr>
            <td colspan="2">
                    <h3>~님 환영합니다.</h3>
            </td>
        </tr>
        <tr>
            <td><a href="">마이페이지</a> </td>
        </tr>
    </table>
    </c:otherwise>
</c:choose>

- 조건문을 사용해서 비회원/ 회원 상태에 따라 화면 다르게 지정 

** 주의 choose와 주석 함께 쓰지 않기

 

"회원가입"버튼 클릭 시 enrollForm.me매핑값으로 이동

request.getRequestDispatcher("WEB-INF/views/member/memberEnrollForm.jsp").forward(request, response);

<WEB-INF/views/member/memberEnrollForm.jsp>

<form action="insert.me" method="post">
    <table align="center">
        <tr>
            <td>* ID</td> 
            <td><input type="text" name="userId" required></td>
        </tr>
        <tr>
            <td>* PWD</td>
            <td><input type="password" name="userPwd" required></td>
        </tr>
        <tr>
            <td>* NAME</td>
            <td><input type="text" name="userName" required></td>
        </tr>
        <tr>
            <td>&nbsp;&nbsp;EMAIL</td>
            <td><input type="email" name="email"></td>
        </tr>
        <tr>
            <td>&nbsp;&nbsp;BIRTHDAY</td>
            <td><input type="text" name="birthday" placeholder="생년월일(6자리)"></td>
        </tr>
        <tr>
            <td>&nbsp;&nbsp;GENDER</td>
            <td align="center">
                <input type="radio" name="gender" value="M" checked> 남
                <input type="radio" name="gender" value="F"> 여
            </td>
        </tr>
        <tr>
            <td>&nbsp;&nbsp;PHONE</td>
            <td><input type="text" name="phone" placeholder="-포함"></td>
        </tr>
        <tr>
            <td>&nbsp;&nbsp;ADDRESS</td>
            <td><input type="text" name="address"></td>
        </tr>
    </table>
    <br>
    <div align="center">
        <button type="reset">초기화</button>
        <button type="submit">회원가입</button>
    </div>
</form>

submit버튼을 누르면 input태그에 담긴 value값과 함께 form태그의 action속성에 쓰인 매핑값으로 이동

 

<MemberInsertController> : /insert.me

request.setCharacterEncoding("UTF-8");

Member m = new Member();
m.setUserId(request.getParameter("userId"));
m.setUserPwd(request.getParameter("userPwd"));
m.setUserName( request.getParameter("userName"));
m.setEmail(request.getParameter("email"));
m.setBirthDay(request.getParameter("birthDay"));
m.setGender(request.getParameter("gender"));
m.setPhone(request.getParameter("phone"));
m.setAddress(request.getParameter("address"));


if(new MemberServiceImpl().insertMember(m)>0) {
    response.sendRedirect(request.getContextPath());
}else { //실패했을 경우 에러페이지(에러문구 담아서)
    request.setAttribute("errorMsg", "회원가입실패");
    request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp");
}

<MemberService>

앞으로 Service는 인터페이스와 인터페이스 구현 클래스를 만들 것.

이유는 ?

=> 유지보수를 위해 분리하기 ! 메소드들의 틀을 미리 만들어 개발자 간의 의사소통 혼선을 줄여주고 다형성 개발의 유리함을 가져올 수 있음 !

 

 

* 인터페이스 : 상수필드(public static final) + 추상메소드(public abstract)

public interface MemberService {

	//회원가입용 메소드
	int insertMember(Member m);
	
	//로그인용 메소드
	Member loginMember(Member m);
	
	
	
	//회원정보수정용 메소드
	int updateMember(Member m);
	
	//회원탈퇴용 메소드
	int deleteMember(Member m);
	
	
	
}

<MemberServiceImple>

인터페이스 구현 클래스

항상 매개변수는 한 개 이상 받지 않는다 ** 만약 여러개면 어디에 담아서 하나로 보내야함

왜???? => 오버라이딩때문에?..

private MemberDao memberDao = new MemberDao();


@Override
public int insertMember(Member m) {

    SqlSession sqlSession = Template.getSqlSession();

    int result = memberDao.insertMember(sqlSession, m);

    if(result>0) sqlSession.commit();

//		트랜잭션이 애초에 생기지 않기 떄문에 할 필요 없음

    sqlSession.close();

    return result;
}

 

앞으로 모든 mybatis관련 파일들은 소스파일폴더인 

이곳에 넣을 것!!

 

XML파일 만들기

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">

 이 문서의 형식이 contiguration(=환경설정)임을 알려줌
 => configuration 태그가 전체를 감싸고 있음
 DTD : 유효성을 체크해줌 (내부 태그들이 configuration태그 안에 존재할 수 있는지 체크)

- < contiguration> 태그로 감싸져있음, 

- setting : MyBatis를 구동 시 설정들을 작성하는 영역

<configuration>

    <settings> 
<!-- 만약 null로 데이터가 전달이 되었다면 빈칸이 아닌 NULL로 인식하겠다.(무조건 대문자 NULL로 작성해야함) -->
        <setting name="jdbcTypeForNull" value="NULL" />
    </settings>

- typeAlias : VO/DTO 클래스들의 풀클래스명을 단순한 클래스명으로 사용하기위해서 별칭을 등록할 수 있는 영역

<typeAliases>
    <typeAlias type="com.kh.mybatis.member.model.vo.Member" alias="member"/>
    <typeAlias type="com.kh.mybatis.board.model.vo.Board" alias="board"/>
    <typeAlias type="com.kh.mybatis.board.model.vo.Reply" alias="Reply"/>
</typeAliases>

- environment : MyBatis에서 연동할 DB정보들을 등록하는 영역 (여러개의 DB정보를 등록 가능)
  => default속성으로 여러개의 id중 어떤 DB를 기본 DB로 사용할 건지 꼭 지정을 해줘야한다.

-  transactionManeger는 JDBC와 MANAGED 둘 중 하나를 선택할 수 있음
   JDBC : 트랜잭션을 내가 직접 관리하겠다 (수동 commit)
  MANAGED : 개발자가 트랜잭션에 대한 어떤 양행도 행사하지 않겠다 (자동 commit)

<environments default="development">
    <environment id="development">
     <transactionManager type="JDBC"/>

dataSource로는 POOLED와 UPPOOLED 둘 중 하나를 선택할 수 있음(ConnectionPool 사용여부)
  > ConnectionPool : Connection 객체를 담아둘 수 있는 영역
    한 번 생성된 Connection객체를 담아두면 재사용해서 쓸 수 있음
    => POOLED : ConnectionPool 사용
    => UNPOOLED : ConnectionPool 사용X

     <dataSource type="POOLED">
        <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
        <property name="username" value="mybatis"/>
        <property name="password" value="mybatis"/>
     </dataSource>

    </environment>
</environments>

- mapper:실행할 sql문들을 기록해둔 mapper파일 등록하는 영역

<mappers>
    <mapper resource = "/mappers/member-mapper.xml"/>
    <mapper resource = "/mappers/board-mapper.xml"/>
</mappers>

주의** 꼭 만들고 나서!!!! 등록하기 (위에 VO도 마찬가지)

 

<com.kh.mybatis.member.model.dao>

sqlSession에서 제공하는 메소드를 통해 sql문을 찾아서 실행하고 결과를 바로 받아볼 수 있다.
sqlSession.sql문 종류에 맞는 메소드("mapper파일의 namespace.해당sql문의 고유한 id");

public int insertMember(SqlSession sqlSession, Member m) {
	return sqlSession.insert("memberMapper.insertMember", m);
    }

 

<memberMapper>

 * DML문일 경우
   <insert id="각 sql문의 식별자" parameterType="전달받을자바타입(풀클래스명) 혹은 별칭">
   쿼리문 작성
   </insert>
   => parameterType속성은 전달받을 값이 없다면 생략 가능하다
   <update></update>
   <delete></delete>
   모두 int로 돌아오기 때문에 따로 작성할 필요없음
  
  * SELECT문일 경우
<select id="각 sql문의 식별자" parameterType="전달받은 자바타입(풀클래스명) 혹은 별칭"
resultType="조회결과를 반환하고자 하는 자바 타입" 또는 resultMap="조회결과를 뽑아서 매핑할 resultMap의 id">
쿼리문 작성
</select>  
   => parameterType속성은 전달받을 값이 없다면 생략이 가능하다.
   => 반드시 resultType(자바에서 제공하는 자료형) 또는 resultMap(내가 만든 VO클래스 타입)으로 결과에 대한 타입을 지정해야 함
   => 왜? SELECT문의 결과는 항상 어떤 SELECT문이냐에 따라서 다를 수 있기 때문에 (COUNT()를 쓰면 정수/name만 조회하면 문자열)
    
     그동안은 pstmt객체를 이용해서 ?(위치홀더)를 사용했지만,
     앞으로는 위치홀더 대신 해당 SQL문에 전달된 객체로부터 값을 꺼내서 사용
     꺼내는 방법 #{필드명 또는 변수명 또는 map의 키값)을 이용

 

namespace : 해당 mapper의 고유한 별칭 전체가 mapper태그로 감싸져 있어야 한다.

<mapper namespace="memberMapper">

 	 <insert id="insertMember" parameterType="member">
  	 	INSERT
  	 		INTO
  	 			MEMBER
  	 			(
  	 			USER_NO,
  	 			USER_ID,
  	 			USER_PWD,
  	 			USER_NAME,
  	 			EMAIL,
  	 			BIRTHDAY,
  	 			GENDER,
  	 			PHONE,
  	 			ADDRESS
  	 			)
  	 	VALUES
  	 			(
  	 			SEQ_UNO.NEXTVAL,
  	 			#{userId},
  	 			#{userPwd},
  	 			#{userName},
  	 			#{email},
  	 			#{birthDay},
  	 			#{gender},
  	 			#{phone},
  	 			#{address}
  	 			)
  	 			<!-- 내부적으로 getter 메소드를 불러옴 -->
  	 </insert>

# 로그인

<form action="login.me" method="post">
    <table>
        <tr>
            <td>아이디</td>
            <td><input type="text" name="userId" required/></td>
            <td rowspan="2"> <button type="submit" style="height: 60px;">로그인</button></td>
        </tr>
        <tr>
            <td>비밀번호</td>
            <td><input type="password" name="userPwd" required></td>
        </tr>
        <tr>
            <td colspan="3" align="center">
                <a href="enrollForm.me">회원가입</a>
            </td>
        </tr>
    </table>
</form>

아이디와 비밀번호를 입력 후 submit버튼 클릭 시 login.me로 요청

 

<LoginController> /login.me

Member m = new Member();
m.setUserId(request.getParameter("userId"));
m.setUserPwd(request.getParameter("userPwd"));

Member loginUser = new MemberServiceImpl().loginMember(m);
//unique제약조건이 걸려있기때문에 member로 돌려줄 수 밖에 없음

if(loginUser == null) {
    request.setAttribute("errorMsg", "로그인실패");
    request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp").forward(request, response);
}else {
    request.getSession().setAttribute("loginUser", loginUser);
    response.sendRedirect(request.getContextPath());
}

<MemberServiceImpl>

@Override
public Member loginMember(Member m) {

    SqlSession sqlSession = Template.getSqlSession();

    Member loginUser = memberDao.loginUser(sqlSession, m);		

    sqlSession.close();

    return loginUser;
}

<MemberDao>

- selectOne() : 조회결과가 없다면 null반환 ***=> 초기화 안해도된

- selectOne이나 selectList 딱 두가지 기억하기 !!
조회결과가 하나 돌아온다면 selectOne, 두개 이상이면 selectList

public Member loginUser(SqlSession sqlSession, Member m) {

    return sqlSession.selectOne("memberMapper.loginUser", m);
}

<member-Mapper>

 * resultMap : Mybatis의 핵심 기능 중 하나
   ResultSet으로부터 조회된 컬럼값을 하나씩 뽑아서
   내가 지정한 VO객체에 각 필드에 담는 JDBC코드를 줄여주는 역할 수행
  
   <resultMap id="식별자" type="조회된 결과를 담아서 반환하고자하는 VO객체의 타입(풀클래스명) 또는 별칭">
   <result column="조회결과를 뽑고자하는 DB컬럼명" property="해당 결과를 담고자 하는 필드명" />
   <result column="조회결과를 뽑고자하는 DB컬럼명" property="해당 결과를 담고자 하는 필드명" />
...
   </resultMap>

 <resultMap id="MemberResultSet" type="member">
    <result column="USER_NO" property="userNo"/>
    <result column="USER_ID" property="userId"/>
    <result column="USER_PWD" property="userPwd"/>
    <result column="USER_NAME" property="userName"/>
    <result column="EMAIL" property="email"/>
    <result column="BIRTHDAY" property="birtyDay"/>
    <result column="GENDER" property="gender"/>
    <result column="PHONE" property="phone"/>
    <result column="ADDRESS" property="address"/>
    <result column="ENROLL_DATE" property="enrollDate"/>
    <result column="MODIFY_DATE" property="modifyDate"/>
    <result column="STATUS" property="status"/>
 </resultMap>

=>  내부적으로 필드에 대한 setter메소드를 호출

<select id="loginUser" parameterType="member" resultMap="MemberResultSet">

만약, resultType을 사용하면?

<select id="loginUser" parameterType="member" resultType="member">
    SELECT 
            USER_NO userNo,
            USER_ID userId,
            USER_PWD userPwd,
            USER_NAME userName,
            EMAIL email,
            BIRTHDAY birthDay,
            GENDER gender,
            PHONE phone,
            ADDRESS address,
            ENROLL_DATE enrollDate,
            MODIFY_DATE modifyDate,
            STATUS status  	
    FROM
         MEMBER
    WHERE
        USER_ID = #{userId}
    AND 
        USER_PWD = #{userPwd}
    AND 
        STATUS = 'Y'

</select>

# 일반게시글 리스트 조회

<menubar.jsp>

<div class="menu" onclick="location.href='list.bo?currentPage=1'">게시판</div>

onclick속성을 줘서 요청 보내기 (페이징바 처리를 위해서 currentPage도 쿼리스트링으로 함께 보내기)

<BoardListController> : /list.bo

--------------------페이징 처리-----------------------

int listCount = new BoardServiceImpl().selectListCount();
int currentPage = Integer.parseInt(request.getParameter("currentPage"));
int pageLimit = 10;
int boardLimit = 5;

PageInfo pi = Pagination.getPageInfo(listCount, currentPage, pageLimit, boardLimit);

maxPage, startPage, endPage는 위의 변수들로 구하는 것.

공식은 같기에 협업할 때 템플릿을 만들어놓고 함께 사용하기 (**파이널 때 참고)

<Pageination>

public static PageInfo getPageInfo(int listCount, int currentPage, int pageLimit, int boardLimit)  {

    int maxPage = (int)Math.ceil(((double)listCount/boardLimit));
    int startPage = (currentPage-1)/pageLimit * pageLimit + 1;
    int endPage = startPage + pageLimit - 1;

    if(endPage > maxPage) {
        endPage = maxPage;
    }
    PageInfo pi = new PageInfo(listCount, currentPage, pageLimit, boardLimit,
                                maxPage, startPage, endPage);

    return pi;
}

구한 페이징바 정보를 가지고 BoardList조회하러 가기

ArrayList<Board> list = new BoardServiceImpl().selectList(pi);

    request.setAttribute("list", list);
    request.setAttribute("pi", pi);

    request.getRequestDispatcher("WEB-INF/views/board/boardListView.jsp").forward(request, response);

왜 어떤건 if(list == null) 처리해주고 안해주지?

 

<BoardService> : 인터페이스

public interface BoardService {

	//게시글 관련 서비스
	int selectListCount();
	
	ArrayList<Board> selectList(PageInfo pi);
	
	int increaseCount(int BoardNo);
	
	Board selectBoard(int BoardNo);
	
	ArrayList<Reply> selectReplyList(int boardNo);
	
}

<BoardServiceImpl> : 인터페이스 구현

public class BoardServiceImpl implements BoardService{

	private BoardDao boardDao = new BoardDao();
	
	@Override
	public int selectListCount() {
		 SqlSession sqlSession = getSqlSession();
		 
		 int listCount = boardDao.selectListCount(sqlSession);
		 
		 sqlSession.close();
		
		return listCount;
	}

	@Override
	public ArrayList<Board> selectList(PageInfo pi) {
		SqlSession sqlSession = getSqlSession();
		
		ArrayList<Board> list = boardDao.selectList(sqlSession, pi);		
		
		sqlSession.close();
		
		return list;
	}

<BoardDao>

 

myBatis에서 페이징 처리를 위해서 RowBounds라는 클래스를 제공

* offset : 몇 개의 게시글을 건너뛰고 조회할건지 대한 값

ex) boardLimit이 5일 경우 
offset(건너뛸 숫자)  boardLimit(조회할 숫자)
currentPage : 1 => 1~5 0 5
currentPage : 2 => 6~10 5 5
currentPage : 3 => 11~14 10 5

currentPage-1*boardLimit = offset

public int selectListCount(SqlSession sqlSession) {

    return sqlSession.selectOne("boardMapper.selectListCount");
}

public ArrayList<Board> selectList(SqlSession sqlSession, PageInfo pi) {

	int offset = (pi.getCurrentPage()-1)*pi.getBoardLimit();
		
    RowBounds rowBounds = new RowBounds(offset, pi.getBoardLimit());
    
return (ArrayList)sqlSession.selectList("boardMapper.selectList",null, rowBounds);
	
	}

RowBounds객체를 넘겨야 할 경우
selectList메소드의 오버로딩된 형태 중 매개변수가 3개인 메소드를 반드시 사용해야 함
딱히 두 번쨰 매개변수 자리에 넘길 값이 없다면 null을 넘겨줌 / null"값"

<mapper namespace="boardMapper">
	<resultMap id="boardResultSet" type="board">
		<result column="BOARD_NO" property="boardNo"/>
		<result column="BOARD_TITLE" property="boardTitle"/>
		<result column="USER_ID" property="boardWriter"/>
		<result column="COUNT" property="count"/>
		<result column="CREATE_DATE" property="createDate"/>
		<result column="BOARD_CONTENT" property="boardContent"/>
	</resultMap>
<!-- 컬럼에 있어야 찾아가기 때문에 내가 조회한 값이 없어도 쓸 수 있음 -->

	<select id="selectListCount" resultType="_int">
		SELECT 
				COUNT(*)
		FROM 	
			BOARD
		WHERE 
			STATUS = 'Y'
	</select>
	<select id="selectList" resultMap="boardResultSet">
	SELECT 
        BOARD_NO,
        BOARD_TITLE,
        USER_ID,
        COUNT,
        CREATE_DATE
	FROM 
		BOARD B
	JOIN
		MEMBER ON(BOARD_WRITER = USER_NO)
	WHERE B.STATUS = 'Y'
	ORDER
		BY
			BOARD_NO DESC
	
	</select>

이전 방법과 차이점 : 모두 다 조회해와서 이곳에서 잘라서 넘겨줌(DB가 아니라) but,데이터가 많으면 부하가 발생할 수 있음

return (ArrayList)sqlSession.selectList("boardMapper.selectList",null, rowBounds);

# 게시글 상세 조회

<td><a href="detail.bo?bno=${b.boardNo}">${b.boardTitle }</a></td>

=> 글 제목을 클릭 시 detail.bo로 이동하도록 a태그로 지정

 

<BoardDetailController> : / detail.bo

int boardNo = Integer.parseInt(request.getParameter("bno"));

BoardService boardService = new BoardServiceImpl();

다형성 (부모-자식) boardService는 추상메소드만 가지고 있음 유지보수를 줄일 수 있음 (어차피 모든 메소드를 오버라이딩 해야하기 떄문에 기능은 그대로 있고 수정한 부분만 반영됨.)

 

1. 조회수를 증가시키는 서비스

2. 해당 게시글을 상세조회하는 서비스

if(boardService.increaseCount(boardNo)>0) {

    request.setAttribute("count", boardService.increaseCount(boardNo));

    request.setAttribute("b",boardService.selectBoard(boardNo) );

- 조회수 증가와 게시글 조회

3. 해당 게시글에 딸린 댓글들을 조회하는 서비스

request.setAttribute("list",boardService.selectReplyList(boardNo) );

request.getRequestDispatcher("WEB-INF/views/board/boardDetailView.jsp").forward(request, response);

4. 조회실패 시 

}else {
    request.setAttribute("errorMsg", "게시글 상세조회 실패");
    request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp").forward(request, response);
}

<BoardServiceImpl>

@Override
public int increaseCount(int boardNo) {

    SqlSession sqlSession = getSqlSession();
    int result = boardDao.increaseCount(sqlSession, boardNo);

    if(result>0) sqlSession.commit();
    sqlSession.close();

    return result;
}

@Override
public Board selectBoard(int boardNo) {

    SqlSession sqlSession = getSqlSession();

    Board b = boardDao.selectBoard(boardNo, sqlSession);

    sqlSession.close();

    return b;
}

@Override
public ArrayList<Reply> selectReplyList(int boardNo) {
     SqlSession sqlSession = getSqlSession();

     ArrayList<Reply> Rlist = boardDao.selectReplyList(sqlSession, boardNo);

     sqlSession.close();

    return Rlist;
}

<BoardDao>

public int increaseCount(SqlSession sqlSession, int boardNo) {

    return sqlSession.update("boardMapper.increaseCount", boardNo);
}

public Board selectBoard(int boardNo, SqlSession sqlSession) {

    return sqlSession.selectOne("boardMapper.selectBoard", boardNo);
}

public ArrayList<Reply> selectReplyList(SqlSession sqlSession, int boardNo) {

    return (ArrayList)sqlSession.selectList("boardMapper.selectReplyList", boardNo);
}

<board-mapper.xml>

<resultMap id="replyResultSet" type="reply">
    <result column="REPLY_NO" property="replyNo"/>
    <result column="REPLY_CONTENT" property="replyContent"/>
    <result column="REF_BNO" property="refBno"/>
    <result column="USER_ID" property="replyWriter"/>
    <result column="CREATE_DATE" property="createDate"/>

</resultMap>
<update id="increaseCount" parameterType="_int">
    UPDATE 
          BOARD
    SET	
        COUNT = COUNT + 1
    WHERE 
        BOARD_NO = #{boardNo}
    AND 
        STATUS = 'Y'
</update>
<select id="selectBoard" parameterType="_int" resultMap="boardResultSet">
    SELECT
            BOARD_NO,
            BOARD_TITLE,
            USER_ID,
            COUNT,
            CREATE_DATE,
            BOARD_CONTENT
    FROM
        BOARD B
    JOIN
        MEMBER ON(BOARD_WRITER = USER_NO)
    WHERE
        BOARD_NO = #{boardNo}
    AND 
        B.STATUS = 'Y'
</select>
<select id="selectReplyList" parameterType="_int" resultMap="replyResultSet">
    SELECT 
            REPLY_NO,
            REPLY_CONTENT,
            REF_BNO,
            USER_ID,
            CREATE_DATE
    FROM
        REPLY R
    JOIN
        MEMBER ON(USER_NO = REPLY_WRITER)
    WHERE 
        REF_BNO = #{boardNo}
    AND 
        R.STATUS = 'Y'
</select>