스프링 프레임워크를 적용한 프로젝트를 진행한다면


여러가지 프로젝트를 사용 할 수 있고


각각의 프로젝트의 라이브러리 버전이 상이 할 수 있다.


그러므로 Workspace마다 repository(라이브러리 경로)가 겹친다면 버전에 충돌이 생기고


실행시 오류가 발생할 수 있다.


그러므로 workspace마다 경로를 다르게 설정하는걸 추천한다.


설정은 다음과 같이 진행한다.





1. 관련 파일 다운로드, 아래 사이트를 방문


https://maven.apache.org/download.cgi


메이븐 홈페이지에서 Maven 툴을 새로 받자.






Files에서 Binary zip 파일을 받고 원하는 경로에 압축을 해제하자.


해제하면 다음과 같이 여러개를 만들어서 쓰면 된다.


스크린샷의 경우 default 경로를 아예 apache-maven-3.6.0로 잡고


새 프로젝트에서는 apache-maven-3.6.0-final 경로를 사용한다.





폴더를 들어가서






사진과 같이 repository 폴더를 생성하자, 이쪽에 라이브러리를 저장하도록 설정할 것이다.


여기서 conf 폴더로 들어가 설정을 바꾼다.






여기서 settings 파일을 열어서


(윈도우 기본 텍스트 에디터로 안 바뀔 수 있으므로 notepad++ 라는 프로그램 사용을 권장)


<localRepository>D:\Dev\apache-maven-3.6.0\repository</localRepository>


위의 태그를 아무대나 localRepository라는 태그 + 아까 추가한 repository 경로를 입력하고 저장하자.






이제 이클립스를 통해 settings 파일을 잡아주면 라이브러리 경로가 해당 경로로 바뀌게 된다.


상단 메뉴바의 Window - Preferences를 열고




settings를 검색하면 Maven의 하위 메뉴에 라이브러리 임의 경로를 설정할 수 있다.


user Setting을 잡아주면


Local Repository가 아까 xml 파일에 입력한 경로로 바뀔것이다.


기본경로는 예전에 소개한 .m2 / repository로 되어있을 것이다.












먼저 Spring Legacy Project를 생성하면 위와 같은 구조로 프로젝트가 생성이 된다.


주목할 부분만 펼쳐서 스크린샷을 찍었다.


스크린샷에서 빨간색 네모 박스는 직접 추가한 폴더






1. 크게 프로젝트는 src 폴더에 코드를 작성하게 된다.


작성한 프로젝트를 Tomcat에 올려서 실행하면 컴파일 되어


target이란 폴더에 저장된다.





2. Class 파일은 src - main - java 아래의


패키지 구조(프로젝트 생성 시 설정한 com.회사명.프로젝트명)로 이루어져 있다.


가장 하단 - MVC 패턴인 분류 - Controller

                                                                                        Model - Dao / Exception / Service / Vo


위의 구조로 생성하게 된다.







3. 프로젝트의 설정들이 저장될 폴더



객체 연결(root-context.xml)이나 로그(log4j.xml) 또는 DB와 관련된 설정 파일들 (/src/main/resources)


뷰에 대한 설정 파일들 js, css, 기본 이미지 폴더 등(/src/main/webapp/resources)


페이지, 프로젝트 연결 설정과 같은 xml 파일들 (/src/main/webapp/WEB-INF/config)


/src/main/webapp/WEB-INF/web.xml는 Tomcat 서버의 기본 설정들이 저장된다.(기본 페이지 경로와 같은 설정들)


pom.xml는 Maven이라는 툴을 이용한 라이브러리 관리 설정들






4. 라이브러리 관리 부연 설명


먼저 가장 중요한 Maven을 통해 Library 관리인데


프로젝트 내 pom.xml 이라는 파일을 열어보면 라이브러리,


버전 또한 이곳에 명시하여 자동으로 다운로드 해준다.


예를들어 pom.xml 파일을 열고 하단의 탭에서 pom.xml 탭을 선택하면


해당 파일의 설정 코드로 볼 수 있다. 이곳에서



dependency라는 태그로 라이브러리를 관리한다.


버전 또한 원하는 버전으로 위와같이 쓰인다.


원하는 라이브러리를 추가 할 때는 예전에 소개한 https://mvnrepository.com/ 이 사이트에서 검색해서


<dependency> 태그를 복사해서 적당한 위치에 붙여넣기 하고 파일을 저장하면 자동으로 다운받아진다.


하지만 이곳에서 다운로드 중 오류가 많이 발생하므로 주의해야한다.


문제가 생기면 톰캣이 안켜지거나 Problem View(탭)에 경고 메세지가 뜰 것이다.


이럴 땐 https://qdgbjsdnb.tistory.com/230?category=733876 설치 방법에서 설명한 방법으로


Eclipse를 종료하고





위의 경로에 있는 오류가 발생한 폴더(다운받은 라이브러리)를 삭제하고 이클립스를 다시 키고


(자동으로 다시 라이브러리를 다운 받지만 업데이트를 하는걸 추천)


프로젝트 우클릭 - Maven - Update Project를 누르고 ( 단축키 : 알트 + F5 )


나오는 창에서 바로 OK를 누르면 다운로드가 진행된다.


다시 다운받은 파일이 또 오류가 날 수 있다. 그러면 다시 반복하면 된다.










이클립스의 workspace를 새로 설정


window - Preferences의 설정에서 인코딩 설정


General - Workspace


General - Editors - Text Editors - Spelling


JSON - JSON Files


Web - CSS Files


Web - HTML Files


Web - JSP Files


XML - XML Files


위의 7가지를 전부 UTF-8로 인코딩을 설정한다.





JSP 파일을 생성할 때 구지 필요없는 문구들이 생성되는데


이는 Web - JSP Files - Editor - Templates에서


New JSP File(html)을 눌러서 우측의 Edit 버튼을 누르면


나오는 파일 내용을


<%@ page language="java" contentType="text/html; charset=${encoding}"
    pageEncoding="${encoding}"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="${encoding}">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
${cursor}
</body>
</html>



이와 같이 바꿔준다. 이제부터 새로 만들 때 마다 jquery까지 포함되서 나옴







다음으로는 View, 인터페이스 조정


모든 인터페이스를 닫고 상단 메뉴바의 Window - Show View에서


Project Explorer, Navigator, Servers, Console, Progress, Problems를 추가


Spring이 잘 설치됫는지 알 필요가 있기 때문에 이 중 Progress, Problems 두가지를 통해서 확인할 수 있다.








다음으로는 Project Explorer에서 우클릭 - New 했을 때 나오는 파일 생성 메뉴들을 조정


상단 메뉴바의 Window - Perspective - Customize Perspective


나오는 창에서 Shortcut Categories 중


General 항목의 File, Folder


Java 항목의 Class, Interface, Package


Web 항목의 CSS File, Dynamic Web Project, Filter, HTML, JSP File, Servlet


XML 항목의 XML File


그리고 나중에 Spring을 전부 설치하고 Spring 항목이 생기면 Spring Legacy Project를 눌러 메뉴들을 수정하자.







이제 Spring Framework의 본격적인 설치


1. 상단 메뉴바의 Help - Eclipse Marketplace 에서 Find에서 'sts'라고 검색




Spring Tools 3 Add-On 항목의 우측 아래의 Install 버튼을 클릭하면 설치가 시작된다.


위의 사진은 이미 설치가 되어있어 Installed라고 나온다.


설치가 시작되면 아까 추가한 인터페이스 중 Progress를 눌러보면 진행 상황이 나온다.


중간에 설치 파일 동의, Legacy 허용? 여부 등 다양한 창이 나오는데


모든 항목을 체크하고 승인하고 진행하자.


Eclipse가 재시작이 되면 설치가 완료된 것이고


설치가 완료됫으면 아까 추가한 인터페이스에서 Problems를 확인해보자.





다음과 같이 Warnings 한 두개만 있는게 정상이다.


빨간색으로 오류가 나온다면 설치가 잘못된 것이다.


해결법은 다음과 같이 있다.







Spring 오류 해결법 첫번째



이클립스 종료 - Spring Maven에서 관리하는 라이브러리들을 전부 삭제하고


다시 다운받기, 다운받는데 오류가 생각보다 많이 발생한다.


(반복)








Spring 오류 해결법 두번째






Eclipse Marketplace에서 sts, Spring Tools를 다시 설치한다.


우측 아래의 Installed 를 누르면 삭제 항목이 나오고


삭제 한 후 다시 install을 하면 된다.






크게 이 두가지 방법으로 Problems의 빨간 오류를 다 잡으면 된다.


간혹 오류를 다 잡고 Spring 프로젝트를 생성하고


Tomcat에 올려 실행할 때 오류가 발생 할 수 있다.


그럴 경우에도 똑같은 해결방법을 해보자.





이제 프로젝트를 생성해보자.


Server 탭에 Tomcat을 올리고


Navigator 우클릭 - New - Spring Legacy Project






프로젝트 명을 설정하고 Template에서 MVC를 선택하고 Next> 를 선택






여기서 빈 부분은 패키지 구조를 넣으면 된다.


보통은 3단계 구조로


com.회사명.프로젝트명 으로 작성한다.


이 후 Finish를 눌러 생성하면 된다.


생성하면 지금 생성한 프로젝트에 필요한 라이브러리를 다운로드하기 시작하고


Progress에 표시된다.


Problems 탭을 자주 확인하여 문제를 확인해야 한다.






프로젝트가 생성되고


Progress에 다운로드가 모두 완료되고


Problems에 문제가 모두 해결되면(Warning은 무시해도 된다.)


프로젝트 - src - main-  webapp - WEB-INF - views - home.jsp 파일에서


상단에 인코딩 처리가 안되어 있기 때문에


<%@ page session="false" pageEncoding="UTF-8" %>


이와 같이 인코딩을 추가한다.










다음으로 jstl 라이브러리 추가, 기본 프로젝트에서 쓰기 때문에 따로 추가해주어야 한다.




다음과 같이 WEB-INF 아래에 lib 폴더를 만들어 jstl 라이브러리를 넣어주자.


jstl 라이브러리(jar 파일)는 https://qdgbjsdnb.tistory.com/217에서 받을 수 있다.







이제 기본적으로 실행하기 위한 준비는 끝났다.


톰캣에 올려서 실행하여 접속해보자.





만약 톰캣 실행 시 오류가 발생하면 아까와 같이 Spring 해결법을 이용해서 해결해보자.





톰캣 주소는 아까 com.회사명.프로젝트명에서


localhost:'설정한 포트번호'/'프로젝트명'  으로 테스트 하면 된다.


톰캣 실행시 오류가 없고 페이지를 열때 문제가 없다면 Spring이 무사히 설치 된 것이다.






추가로 여러가지 버전 정보를 바꾸는 방법


프로젝트에 pom.xml이란 파일이 있는데 이 파일을 열어 pom.xml 탭을 누르면


xml 태그 소스들을 볼 수 있다.


기본 버전 정보는 다음과 같은 형식으로 되어 있을것이다.




버전은 임의로 바꾼 값들을 넣었다.


그리고 properties 아래에 dependency 태그들은 추가 될 라이브러리들의 버전을 관리해준다.






https://qdgbjsdnb.tistory.com/228 저번 게시글에서 myBatis에서


사용하는 xml 파일을 게시했는데 그에대한 부연 설명


먼저 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>
        <!-- 만약에 null로 데이터가 전달 되었다면 빈칸이 아닌 null로 인식해라 라는 뜻 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
   
    <!-- vo 객체들의 풀 네임을 사용하기 번거롭기 때문에 별칭을 등록하는 부분 -->
    <typeAliases>
        <!-- 클래스에 대한 별칭 -->
        <typeAlias type="com.kh.mb.member.model.vo.Member" alias="Member"/>
        <typeAlias type="com.kh.mb.board.model.vo.Board" alias="Board"/>
        <typeAlias type="com.kh.mb.board.model.vo.Reply" alias="Reply"/>
    </typeAliases>
   
    <!-- DB 연결할 설정에 대한 정보를 선언하는 부분 -->
    <environments default="firstDev">
        <environment id="firstDev">
            <!-- 트랙잭션 매니저는 JDBC 혹은 MANAGED 둘 중 하나를 선택할 수 있음 -->
            <!-- JDBC는 JDBC가 commit과 rollback의 기능을 직접 사용 가능하게 하는 옵션(수동 commit) -->
            <!-- MANAGED는 트랙잭션에 대해 어떤 영향도 행사하지 않는다는 뜻(자동 commit) -->
            <transactionManager type="JDBC"/>
           
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis"/>
            </dataSource>
        </environment>
    </environments>
   
    <mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>
   
</configuration>

출처: https://qdgbjsdnb.tistory.com/ [하위^^]


<?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>
        <!-- 만약에 null로 데이터가 전달 되었다면 빈칸이 아닌 null로 인식해라 라는 뜻 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
   
    <!-- vo 객체들의 풀 네임을 사용하기 번거롭기 때문에 별칭을 등록하는 부분 -->
    <typeAliases>
        <!-- 클래스에 대한 별칭 -->
        <typeAlias type="com.kh.mb.member.model.vo.Member" alias="Member"/>
        <typeAlias type="com.kh.mb.board.model.vo.Board" alias="Board"/>
        <typeAlias type="com.kh.mb.board.model.vo.Reply" alias="Reply"/>
    </typeAliases>
   
    <!-- DB 연결할 설정에 대한 정보를 선언하는 부분 -->
    <environments default="firstDev">
        <environment id="firstDev">
            <!-- 트랙잭션 매니저는 JDBC 혹은 MANAGED 둘 중 하나를 선택할 수 있음 -->
            <!-- JDBC는 JDBC가 commit과 rollback의 기능을 직접 사용 가능하게 하는 옵션(수동 commit) -->
            <!-- MANAGED는 트랙잭션에 대해 어떤 영향도 행사하지 않는다는 뜻(자동 commit) -->
            <transactionManager type="JDBC"/>
           
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis"/>
            </dataSource>
        </environment>
    </environments>
   
    <mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>
   
</configuration>




위와 같이 설정 파일을 구성하였다.


이해한대로 키워드를 설명하면


- mybatis를 사용하기 위한 기본 선언


<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">




- <configuration> 설정 파일 내용( 전체를 감싸고 있음 )


- <settings> mybatis 기본 설정 중 널처리에 관련된 설정


- <typeAliases> mybatis에서 프로그램단에서 작성한 객체를 사용하기 위한 선언


- <environments>, <transactionManager>, <dataSource>, <property> 사용할 DB에 대한 정보를 선언


- <mappers> DB를 사용하기 위한 정보와 쿼리문들을 선언한 xml 파일의 경로를 선언







다음은 쿼리문이 들어있는 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="Member">
    <resultMap type="com.kh.mb.member.model.vo.Member" id="memberResultSet">
        <id property="mid" column="MID"/>
        <result property="userId" column="USER_ID"/>
        <result property="userPwd" column="USER_PWD"/>
        <result property="userName" column="USER_NAME"/>
        <result property="email" column="EMAIL"/>
        <result property="birthDay" column="BIRTHDAY"/>
        <result property="gender" column="GENDER"/>
        <result property="phone" column="PHONE"/>
        <result property="address" column="ADDRESS"/>
        <result property="enrollDate" column="ENROLL_DATE"/>
        <result property="modifyDate" column="MODIFY_DATE"/>
        <result property="status" column="STATUS"/>
    </resultMap>
   
    <select id="loginMember" parameterType="Member" resultMap="memberResultSet">
        SELECT *
        FROM MEMBER
        WHERE USER_ID = #{userId}
        AND USER_PWD = #{userPwd}
    </select>
   
    <insert id="insertMember" parameterType="Member">
        INSERT INTO member
        VALUES (SEQ_MID.NEXTVAL, #{userId}, #{userPwd}, #{userName}, #{email}, #{birthDay}, #{gender}, #{phone}, #{address}, SYSDATE, SYSDATE, DEFAULT)
    </insert>
   
   
</mapper>







- <mapper> mybatis config xml에서 불러올 부분, 이름을 지정해서 찾는다.


- <resultMap> Sql DB 조회 결과를 만들어둔 java 객체파일과 연결시켜서 해당하는 값 = 컬럼 관계를 선언


   id를 지정하여 사용, 여러개를 만들어 유동적으로 사용할 수 있다.( 같은 객체 또한 가능 )


- <select>, <insert>, <update> 등등 sql 쿼리문이 들어가는 영역, id를 지정하여 java에서 불러올 때 찾으며


   parameterType으로 매개변수를 설정하고 resultMap으로 반환할 대상을 정한다.






SQL 쿼리문이 들어가는 영역은 줄넘김을(Enter) 허용한다.


EL 태그와 비슷하게 메소드를 통해서 전달받은 객체를 사용할 수 있다.


또한 여기서 사용할 수 있는 태그 조건문 같은 태그가 따로 있다.


다음 예를 보자


    <!-- 게시글 수 조회용 쿼리문 -->
    <select id="selectListCount" resultType="_int">
        SELECT COUNT(*)
        FROM board
        WHERE status = 'Y'
    </select>
   
    <!-- 게시물 목록 조회용 쿼리문 -->
    <select id="selectBoardList" resultMap="boardResultSet">
        SELECT *
        FROM board b
        JOIN member m ON (b.bwriter = m.mid)
        WHERE b.status = 'Y'
        ORDER BY bid DESC
    </select>
   
    <!-- 조회수 증가용 쿼리문 -->
    <update id="updateBoardCount" parameterType="_int">
        UPDATE board
        SET bCount = (SELECT bCount FROM board WHERE bid = #{bid}) + 1
        WHERE bid = #{bid}
    </update>
   
    <!-- 상세보기용 쿼리문 -->
    <select id="selectBoardOne" parameterType="_int" resultMap="boardResultSet2">
        SELECT *
        FROM board b
        JOIN member m ON(b.bWriter = m.mid)
        LEFT JOIN reply r ON (r.ref_bid = b.bid)
        WHERE b.bid = #{bid}
        AND b.status = 'Y'
        ORDER BY rid DESC
    </select>
   
    <!-- 검색 결과 수 조회용 쿼리문 -->
    <select id="selectSearchResultCount" resultType="_int">
        SELECT COUNT(*)
        FROM board b
        JOIN member m ON (b.bWriter = m.mid)
        WHERE b.status = 'Y'
        <!-- <if test="writer != null">
            AND user_name = #{ writer }
        </if>
        <if test="title != null">
            AND bTitle LIKE '%' || #{title} || '%'
        </if>
        <if test="content != null">
            AND bContent Like '%' || #{content} || '%'
        </if> -->
        <choose>
            <when test="writer != null">
                AND user_name = #{writer}
            </when>
            <when test="title != null">
                AND bTitle LIKE '%' || #{title} || '%'
            </when>
            <otherwise>
                AND bContent LIKE '%' || #{content} || '%'
            </otherwise>
        </choose>
    </select>
   
    <!-- 만약 쿼리문 안에 < 기호가 포함된다면 연산자로 인식하지 않고 태그로 인식한다. -->
    <!-- 이걸 해결해 주기 위해서 쿼리문을 cdata 주석으로 감싸야 한다. -->
    <!--
    <![CDATA[
        <
    ]]>
     -->
     
     <!-- 게시물 검색 결과 조회용 메소드 -->
     <select id="selectSearchResultList" resultMap="boardResultSet">
         SELECT *
         FROM board b
         JOIN member m ON (b.bwriter = m.mid)
         WHERE b.status = 'Y'
         <choose>
             <when test="writer != null">
                 AND user_name = #{writer}
             </when>
             <when test="title != null">
                 AND bTitle LIKE '%' || #{title} || '%'
             </when>
             <otherwise>
                 AND bContent LIKE '%' || #{content} || '%'
             </otherwise>
         </choose>
         ORDER BY bid DESC
     </select>



#{ 객체 필드값 } 과 같이 값을 원하는 부분에 사용하고


<if> 태그, <choose>, <when>, <otherwise> 태그가 있고


    <![CDATA[

<
    ]]>


위의 CDATA를 통해서 xml에서 <> 꺽쇠 키워드를 방지 할 수 있다.




- <if> if는 java에서 사용하는 if와 같이 쓰인다. else는 따로 없는듯


- <choose>, <when>, <otherwise> java에서의 switch case 문과 비슷한 역할을 한다.




그런데 만약 choose 문에서 WHERE 라고 쓰고 조건문을 넣었는데


해당하는 조건이 없어서


SELECT * FROM 테이블 WHERE


위와같이 입력되면 안되기 때문에


아예 WHERE 키워드 뒤에 1=1 을 넣어서 방지하거나


<WHERE>태그를 사용하여 감싸면 조건이 true 일때 WHERE을 따로 입력해준다.




Mybatis의 기능으로 DB를 매우 간편하게 조회할 수 있는데


다음의 로그인 예제를 컨트롤러부터 보고 확인해보자.


순서는 controller -> Service -> dao




String userId = request.getParameter("userId");
        String userPwd = request.getParameter("userPwd");
       
        System.out.println("userId : " + userId);
        System.out.println("userPwd : " + userPwd);
       
        // myBatis는 데이터 한개만 받기 때문에 처리를 해야함
        Member m = new Member();
        m.setUserId(userId);
        m.setUserPwd(userPwd);
       
        // 객체간의 의존성을 줄여줌, Spring의 핵심기능
        // 예) Service에서 메소드를 수정하면 Controller에서도 수정을 해야함 -> 의존성이 강하다.
        MemberService ms = new MemberServiceImpl();
        try {
            Member member = ms.selectMember(m);
           
            HttpSession session = request.getSession();
           
            session.setAttribute("loginUser", member);
           
            response.sendRedirect("index.jsp");
        } catch (LoginFailException e) {
            RequestDispatcher error = request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp");
            request.setAttribute("message", e.getMessage());
           
            error.forward(request, response);
        }



컨트롤러에서 Service의 인터페이스 생성, 일반 클래스 객체화 방법을 통해서 호출


JSP 페이지로부터 받은 값 m을 전달한다.








public interface MemberService {
    // 로그인용 메소드
    // public abstract가 전부 들어가기 때문에 애초에 생략해도 된다.(Interface)
    Member selectMember(Member m) throws LoginFailException;
   
}



import org.apache.ibatis.session.SqlSession;

import com.kh.mb.member.model.dao.MemberDao;
import com.kh.mb.member.model.exception.InsertFailException;
import com.kh.mb.member.model.exception.LoginFailException;
import com.kh.mb.member.model.vo.Member;
import static com.kh.mb.common.Template.*;


public class MemberServiceImpl implements MemberService{
   
    // 자동으로 더 넓거나 같은 접근제한자가 잡힌다.
    // 이러한 인터페이스를 사용한 구조는 딱 정해져있는 메소드를 사용하므로(매개변수도 바꿀 수 없다.)
    // 행위를 강제한다고 한다.
    @Override
    public Member selectMember(Member m) throws LoginFailException {
        // SqlSession은 MyBatis에서 커넥션 대신 사용
        SqlSession session = getSqlSession();
       
        Member member = new MemberDao().selectMember(session, m);
       
        session.close();
       
        return member;
    }

}


인터페이스를 이용한 구조로 강제와된 selectMember 메소드드를 통해 Dao에 값을 전달


SqlSession session = getSqlSession();의 SqlSession 객체는 myBatis 라이브러리의 객체이다.


getSqlSession은 myBatis의 SQLSession을 생성하기 위해 공통적으로 이용하는 기능이므로


따로 common에 Template 자바 파일을 만들어 생성/관리 한다.


해당 템플릿 파일은 다음과 같다.




import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Template {
    public static SqlSession getSqlSession() {
        SqlSession session = null;
       
        try {
            InputStream stream = Resources.getResourceAsStream("resources/mybatis-config.xml");
           
            System.out.println(stream);
           
            // openSession 인자에 false를 주면 Auto Commit이 차단 됨
            session = new SqlSessionFactoryBuilder().build(stream).openSession(false);
           
            System.out.println(session);
           
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
       
       
        return session;
    }
}



Resources.getResourceAsStream("resources/mybatis-config.xml"); 를 통해서 해당 경로에 있는


mybatis-config.xml에 작성 해 둔 설정을 불러와 SqlSession 객체를 생성한다.


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>
        <!-- 만약에 null로 데이터가 전달 되었다면 빈칸이 아닌 null로 인식해라 라는 뜻 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
   
    <!-- vo 객체들의 풀 네임을 사용하기 번거롭기 때문에 별칭을 등록하는 부분 -->
    <typeAliases>
        <!-- 클래스에 대한 별칭 -->
        <typeAlias type="com.kh.mb.member.model.vo.Member" alias="Member"/>
        <typeAlias type="com.kh.mb.board.model.vo.Board" alias="Board"/>
        <typeAlias type="com.kh.mb.board.model.vo.Reply" alias="Reply"/>
    </typeAliases>
   
    <!-- DB 연결할 설정에 대한 정보를 선언하는 부분 -->
    <environments default="firstDev">
        <environment id="firstDev">
            <!-- 트랙잭션 매니저는 JDBC 혹은 MANAGED 둘 중 하나를 선택할 수 있음 -->
            <!-- JDBC는 JDBC가 commit과 rollback의 기능을 직접 사용 가능하게 하는 옵션(수동 commit) -->
            <!-- MANAGED는 트랙잭션에 대해 어떤 영향도 행사하지 않는다는 뜻(자동 commit) -->
            <transactionManager type="JDBC"/>
           
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis"/>
            </dataSource>
        </environment>
    </environments>
   
    <mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>
   
</configuration>






<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">


이 부분은 mybatis홈페이지에서 가져와야 한다.


http://www.mybatis.org/mybatis-3/ko/getting-started.html


위 링크에 가면 자세히 설명이 되어있다


mybatis의 경우 페이지 한글화가 정말 잘 되 있어서


위의 페이지에서 xml 태그들의 설명을 위의 예제와 함께 이해해보자.








예제로 돌아와서 서비스에서


Member member = new MemberDao().selectMember(session, m);으로


SqlSession객체와 로그인 정보를 담고있는 멤버 객체를 Dao에 전달한다.





import org.apache.ibatis.session.SqlSession;

import com.kh.mb.member.model.exception.InsertFailException;
import com.kh.mb.member.model.exception.LoginFailException;
import com.kh.mb.member.model.vo.Member;

public class MemberDao{
   
    public Member selectMember(SqlSession session, Member m) throws LoginFailException {
        Member member = null;
       
        member = session.selectOne("Member.loginMember", m);
       
        if (member == null) {
            session.close();
            throw new LoginFailException("로그인 실패");
           
        }
       
        return member;
    }
}


selectMember 메소드에서 m에 담겨있는 정보를


selectOne이라는 mybatis 메소드를 이용하게 된다.


Member.loginMember 이라는 키워드를 통해서


Member라는 해당 namespace로 지정된 xml 파일을 찾아 loginMember에 지정된 쿼리문을 실행시킨다.


그 쿼리문에는 Member 객체를 받아서 이용하게 된다.







먼저 아까 SqlSession에 설정한 mybatis-config xml 파일을 살펴보면


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


mappers 태그에 member-mapper.xml이라는 파일을 볼 수 있다.


그리고 member-mapper.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="Member">
    <resultMap type="com.kh.mb.member.model.vo.Member" id="memberResultSet">
        <id property="mid" column="MID"/>
        <result property="userId" column="USER_ID"/>
        <result property="userPwd" column="USER_PWD"/>
        <result property="userName" column="USER_NAME"/>
        <result property="email" column="EMAIL"/>
        <result property="birthDay" column="BIRTHDAY"/>
        <result property="gender" column="GENDER"/>
        <result property="phone" column="PHONE"/>
        <result property="address" column="ADDRESS"/>
        <result property="enrollDate" column="ENROLL_DATE"/>
        <result property="modifyDate" column="MODIFY_DATE"/>
        <result property="status" column="STATUS"/>
    </resultMap>
   
    <select id="loginMember" parameterType="Member" resultMap="memberResultSet">
        SELECT *
        FROM MEMBER
        WHERE USER_ID = #{userId}
        AND USER_PWD = #{userPwd}
    </select>
   
    <insert id="insertMember" parameterType="Member">
        INSERT INTO member
        VALUES (SEQ_MID.NEXTVAL, #{userId}, #{userPwd}, #{userName}, #{email}, #{birthDay}, #{gender}, #{phone}, #{address}, SYSDATE, SYSDATE, DEFAULT)
    </insert>
   
   
</mapper>




<mapper namespace="Member">


namespace가 Member로 설정되서


session.selectOne("Member.loginMember", m);라는 메소드에서 이 xml까지 찾아와서


<select id="loginMember" parameterType="Member" resultMap="memberResultSet">
        SELECT *
        FROM MEMBER
        WHERE USER_ID = #{userId}
        AND USER_PWD = #{userPwd}
    </select>


id가 loginMember 라고 지정되어 있는 이 쿼리문을 사용하게 된다.


parameterType과 resultMap, #{userId} 등 특이한 키워드들이 보이는데


이에 대한 설명은 myBatis xml 파일을 설명하는 게시글에서 추가로 한다.


참고로 mybatis 홈페이지에도 충분히 이해하기 쉽게 설명이 되어있다.






xml에 지정해놓은 여러가지 키워드들을 통해서 DB를 조회하고 값을 리턴하게 된다.


























Front Controller는 맵핑 주소를 관리하는 java 파일이 된다.


맵핑 주소로 접근 할 때 모든 접근을 Front Controller를 통하여 해당하는 컨트롤러에 이동시켜준다.


예전에 했던 필터와 유사




예를 들어 주소가 me로 끝나는 맵핑 주소는 항상 위의 컨트롤러를 지나게 된다.


다음 예를 보자.



package com.kh.mb.frontController;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("*.me")
public class MemberFrontController extends HttpServlet {
    private static final long serialVersionUID = 1L;
      
    public MemberFrontController() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
       
        String uri = request.getRequestURI();
        System.out.println(uri);
       
        String action = uri.substring(uri.lastIndexOf("/") + 1, uri.lastIndexOf(".me"));
        System.out.println("action : " + action);
       
        RequestDispatcher rd = null;
        switch (action) {
        case "login":
            rd = request.getRequestDispatcher("login");
            break;
        case "logout":
            rd = request.getRequestDispatcher("logout");
            break;
        case "showInsertForm":
            rd = request.getRequestDispatcher("showInsertForm");
            break;
        case "minsert":
            rd = request.getRequestDispatcher("minsert");
            break;
        }
       
        rd.forward(request, response);
       
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}





필터 클래스에서 했던것처럼 문자 형식을 UTF-8로 처리해주고


맵핑 주소 문자열을 분리해서 Switch Case 문으로 해당하는 컨트롤러로 연결시킨다.






















프로젝트를 진행하게 되면 절대 혼자서 하지 않고 여러명에서 잡고 진행하게 된다.


하지만 지역마다 사투리가 있는것 처럼 사람마다 다양한 코딩 스타일이 존재하고


메소드 이름을 짓는 방식이 다르고 등등 여러가지 일치하지 않는 부분이 생기게 된다.


이를 해결하기 위해 핵심 기능 구현부분에 Interface를 적용한다.







Interface를 상속받는 클래스는 Interface에 존재하는 메소드 형식을 사용해야 하는 제약이 걸리기 때문에


틀(Frame)을 어느정도 일치 시킬 수 있다.


한번 확인해보자.



public interface MemberService {
    // 로그인용 메소드
    // public abstract가 전부 들어가기 때문에 애초에 생략해도 된다.(Interface)
    Member selectMember(Member m) throws LoginFailException;

    // 회원 가입용 메소드
    void insertMember(Member m) throws InsertFailException;
   
}


먼저 Member Service라는 인터페이스이고


throw가 붙은 두가지의 메소드가 존재한다.








다음은 MemberServiceImpl 클래스는 인터페이스 MemberService를 상속받는다.



public class MemberServiceImpl implements MemberService{
   
    // 자동으로 더 넓거나 같은 접근제한자가 잡힌다.
    // 이러한 인터페이스를 사용한 구조는 딱 정해져있는 메소드를 사용하므로(매개변수도 바꿀 수 없다.)
    // 행위를 강제한다고 한다.
    @Override
    public Member selectMember(Member m) throws LoginFailException {
        // SqlSession은 MyBatis에서 커넥션 대신 사용
        SqlSession session = getSqlSession();
       
        Member member = new MemberDao().selectMember(session, m);
       
        session.close();
       
        return member;
    }

    // 회원 가입용 메소드
    @Override
    public void insertMember(Member m) throws InsertFailException {
        SqlSession session = getSqlSession();
       
        new MemberDao().insertMember(session, m);
       
        session.commit();
        session.close();
    }
   
}



어노테이션을 통해 인터페이스에 해당하는 메소드를 생성하지 않으면 오류가 발생하고


리턴 값이나 throw 같은 형식 또한 완전 일치된다.


이를 통해 팀 프로젝트 설계 시 충돌나는 부분을 최소화 할 수 있다.








또한 이와 같은 구조를 사용하는 이점으로는 유지보수가 훨신 수월하게 된다.


사용할 때 다음과 같이 사용하게 되는데


MemberService ms = new MemberServiceImpl();



        try {
            Member member = ms.selectMember(m);
           

        } catch (LoginFailException e) {

        }




생성을 MemberService ms = new MemberServiceImpl(); 이와같이 Interface로 선언하고


일반 클래스로 생성하는 방식으로 사용하게 된다.


이러한 방식을 의존성을 낮춘다고 한다.(의존성 역전, IoC라고 한다.)


(나중에 DI, Dependency Injection, 의존성 주입이라는 개념이 Spring Framework에 나온다.)


의존성이란 클래스 선언과 생성과의 관계로 한쪽이 바뀔 경우 한쪽도 같이 바꿔야 하는데 이러한 경우를 의존성이 강하다고 한다.


프로젝트 구조에 관해서는 다음에 자세히 설명.










해당 커스텀 Exception 처리를 통해 에러페이지로 포워딩 하는 부분이다.




순서는 ( controller -> Service -> dao ) 에서 데이터 처리 과정에 오류가 생기면 throw하여 controller에서 에러 페이지로 포워딩 하게 된다.







이 중 먼저 LoginFailException 부분이다.


public class LoginFailException extends Exception {
    public LoginFailException(String msg) {
        super(msg);
    }
}


Exception을 상속받는 클래스로 생성자를 통해 오류 메세지를 Exception 생성자로 전달.









Controller 부분으로 페이지에서 받은 값을 Service로 전달하여 DB를 조회한다.

try catch문에서 커스텀 예외처리를 적용한다.


        MemberService ms = new MemberServiceImpl();
        try {
            Member member = ms.selectMember(m);
           
            HttpSession session = request.getSession();
           
            session.setAttribute("loginUser", member);
           
            response.sendRedirect("index.jsp");
        } catch (LoginFailException e) {
            RequestDispatcher error = request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp");
            request.setAttribute("message", e.getMessage());
           
            error.forward(request, response);
        }









Service 부분의 인터페이스 부분.

    // 로그인용 메소드
    // public abstract가 전부 들어가기 때문에 애초에 생략해도 된다.(Interface)
    Member selectMember(Member m) throws LoginFailException;


왜 인터페이스를 만들어 메소드를 관리하는지는 다음 페이지에서 설명(https://qdgbjsdnb.tistory.com/226)










이 인터페이스를 받는 Service java 파일

public class MemberServiceImpl implements MemberService{
   
    @Override
    public Member selectMember(Member m) throws LoginFailException {
        // SqlSession은 MyBatis에서 커넥션 대신 사용
        SqlSession session = getSqlSession();
       
        Member member = new MemberDao().selectMember(session, m);
       
        session.close();
       
        return member;
    }
}

throws LoginFailException을 통해서 처리해준다.







Dao에서 DB를 조회하는 부분.

XML 파일을 통해 Mybatis 기능을 이용하는 부분

    public Member selectMember(SqlSession session, Member m) throws LoginFailException {
        Member member = null;
       
        member = session.selectOne("Member.loginMember", m);
       
        if (member == null) {
            session.close();
            throw new LoginFailException("로그인 실패");
           
        }
       
        return member;
    }


member = session.selectOne("Member.loginMember", m); - DB를 조회하는 Mybatis 기능

throw new LoginFailException("로그인 실패"); 을 통해서 조회한 DB가 없을 경우 커스텀 예외처리 생성자를 호출한다.

throws LoginFailException을 통해서 Exception으로 전달










에러가 발생한다면 계속 Throw 해서 처음 메소드를 호출했던 Mybatis 까지 와서 try catch 문에서

catch문이 실행되게 한다.

catch (LoginFailException e) {
            RequestDispatcher error = request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp");
            request.setAttribute("message", e.getMessage());
           
            error.forward(request, response);
        }






먼저 MyBatis는 저번의 Servlet 통신을 이용하여 DataBase 조회 방법이 매우 간단해진다.


XML 파일을 이용하여 간편하게 데이터를 조회하고 심지어 페이징 처리도 깔끔하게 가능하다.


이번 MVC 패턴의 폴더구조가 조금 바뀌는데 한번 확인해보자.


Spring Framework 형태에 맞추며 나중에 이해하기 쉬울 것 이다.







주목할 부분은


1. Model 부분의 Exception 추가(링크)


2. Model 부분의 Service 인터페이스 추가(링크)


3. FrontController를 통한 페이지 맵핑 처리(링크)


4. SQL 쿼리를 관리하던 Property 파일이 사라지고 XML 파일로 관리(링크)


5. web 아래에 resources 폴더를 추가하여 css image js uploadfile 등을 관리


6. view 파일들이 전부 WEB-INF 아래로 이동


6가지이다.










1. Model 부분의 Exception 추가(링크)



데이터 조회 시 오류 발생 시 커스텀 Exception을 이용하여


try catch 문에서 오류 페이지로 이동시켜준다.






2. Model 부분의 Service 인터페이스 추가(링크)



Service 부분에서 Interface를 이용하여 Service에 정의 할 메소드를 제한시키는 것.


Framework의 특징으로 미리 틀을 만들어 두는 느낌으로 이해하면 된다.


원래는 Dao에도 적용해야 한다.





3. FrontController를 통한 페이지 맵핑 처리(링크)



모든 맵핑 링크가 FrontController를 거치고 작업을 한 후


해당 서블릿으로 이동된다, 프로젝트의 규모가 커저도 페이지 관리가 편해진다.






4. SQL 쿼리를 관리하던 Property 파일이 사라지고 XML 파일로 관리(링크)



Mybatis의 핵심 기능으로 DB를 사용하기 매우 간편해진다.






5. web 아래에 resources 폴더를 추가하여 css image js uploadfile 등을 관리



핵심 파일들과 떨어뜨려 한 눈에 보기 편하게 관리






6. view 파일들이 전부 WEB-INF 아래로 이동









항목별로 설명이 필요한것은 새로 포스팅 합니다.







먼저 MyBatis jar파일을 라이브러리에 추가한다.


메이븐이라는 사이트를 이용


mvnrepository.com


메이븐은 나중에 나오지만 라이브러리들의 버전을 관리해주고 프로젝트 관리를 해주는 시스템? 이라고 한다.


위 사이트로 들어가 상단 검색창에 mybatis를 검색하자.








리스트에 나온 MyBatis를 클릭하고







해당 화면이 나오는데 여태껏 나온 버전별로 쭉 나온다.


버전별로 이용률도 한눈에 확인 할 수 있다.(Usages)


버전의 경우 X1.X2.X3 과 같은 형태로 관리된다.


X1의 경우는 하위 버전과 호환이 안되고 매우 크게 변화가 있는 경우이고


X2의 경우는 하위 버전과 호환이 되지만 일부 오류가 있을 수 있다.


X3의 경우는 보통 버그 픽스나 간단한 수정 등 작은 변경시 기록된다.




신버전은 항상 최신이여서 좋은것은 아니다.


신버전일 수록 사람들이 많이 사용해보지 않았고 버그가 발견되지 않아서 안정성 문제가 발생 할 수 있다는 점을 알아두자.



이 블로그에서 사용할 MyBatis 버전은 3.4.1이다. 3.4.1버전의 숫자를 눌러 다음 페이지로 넘어가자








jar 파일을 다음과 같이 lib 폴더에 추가하면 된다.














+ Recent posts