/ #JAVA#이클립스#메이븐

이클립스 메이븐 프로젝트

학원에서 열심히 스프링 부트를 중심으로 JPA를 연마했건만 현업에서는 부트가 쓰이지 않는다고 합니다.

심지어 에디터 조차도 대부분 이클립스를 사용한다고 하는데, 최근까지 지원하던 STS3을 더이상 지원하지 않아 이클립스 최신버전에서는 레거시 프로젝트조차 만들수 없는 상황이 왔습니다.

이번 글에서는 연습삼아서 이클립스를 통해 메이븐 프로젝트를 생성하고, 스프링 mvc를 구성해보도록 하겠습니다.

이클립스 / JDK 11 / Tomcat9 다운로드

우선 이클립스를 다운받도록 하겠습니다.

https://www.eclipse.org/downloads/packages/로 들어갑니다.

인스톨러를 다운받아도 되지만 패키지 파일을 받도록 하겠습니다. 웹어플리케이션을 개발할 것이니 Eclipse IDE for Enterprise Java and Web Developers를 다운로드 합니다.

img001

JDK와 톰캣은 레거시 프로젝트때 다운받은 것을 사용하겠습니다.

oracle JDK 11

Tomcat 9


이클립스 패키지를 적당한곳에 압축을 풀고 작업공간을 만들어줍시다. 저는 eclipse-ws라고 만들었습니다.

eclipse 폴더에 들어가서 eclipse.exe를 실행합니다. 작업공간을 지정해주고 Launch를 클릭하면 이클립스가 실행됩니다.

img002

환경설정 설정

STS3이 이클립스 기반이기 때문에 환경설정의 경우 STS3과 동일하게 설정하면 됩니다.

상단에 Window > Preferences를 클릭합니다.

img08

General > Content Types에 들어가서 Text를 선택하고 하단에 Default encodingutf-8을 입력후 Update 버튼을 누르세요.

img09

General > Workspace에서 Text file encoding 또한 UTF-8로 번경 후 Apply 버튼을 누르세요

img10

Web > CSS Files, Web > HTML Files, Web > JSP Files, XML > XML Files, JSON > JSON Files 또한 Encoding을 UTF-8로 변경후 Apply 버튼을 누르세요.

img11

이클립스 역시 기존 JDK 버전을 17로 사용하고 있어서 환경변수가 17 버전으로 되어 있습니다. JAVA > Compiler에서 JDK Compiler compliance level11로 변경하고, JAVA > Installed JREs에서 JDK 11버전 경로를 추가한 후 Apply를 클릭하세요.

img12

img13

톰캣 서버 생성

STS와 동일합니다.

상단에 window > Show View > Servers를 클릭하여 서버창을 엽니다.

img003

왼쪽 하단에 링크를 클릭하여 서버를 추가합시다.

img05

Tomcat v9.0 Server를 선택후 Next 버튼을 클릭합니다.

img06

Browse... 버튼을 클릭하여 설치한 톰캣 폴더를 디렉토리로 지정한후 Finish를 클릭하세요.

img07

메이븐 프로젝트 생성

메이븐(Maven)은 빌드도구로, pom.xml 파일을 통해 프로젝트 의존성을 관리해줍니다. pom은 Project Object Model의 약자입니다. 스프링 레거시 프로젝트를 만들때 기본 빌드도구로 메이븐을 사용했었습니다. 참고로 스프링 부트 프로젝트의 경우 gradle을 기본 빌드도구로 사용합니다.

우선 상단메뉴에서 File > New > Maven Project를 클릭합니다.

img004

Next를 클릭합니다.

img005

maven-archetype-webapp 아티팩트(Artifact)를 검색하여 그룹id가 org.apache.maven.archetype인 아티팩트를 선택하고 Next를 클릭합니다.

img006

그룹id와 아티팩트id를 입력하고 Finish를 눌러 프로젝트를 생성합니다.

img007

하단 콘솔창에 프로젝트를 생성할 것인지 확인하는 문구가 뜹니다. y를 입력하고 엔터를 눌러 프로젝트를 생성합니다.

img008

프로젝트가 생성되는데 오류마커가 뜹니다. 마커창을 확인하면 우선 JRE 버전이 달라서 문제가 발생했습니다.

img009

좌측에 프로젝트 폴더를 선택후 상단메뉴의 Project > Properties를 클릭합니다.

img010

Project Facets에서 Java 버전을 11로 설정후 Apply and Close를 눌러 자바버전을 변경합니다.

img011

JSP 관련 오류도 발생했는데 이것은 의존성이 없기때문입니다. 메이븐 레포지토리를 참고하여 javax.servlet-api, javax.servlet.jsp-api를 의존성으로 추가합니다. jstl도 같이 추가해줍시다.

pom.xml
...
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.3</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
...

index.jsp에 다음 헤더를 입력하고 저장하면 모든 오류가 제거됩니다.

/src/main/webapp/index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>

아직 web.xml 파일을 설정하지 않아서 루트경로는 /src/main/webapp입니다. 톰캣에 프로젝트를 추가하여 실행시켜보겠습니다.

톰캣서버에 마우스 오른쪽 클릭하고 Add and Remove...를 클릭합니다.

img012

왼쪽에 있는 프로젝트를 선택후 Add >를 클릭하여 오른쪽으로 이동시킨 후 Finish를 눌러 프로젝트를 톰캣서버에 설정합니다.

img013

톰캣서버를 클릭후 단축키 Ctrl + Alt + R을 누르면 톰캣서버가 실행됩니다. default 경로는 프로젝트 이름이고, default 포트는 8080입니다.

http://localhost:8080/work로 이동하면 톰캣서버가 잘 작동한다는 사실을 알 수 있습니다.

img014

스프링 프레임워크 의존성 추가

이제 스프링 프레임워크를 의존성 추가해주세요.

프로퍼티로 자바 버전을 설정하고 spring-corespring-webmvc를 추가하겠습니다.

pom.xml
...
    <properties>
        ...
        <org.springframework-version>5.3.23</org.springframework-version>
    </properties>
...
        <!-- 스프링 프레임워크 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
...

웹어플리케이션 설정

web.xml 설정

web.xml 파일은 클라이언트의 요청 경로와 요청을 처리하는 서블릿 사이의 매핑을 정의합니다.

요청 경로와 서블릿을 매핑하기 sevlet 태그로 서블릿을 선언하고 servlet-mapping 태그에 매핑할 경로를 정의합니다. 예제로 다음과 같이 작성해보겠습니다.

/src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

    <display-name>Mission Web Application</display-name>
  
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>kr.kro.rubisco.work.TestServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

기본적으로 web-app 버전이 2.3으로 설정되어 있는데, 2.3은 el 사용이 안되므로 의존성에 추가한 3.1버전으로 바꿔줍시다.

hello라는 이름의 서블릿을 정의하는데, 해당 서블릿의 타입(클래스)는 kr.kro.rubisco.work.TestServlet 입니다. hello 서블릿은 /hello라는 클라이언트의 요청에 매핑됩니다.

이제 kr.kro.rubisco.work 패키지에 TestServlet 이라는 서블릿 클래스를 작성하면 됩니다.

처음에는 소스 경로가 없는데, /src/main 폴더에 java 폴더를 생성하면 소스경로가 생깁니다.

img015

img016

img017

해당 소스경로에 오른쪽 클릭을 하여 패키지를 생성합니다.

img018

img019

해당 패키지에 TestServlet 클래스를 생성하여 다음과 같이 작성합니다.

/kr.kro.rubisco.TestServlet.java
package kr.kro.rubisco.work;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;

public class TestServlet extends HttpServlet {
    
    private static final long serialVersionUID = 1L;

    public void init(ServletConfig config) throws ServletException {
        System.out.println("init() 실행됨!");
    }

    public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
        System.out.println("service() 실행됨!");
    }
}

서블릿이기때문에 웹요청에 대한 서블릿인 HttpServlet을 상속받아 구현합니다.

http://localhost:8080/work/hello 경로로 이동하면 hello 서블릿으로 매핑되어 서비스 처리를 하는 것을 볼 수 있습니다.

img020

init 메소드는 톰캣서버가 실행될 때 최초 한번 실행되는 초기화 메소드이며, service 메소드는 클라이언트 요청이 있을때마다 호출되는 메소드입니다.

HttpServlet에 다양한 메소드가 있는데 일단 생략하겠습니다.


스프링 프레임워크의 DispatcherServlet 역시 HttpServlet을 상속받은 서블릿입니다. 즉, 모든 요청에 대하여 DispatcherServlet에 매핑되도록 하면 스프링 컨테이너를 통해 클라이언트 요청을 제어할 수 있게 됩니다.

다음과 같이 web.xml을 작성해주세요.

/src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">

    <display-name>Mission Web Application</display-name>

    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    
    <filter>
        <filter-name>httpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!-- Processes application requests -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
</web-app>

레거시 프로젝트에서 그대로 가져왔습니다.

display-name 태그는 어플리케이션의 이름을 나타냅니다.

context-param은 전역변수에 해당하는 Root WebApplicationContext를 설정합니다. 컨텍스트는 쉽게 말하자면 빈(bean)이 담겨있는 컨테이너라고 보시면 됩니다. ServletContext는 크게 2가지로 형태로 존재하는데, 그 중 하나가 전역변수에 해당하는 Root WebApplicationContext 입니다.

img021

서블릿이 로드될 때 아래에서 설명할 ContextLoaderListener에 의하여 Root WebApplicationContext가 생성되는데, 이때 ContextLoaderListener는 context-param의 contextConfigLocation 파라미터에 지정된 설정파일에 따라 빈을 로드하여 컨텍스트를 생성하고 ServletContext에 저장합니다. contextConfigLocation이 설정되지 않으면 기본값으로 /WEB-INF/applicationContext.xml 경로파일을 찾으며, 해당 컨텍스트 파일에는 보통 Service나 DAO 객체가 정의됩니다.

fliter는 지난번 글에서 자세히 설명했으므로 생략합니다. 인코딩 필터와 히든 메소드 필터를 추가합니다.

listener는 특정 이벤트에 대하여 호출되어 처리하는 객체입니다. ContextLoaderListener의 경우 웹서비스가 시작되거나 종료될 때 호출되며, 이때 전역변수에 해당하는 Root WebApplicationContext를 생성하거나 제거하는 역할을 합니다. 위에서 말한것과 같이 리스너에 의해 생성된 Root WebApplicationContext는 ServletContext에 저장됩니다.

servlet 태그 아래 init-param 태그는 지역변수에 해당하는 Child WebApplicationContext를 설정합니다. 서블릿에 의해 생성되며, 루트 컨텍스트와 마찬가지로 contextConfigLocation 파라미터에 지정된 설정파일에 따라 빈을 로드하여 컨텍스트를 생성하고 ServletContext에 저장합니다. contextConfigLocation이 설정되지 않으면 기본값으로 /WEB-INF/[서블릿 이름]-servlet.xml 경로파일을 찾으며, 해당 컨텍스트 파일에는 보통 Controller나 ViewResorver 객체가 정의됩니다.

Child WebApplicationContext에서는 ContextLoaderListener에 의해 생성된 Root WebApplicationContext에 접근이 가능합니다. 하지만 그 반대는 불가합니다. 즉, Controller 또는 ViewResolver에서는 Service나 DAO를 참조할 수 있지만, Service에서 Controller는 참조하지 못합니다. SevletContext에 저장된 빈들은 ServletConfiggetinitParameter 메소드를 통해 불러올 수 있습니다. 위에서 작성한 TestServlet의 init 메소드를 참고하세요.

마지막으로 load-on-startup 태그는 톰캣서버가 실행될 때 DispatcherServlet의 초기화 여부와 순서를 나타냅니다. 서블릿은 최초 클라이언트의 요청에 대하여 한번 초기화되는데, 그렇기때문에 최초 요청에 대하여 처리속도가 느릴 수 있습니다. 이때 해당 태그를 양수로 입력하면 클라이언트의 최초 요청이 아니라 톰캣서버가 실행되는 시점에서 서블릿이 초기화되며, 숫자는 초기화되는 우선순위를 나타냅니다.

root-context.xml 설정

root-context.xml 파일은 context-param에서 정의되는 Root WebApplicationContext의 설정파일입니다. 위에서 말했듯이 해당 컨텍스트는 전역변수와 같아서 모든 ServletContext에서 참조가능합니다.

/src/main/webapp/WEB-INF/spring/root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <context:component-scan base-package="kr.kro.rubisco.work.service"></context:component-scan>
</beans>

DAO나 Service 등 모든 서블릿에서 접근가능한 빈이 등록되는데 DB연결은 나중에 하도록 하고 우선 context 네임스페이스의 component-scan을 설정합니다. 속성으로 base-package를 입력하는데, 해당 값에 등록된 패키지를 스캔하여 @Component 어노테이션이 붙은 클래스를 자동으로 빈(bean)으로 등록하는 역할을 합니다. kr.kro.rubisco.work.service 패키지에 서비스를 작성할 예정이므로 미리 해당 패키지를 생성해둡시다.

servlet-context.xml 설정

servlet-context.xml 파일은 servlet의 init-param에서 정의되는 Child WebApplicationContext의 설정파일입니다. 위에서 말했듯이 해당 컨텍스트는 지역변수와 같아서 해당 ServletContext에서만 참조가능합니다.

/src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <resources mapping="/**" location="/WEB-INF/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <context:component-scan base-package="kr.kro.rubisco.work.controller" />
    
</beans:beans>

네임스페이스로 mvc, context, beans를 설정하고, 빈으로 InternalResourceViewResolver을 등록했습니다. 해당 resolver는 DispatcherServlet의 기본 뷰 리졸버로, JSP를 뷰로 사용할 때 쓰입니다. 프로퍼티로 prefixsuffix를 가지며, 클라이언트의 요청으로부터 접두어(prefix)와 접미어(suffix)를 붙여 실제 리소스 경로를 찾을수 있도록 돕습니다. 즉, /hello 라는 ViewName을 통해 /WEB-INF/views/hello.jsp 파일을 찾게 됩니다. prefix를 변경했기때문에 기존에 생성되어있던 index.jsp 파일을 /WEB-INF/views 경로로 이동시켜주세요.

서비스와 마찬가지로 context 네임스페이스의 component-scan을 설정하여 kr.kro.rubisco.work.controller 패키지를 스캔하도록 합니다. 해당 패키지에는 컨트롤러를 작성할 예정이므로 미리 패키지를 생성해둡시다.

annotation-driven 태그는 어노테이션을 통해 Controller 호출이나 bean 객체 등록 등의 매핑작업을 편리하게 할 수 있도록 해줍니다.

resources 태그는 정적 자원의 경로를 설정합니다. 클라이언트가 /resources/** 경로로 요청하면 /resources/ 경로에서 정적 자원을 가져옵니다.

Controller 작성

스프링 MVC의 설정은 끝이며, 여기서부터는 기존 레거시 프로젝트와 동일하게 진행하면 됩니다. 톰캣서버에서 루트 경로를 /으로 변경하고 다음 컨트롤러와 뷰를 작성 후 http://localhost:8080/hello으로 이동해보세요.

/kr/kro/rubisco/controller/WorkController.java
package kr.kro.rubisco.work;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class WorkController {

    @GetMapping("/hello")
    public void getHelloView() {}
}
/src/main/webapp/WEB-INF/views/hello.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>안녕하세요?</title>
</head>
<body>
    <h1>이클립스를 통한 스프링 MVC 설정</h1>
</body>
</html>

img022

오라클 DB

이번 프로젝트에서는 오라클 클라우드가 아닌 오라클DB를 직접 설치하고 연결해보도록 하겠습니다. 11버전을 다운받겠습니다.

설치

우선 아래 링크로 들어가서 오라클을 다운로드 합니다.

: https://www.oracle.com/database/technologies/xe-prior-release-downloads.html

img023

라이센스 동의에 체크한 후 다운로드 버튼을 클릭합니다.

img024

압축파일을 풀고 setup.exe를 실행시켜 오라클을 설치할 수 있습니다.

권한설정

이제 오라클DB를 사용하기위한 계정을 만들고 권한을 부여해보겠습니다. 먼저 단축키 Ctrl + R을 눌러 실행창을 열고 powershell 또는 cmd를 입력후 터미널을 실행시켜줍니다.

명령프롬프트에서 sqlplus 라고 입력하면 DB에 접속됩니다. 유저네임과 패스워드를 입력하라고 뜨는데, 시스템 계정으로 접속합니다. 유저네임은 system, 패스워드는 오라클을 설치할때 입력한 암호입니다.

img025

다음 쿼리문을 통해 계정을 생성합니다.

계정 생성 쿼리문
create user [아이디] identified by [비밀번호];

생성된 계정에 권한을 부여하겠습니다.

권한 부여 쿼리문
grant connect, resource to [아이디];

connect는 DB에 접속하기 위한 기본적인 시스템 권한을, resource는 객체(테이블, 뷰, 인덱스) 생성에 필요한 시스템 권한을 설정합니다. DBA는 DB 관리자 권한을 부여하여 모든 권한을 가지게 됩니다.

DB 연결

이제 DB에 연결해보겠습니다. 오라클DB의 연결 정보는 보통 tnsnames.ora 파일에 저장되어 있습니다. 만약 해당 파일이 있다면 IP 주소나 SID 또는 Service Name을 직접 입력하지 않아도 바로 DB와 연결할 수 있습니다.

오라클을 설치하면 해당 파일이 하나 생성됩니다. 해당 파일은 $ORACLE_HOME/network/admin/에 위치하며, 위에 과정을 따라왔다면 C:/oraclexe/app/oracle/product/11.2.0/server/network/ADMIN 경로에 존재합니다.

img026

맨 앞에 XE는 별칭(alias)입니다. 요한 부분은 HOST, PORT, SERVICE_NAME 입니다.

3개의 정보를 가지고 DB 연결 주소를 직접 작성할 수도 있습니다.

jdbc:oracle:thin@localhost:1521:XE

하지만 tnsnames.ora 파일이 있다면 연결정보를 직접 작성하지 않아도 됩니다. DBever를 통해 연결해보겠습니다. DBever를 열고 상단메뉴에 새 데이터베이스 연결을 클릭합니다.

img027

오라클을 선택 후 Connection Type에서 TNS 탭을 클릭하여 alias와 tnsnames.ora 파일이 있는 경로를 설정해줍니다. 유저네임과 패스워드를 입력후 Client를 선택하는데 오라클을 설치할 때 XE라는 이름으로 하나 생성되어 있습니다. 완료를 누르면 오라클DB에 바로 연결됩니다.

img028

Mybatis 연동

이제 웹어플리케이션에 DB를 연결해보겠습니다. DB에 연결하기 위해서는 JDBC가 필요하며, Mybatis를 통해 쿼리를 매핑할 것입니다. 의존성으로 spring-jdbc, mybatis, mybatis-spring, ojdbc를 추가합니다. 오라클 11.2의 경우 ojdbc6를 사용합니다.

pom.xml
...
        <!-- Spring JDBC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc6 -->
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.4</version>
        </dependency>
...

다음으로 db 연결정보를 가지는 dataSource와 쿼리를 만들어내는 sqlSessionFactory, 쿼리를 담고있는 sqlSession을 빈으로 추가해야합니다. DAO에 해당하므로 Root WebApplicationContext에 추가합니다.

/src/main/webapp/WEB-INF/spring/root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <!-- Root Context: defines shared resources visible to all other web components -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
          <property value="oracle.jdbc.driver.OracleDriver" name="driverClassName"/>
          <property value="[연결경로]" name="url"/>
          <property value="[아이디]" name="username"/>
          <property value="[패스워드]" name="password"/>
    </bean>
         
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
         <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml"></property>            
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
    </bean>

    <context:component-scan base-package="kr.kro.rubisco.work.service"></context:component-scan>
</beans>

dataSource에서 연결 경로는 위해서 설명했듯이 jdbc:oracle:thin@HOST:PORT:SERVICE_NAME 형태로 작성합니다.

mybatis의 설정파일은 classpath에 존재합니다. 레거시와 동일하게 /src/main 경로 아래에 resources 폴더를 만들고 해당 폴더를 classpath로 설정해주겠습니다. 폴더를 생성한 후 오른쪽 클릭하여 Build Path > Use as Source Folder를 클릭하면 classpath로 설정됩니다.

img029

resources 폴더 아래에 mybatis의 설정파일을 작성합니다.

/src/main/resources/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"/>
        <setting name="callSettersOnNulls" value="true"/>
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
    
    <typeAliases>
        <package name="kr.kro.rubisco.work.dto"/>
    </typeAliases>
</configuration>

레거시에서 설명했기에 생략합니다. dto의 alias가 패키지에 해당하므로 해당 패키지 폴더를 생성해주세요. 또한 resources 폴더 아래 매퍼를 작성하기 위한 mappers 패키지 폴더를 만들어주세요.

DBever를 통해 테이블을 먼저 만들겠습니다.

테이블 생성 쿼리
CREATE TABLE mission (
    idx         NUMBER(10) PRIMARY KEY,
    id          VARCHAR2(50) UNIQUE NOT NULL,
    name        VARCHAR2(50) NOT NULL,
    gender      CHAR(1) NOT NULL,
    nation      VARCHAR2(50) NOT NULL,
    city        VARCHAR2(50) NOT NULL,
    create_date DATE NOT NULL
);

CREATE  SEQUENCE mission_idx_seq
        INCREMENT BY 1
        START WITH 1
        MINVALUE 1
        MAXVALUE 9999999999
        NOCYCLE
        NOCACHE
        NOORDER;

해당 테이블을 기준으로 DTO를 작성하겠습니다.

/kr/kro/rubisco/work/dto/MissionDTO.java
package kr.kro.rubisco.work.dto;

import java.util.Date;

public class MissionDTO {
    
    private Long idx;
    private String id;
    private String name;
    private String gender;
    private String nation;
    private String city;
    private Date date;
    
    public Long getIdx() {
        return idx;
    }
    public void setIdx(Long idx) {
        this.idx = idx;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String getNation() {
        return nation;
    }
    public void setNation(String nation) {
        this.nation = nation;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
}

롬복을 사용하지 않았습니다. 멤버변수만 정의하고 단축키 Alt + S, R을 누르면 제네레이터창이 뜹니다. 해당 제네레이터를 통해 getter와 setter를 만들었습니다.

다음으로 DAO 인터페이스를 작성하겠습니다. DAO는 Mapper에 의하여 구현됩니다.

/kr/kro/rubisco/work/dao/MissionDAO.java
package kr.kro.rubisco.work.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import kr.kro.rubisco.work.dto.MissionDTO;

@Mapper
public interface MissionDAO {

    public void create(MissionDTO mission) throws Exception;
    
    public List<MissionDTO> listAll() throws Exception;
    
    public void update(MissionDTO mission) throws Exception;

    public void delete(MissionDTO mission) throws Exception;
}

이제 mapper를 작성합니다.

/src/main/resources/mappers/missionMapper.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="kr.kro.rubisco.work.dao.MissionDAO">

<insert id="create">
insert into mission (idx, id, name, gender, nation, city, create_date)
values (mission_idx_seq.NEXTVAL, #{id}, #{name}, #{gender}, #{nation}, #{city}, #{date})
</insert>

<select id="listAll" resultType="MissionDTO">
select * from mission
</select>

<update id="update">
update mission
set id=#{id},
    name=#{name},
    gender=#{gender},
    nation=#{nation},
    city=#{city}
where idx = #{idx}
</update>

<delete id="delete">
delete from mission where idx = #{idx}
</delete>

</mapper>

서비스를 만드세요.

/kr/kro/rubisco/work/service/MissionService.java
package kr.kro.rubisco.work.service;

import java.util.List;

import kr.kro.rubisco.work.dto.MissionDTO;

public interface MissionService {

    public void insertMission(MissionDTO mission) throws Exception;
    
    public List<MissionDTO> getMissionList() throws Exception;
    
    public void updateMission(MissionDTO mission) throws Exception;
    
    public void deleteMission(MissionDTO mission) throws Exception;
}
/kr/kro/rubisco/work/service/impl/MissionServiceImpl.java
package kr.kro.rubisco.work.service.impl;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kr.kro.rubisco.work.dao.MissionDAO;
import kr.kro.rubisco.work.dto.MissionDTO;
import kr.kro.rubisco.work.service.MissionService;

@Service
@Transactional(readOnly = true)
public class MissionServiceImpl implements MissionService {

    private final MissionDAO missionDAO;
    
    public MissionServiceImpl(SqlSession sqlSession) {
        this.missionDAO = sqlSession.getMapper(MissionDAO.class);
    }
    
    @Override
    public void insertMission(MissionDTO mission) throws Exception {
        missionDAO.create(mission);
    }

    @Override
    public List<MissionDTO> getMissionList() throws Exception {
        return missionDAO.listAll();
    }

    @Override
    public void updateMission(MissionDTO mission) throws Exception {
        missionDAO.update(mission);
    }

    @Override
    public void deleteMission(MissionDTO mission) throws Exception {
        missionDAO.delete(mission);
    }

}

기본적인 CRUD만 구현했습니다. 이제 컨트롤러를 작성합니다.

/kr/kro/rubisco/work/controller/MissionRestController.java
package kr.kro.rubisco.work.controller;

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import kr.kro.rubisco.work.dto.MissionDTO;
import kr.kro.rubisco.work.service.MissionService;

@RestController
@RequestMapping("/mission")
public class MissionRestController {

    private final MissionService missionService;
    
    public MissionRestController(MissionService missionService) {
        this.missionService = missionService;
    }
    
    @GetMapping()
    public ResponseEntity<List<MissionDTO>> getMissionList() throws Exception {
        return ResponseEntity.ok(missionService.getMissionList());
    }
    
    @PostMapping()
    public ResponseEntity<MissionDTO> insertMission(@RequestBody MissionDTO mission) throws Exception {
        missionService.insertMission(mission);
        return ResponseEntity.ok(mission);
    }
    
    @PutMapping()
    public ResponseEntity<MissionDTO> updateMission(@RequestBody MissionDTO mission)throws Exception {
        missionService.updateMission(mission);
        return ResponseEntity.ok(mission);
    }
    
    @DeleteMapping()
    public ResponseEntity<MissionDTO> deleteMission(@RequestBody MissionDTO mission)throws Exception {
        missionService.deleteMission(mission);
        return ResponseEntity.ok(mission);
    }
}

빠른 확인을 위해 RestController를 작성했습니다. json 객체를 binding 하기 위해서는 jackson-databind를 의존성 추가해야합니다.

pom.xml
...
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.3</version>
        </dependency>
...

localhost:8080으로 이동하여 크롬 개발자도구를 열고 fetch를 통해 비동기 요청으로 insert를 해보세요.

POST 요청
fetch("/mission", {
    method: "POST",
    headers: {"Content-type": "application/json"},
    body: JSON.stringify({
        id:"id1", 
        name: "루비스코", 
        gender: "M", 
        nation: "한국",
        city: "천안",
        date: "2022-11-19"
    })
})
.then(res=>res.json())
.then(data=>console.log(data));

get요청을 보내면 리스트가 출력됩니다.

GET 요청
fetch("/mission")
    .then(res=>res.json())
    .then(data=>console.log(data));

insert가 잘 되어있습니다. 이번에는 PUT 요청으로 업데이트를 합니다.

PUT 요청
fetch("/mission", {
    method: "PUT",
    headers: {"Content-type": "application/json"},
    body: JSON.stringify({
        idx: 1,
        id:"id2", 
        name: "루비스코", 
        gender: "M", 
        nation: "한국",
        city: "천안",
        date: "2022-11-19"
    })
})
.then(res=>res.json())
.then(data=>console.log(data));

id를 변경했습니다. get요청으로 다시 확인하면 id가 바뀐것을 확인할 수 있습니다.

마지막으로 delete 요청을 하고 get요청으로 확인하면 삭제가 된 것을 확인할 수 있습니다.

DELETE 요청
fetch("/mission", {
    method: "DELETE",
    headers: {"Content-type": "application/json"},
    body: JSON.stringify({idx: 1})
})
.then(res=>res.json())
.then(data=>console.log(data));