Spring

코드로 배우는 스프링 웹 프로젝트

paulaner80 2016. 4. 26. 10:10
반응형


/*

STS 다운로드 주소 : https://spring.io/tools/sts/all

*/

1.3 Spring Project를 이용한 프로젝트 생성


Run As > Run on Server


스프링 프로젝트는 기본적으로 프로젝트 생성 시 지정하는 패키지의 마지막 경로가 웹 애플리케이션의 중간 경로가 됩니다.

"org.zerock.web"패키지를 작성했기 때문에 중간경로는 /web으로 지정됨



[ C:\sts-bundle\sts-3.7.2.RELEASE  sts가 있음]


1.4.4 Spring 프레임워크의 버전 변경

http://projects.spring.io/spring-framework/ 를 참고하면 현재 스프링의 여러 프로젝트 버전을 확인 할 수있음.


<properties>

<java-version>1.8</java-version>

<org.springframework-version>4.2.6.RELEASE</org.springframework-version>

<org.aspectj-version>1.8.9</org.aspectj-version>

<org.slf4j-version>1.7.21</org.slf4j-version>

</properties>


2장.

DI

IoC




3강

Download MySQL Community Server 설치

MySQL 설치 3306번 포트


3.2.2 MySQL의 새로운 스키마 추가


3.2.4 DB 테스트

문자열이 UTF-8로 지정됐는지 확인

    윈도우즈의 경우 Command line Client 실행 > 암호 입력 > 안됨.. (root 계정 password 생각 안남 -> http://cieneyes.tistory.com/358 안됨, http://cieneyes.tistory.com/358) 

--> 안되서 확인 못함.

-> 재설치함 mySQL id/pw = root/1111


3.2.3 사용자의 기본 스키마 설정

Default Schema : book_ex

3.3 JUnit

3.3.1 MySQL 테스트 관련 라이브러리

 Connect J라는 라이브러리 필요

 maven의 pom.xml을 이요하는 것이 편리

 

  <dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.38</version>

</dependency>

추가 (https://dev.mysql.com/downloads/connector/j/  에서 버전 확인 하고 추가함.)

3.3.2 JDK 1.7이상 JDBC 코딩

3.3.2.1 JDK 버전 변경과 jUnit의 버전 변경과

try-with 구문은 JDK 1.7 버전 이후에 지원되기 때문에 현제 프로젝트의 JDK 버전을 1.7이나 그 이상으로 변경해 줄 필요가 있습니다.


<properties>

<java-version>1.8</java-version>

<org.springframework-version>4.2.4.RELEASE</org.springframework-version>

<org.aspectj-version>1.6.10</org.aspectj-version>

<org.slf4j-version>1.6.6</org.slf4j-version>

</properties>

...

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

 

 3.3.2.2 JDBC 연결 테스트 코드 만들기

jUnit을 이용한 JDBC 연결 코드 작성은 src/test/java 밑에 MySQLConnectionTest 로 작성합니다.

/*

중간에 JDK 버전을 1.6 -> 1.8 로 변경했더니 Java compiler level does not match the version of the installed Java project facet 에러가 나옴.

Unbound classpath container: 'JRE System Library [JavaSE-1.8]' in project 'ex00' ex00 Build path Build Path Problem

->http://stackoverflow.com/questions/6798281/unbound-class-path-container-error-in-eclipse  : jre 1.8이 없어서 그런듯.. java Compiler, Project facet, pom.xml 의 버전을 1.7로 통일시켜주니까 사라짐.


*/

/*

 버전 맞춰서 해보니 아래 같은 메시지가 나옴...

 Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

 warning 메시지이므로 일단 넘어감.


-> ?useSSL=false 추가해서 해함..

*/

Chapter 04  스프링 + MyBatis + MySQL의 설정

   . 스프링과 MyBatis 연동을 위한 라이브러리 설정

   . 데이터베이스와의 연동을 담당하는 DataSource 객체 설정

   . MyBatis의 핵심이 SqlSessionFactory 객체 설정및 테스트

   

   

4.1 일반적이 스프링 웹 프로젝트 구성

  Presentation Layer  |  Business Logic Layer | Data Access Layer | Database

  

4.1.1 이 책의 프로젝트 구성

  Model (jsp, html, css, javascript) | Controller | Service | DAO | MyBatis | Database

  

  Presentation Layer 의 경우 View를 구성하는 부분과 Controller라는 부분으로 분리되어서 작성됩니다.

  Data Access Layer의 경우는 MyBatis가 추가 됩니다. DAO의 경우 MyBatis를 호출하고 사용하는 구조로 만들어집니다.


4.2 MyBatis와 구성

   구조

      SesstionTemplete, SqlSessionTemplete, DataSource, SqlSessionFactory, spring-mybatis, MyBatis

4.3 MyBatis 연동을 위한 준비

4.3.1 spring-jdbc, spring-test, MyBatis, mybatis-spring 추가

    스프링 + MyBatis-spring + MyBatis <-> MySQL

http://blog.mybatis.org/ 에서 버전확인

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.3.1</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.2.4</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>${org.springframework-version}</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>${org.springframework-version}</version>

</dependency>

spring-test는 스프링과 MyBatis가 정상적으로 연동되었는지 확인하는 용도로 사용합니다.

4.3.2 Spring Project에서 root-context.xml 파일의 수정

    src/main/webapp/WEB-INF/spring/root-content.xml 파일은 STS가 스프링 프로젝트를 생성할 때 만들어주는 파일에서 가장 중요한 파일입니다.

웹자원과 관련되지 않은 모든 자원의 설정을 위해서 존재합니다.

root-conext.xml파일에서 스프링 프레임워크에 다양한 설정을 하기위해서는  STS상에서 Namespace 탭을 이용해 사용 가능한 xml 태그의 폭을 넓혀주어야합니다.

4.4 MySQL과 연결을 담당하는 DataSource 설정하기

root-context.xml

DataSource는 JDBC의 커넥션을 처리하는 기능을 가지고 있기 때문에 데이터베이스와 연동작업이 반드시 필요합니다.

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="com.mysql.jdbc.Driver"/>

<property name="url" value="jdbc:mysql://127.0.0.1:3306/book_ex"/>

<property name="username" value="zerock"/>

<property name="password" value="zerock"/>

</bean>

[url의 value값중에 땡땡을 빼 먹어서 에러났는데 찾는데 시간 오래걸린적 있음.]

4.4.1 DataSource의 테스트 진행

스프링은 하나의 설정에 문제가 있다면 정상적으로 로딩이 되지않기 때문에 최대한 빨리 변경된 설정에 대해서 테스트를 진행해야만 합니다.

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations ={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})


@RunWith, @ContextConfiguration 애노테이션은 현재 테스트 코드를 실행할 때 스프링이 로딩되도록 하는 부분입니다.

@ContextConfigurationv의 lotation속성 경로에 xml 파일을 이용해서 스프링이 로딩됩니다.

인스턴스 변수의 @Injec 어노테이션처리된 DataSource는 스프링이 생성해서 주입해주므로 개발자가 객체생성 혹은 다른 작업을 하지 않아도 됩니다.

DataSourceTest

package org.zerock.web;


import static org.junit.Assert.fail;


import java.sql.Connection;


import javax.inject.Inject;

import javax.sql.DataSource;


import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations ={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class DataSourceTest {

@Inject

private DataSource ds;


@Test

public void testConnection() {

try(Connection conn = ds.getConnection()){

System.out.println(conn);

}catch (Exception e) {

// TODO: handle exception

}

}


}


@Inject 애노테이션 처리된 DataSource는 스프링이 생성해서 주입해 주므로 개발자가 객체 생성 혹은 다른 작업을 하지 않아도 됩니다.


4.5 MyBatis 연결

DataSource 연결 설정이 정상적으로 이루어진 후에 진행해야합니다.

4.5.1 SqlSessionFactory 객체 설정

MyBatis와 스프링의 연동자업에서 핵심은 Connection을 생성하고, 처리하는 SqlSessionFactory의 존재입니다.

SqlSessionFactory는 데이터베이스와의 연결과 SQL 실행에 대한 모든것을 가진 가장 중요한 객체입니다.

스프링을 이용할 때는 SqlSessionFactory를 생성해 주는 특별한 객체를 설정해 주는데 SqlSessionFactoryBean이라는 클래스를 사용합니다.

root-context.xml에 등록

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource"></property>

</bean>

4.5.2 mybatis-config.xml 파일의 추가

MyBatis는 SQL Mapping 프레임 워크로 별도의 설정파일을 가질 수 있습니다.

이를 이용하면 스프링의 설정과는 별도로 사용하는 모든 MyBatis의 설정 기능을 활용할 수 있습니다.

4.5.2.1 mybatis-config.xml 파일의 작성

'src/main/resources'내에 파일을 생성해 둡니다.

http://www.mybatis.org/mybatis-3/ko/getting-started.html 에서 복사해서 씀.


<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource"></property>

<property name="configLocation" value="classpath:mybatis-config.xml"></property>

</bean>




4.5.3 MyBatis 연결 테스트

MyBatisTest

import javax.inject.Inject;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations ={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class MyBatisTest {

@Inject

private SqlSessionFactory sqlFactory;

@Test

public void testFactory(){

System.out.println(sqlFactory);

}

@Test

public void testSession() throws Exception{

try(SqlSession session = sqlFactory.openSession()){

System.out.println(session);

}catch(Exception e){

e.printStackTrace();

}

}

}


4.6 작업내용 정리

테스트를 하는 습관은 나중에 유지보수를 진행하거나 개별적인 코드를 하나로 통합하는 과정에서 그 위력이 드러납니다.


Chapter 05 모델2 방식과 스프링 MVC

스프링 MVC는 모델 2 방식 구조를 이용


5.1 모델2 패턴의 이해

모델 : 데이터 혹은 데이터를 처리하는 영역을 의미합니다.

뷰 : 결과 화면을 만들어내는데 사용하는 자원을 의미합니다.

컨트롤러 : 웹의 요청을 처리하는 존재로 뷰와 모델 사이의 중간 통신 역활을 합니다.

모든 요청은 기본적으로 컨트롤러를 호출합니다.

각 컨트롤러는 자신을 호출하는 특정한 URI 경로를 가지고 있습니다.

5.1.1 모델2에서 Front Controller 패턴으로

Front Controller 패턴의 가장 중요한 변화는 전체 로직의 일부만 컨트롤로가 처리하도록 변경되었다는 점입니다.

흔히 '위임(Delegation')이라고 하는데, 전체 로직의 일부를 컨트롤러에게 위임하고 모든 흐름제어는 앞쪽의 Front Controller가 담당하게 됩니다.

5.1.2 스프링 MVC의 구조

(1)사용자의 모든 요청은 스프링 MVC의 Front Controller에게 전달됩니다.

(2)전달된 요청은 적절한 컨트롤러를 찾아서 호출 하게되는데, 이때 사용되는 컨트롤러의 작업이 개발자의 몫으로 남겨진일

  컨트롤러는 적절한 서비스 객체를 찾아서 호출하고

(3)서비스는 데이터베이스의 작업을 담당하는 DAO를 이용해서 원하는 데이터를 요청하게 됩니다.

(4)DAO객체는 MyBatis를 이용하는 Mapper를 통해서 원한는 작업을 수행하게 됩니다.

(5)서비스가 처리한 데이터를 컨트롤러에게 전달하게 되면

(6)컨트롤러는 다시 스프링 MVC쪽으로 데이터를 전달하게 됩니다.

스프링MVC가 처리해 주는 작업

URI를 분석해서 적절한 컨트롤러를 찾는 작업

컨트롤러에 필요한 메소드를 호출하는 작업

컨트롤러의 결과 데이터를 뷰로 전달하는 작업

적절한 뷰를 찾는 작업

개발자가 해야하는 작업

특정 URI에 동작하는 컨트롤러를 설계하는 작업

서비스 객체의 생성

DAO 객체의 생성

컨트롤러 내에 원하는 결과를 메소드로 설계

뷰에서 전달받은 데이터의 출력



5.2 스프링 MVC의 컨트롤러

스프링 MVC의 컨트롤러가 무엇을 처리해주는가

파라미터 수집 : 데이터 추출 -> VO(Value Object) 혹은 DTO(Data Transfer Object)로 변환

애노테이션을 통한 가편 설정 : 스프링 MVC 설정은 크게 XML과 애노테이션을 사용할 수 있지만, 애노테이션을 사용하는 경우가 더 많습니다. 애노테이션을 추가하는 작업을 통해서 요청이나 응답에 필요한 모든 처리를 완료할 수 있습니다.

로직의 집중 : 스프링 MVC 컨트롤러의 경우 각 메소드 마다 필요한 애노테이션을 설정할 수 있기 때문에 여러 메소드를 하나의 컨트롤러에 집중해서 작성할 수 있습니다.

테스트의 편리함 : 스프링은 테스트 모듈을 사용해서 스프링 MVC로 작성된 코드를 WAS의 실행없이도 테스트 할 수있는 편리한 방법을 제공합니다.


기존 java 코드와 다른 점

.상속이나 인터페이스 구현없이 @Controller라는 애노테이션만 추가하면 됨

.파라미터와 리턴타입에 제약이 없음.

.스프링 MVC가 제공하는 유용한 클래스들이 존재


5.2.1 Spring Project의 servlet-context.xml 파일

appServlet 폴더 내에 존재하는 servlet-context.xml은 스프링 MVC 관련 설정만을 분리하기 위해서 만들어진 파일입니다.

-. <annotation-driven> : 클래스 선언에 어노테이션을 이용해서 컨트롤러를 작성할 수 있다는 선언이다.

- .InternalResourceViewResolver : 뷰를 어떻게 처리하는가에 대한 설정    

- .<rescource>는 웹에서 이미지나 CSS나 JavaScript 파일과 같이 고정된 자원들의 위치를 의미합니다.

- . <component-scan> base-package 속성값에 해당하는 패키지 내부의 클래스들을 조사한다는 뜻입니다.

5.2.2 스프링 MVC에서 주로 사용하는 어노테이션의 종류

@Controller - 스프링 MVC의 컨트롤러 객체임을 명시하는 애노테이션 - 클래스

@RequestMapping - 특정 URI에 매칭되는 클래스나 메소드임을 명시하는 애노테이션 - 클래스, 메소드

@RequestParam

@RequestHeader

@PathVariable

@CookieValue

@ModelAttribute

@SessionAttribute

@InitBinder

@ResponseBody

@RequestBody

@Repository

@Service



5.2.3 기초적인 컨트롤러 생성 실습


5.2.3.1 void 리턴 타입의 경우

현재 경로에 해당하는 JSP파일을 실행하게 됩니다.

package org.zerock.web;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;


@Controller

public class SampleController {

private static final Logger logger = LoggerFactory.getLogger(SampleController.class);

@RequestMapping("doA")

public void doA(){

logger.info("doA called");

}


@RequestMapping("doB")

public void doB(){

logger.info("doB called");

}

}

Run on Server

INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/],methods=[GET]}" onto public java.lang.String org.zerock.web.HomeController.home(java.util.Locale,org.springframework.ui.Model)

INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/doB]}" onto public void org.zerock.web.SampleController.doB()

INFO : org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped "{[/doA]}" onto public void org.zerock.web.SampleController.doA()

메소드의 리턴타입이 void인 경우에는 스프링 MVC는 현제 경로에 해당하는 JSP파일을 실행하게 됩니다.

/web/WEB-INF/views/doA.jsp

     [ 프로젝트 생성시 패키지의 마지막 이름이 경로가 되므로 지금은 /web 이 됩니다. ]



5.2.3.2 String 리턴 타입의 경우

메소드의 리턴타입이 문자열인 경우라면 결과는 '문자열.jsp'파일을 찾아서 실행하게 됩니다.

메소드의 선언에 사용된 @RequestMapping은 URI가 '/doC'인 경우에 동작함을 의미합니다.

파라미터에 사용된 @ModelAttribute("msg")는 요청시 'msg'이름의 파라미터를 문자열로 처리해 주고, 뷰에 전달하도록합니다.

doC() 메소드의 리턴 값으로 사용된 'result'는 결과적으로 /WEB-INF/views/result.jsp파일을 찾아서 실행하게 됩니다.

package org.zerock.web;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

import org.springframework.web.bind.annotation.RequestMapping;


@Controller

public class SampleController2 {

private static final Logger logger = LoggerFactory.getLogger(SampleController2.class);

@RequestMapping("doC")

public String doC(@ModelAttribute("msg") String msg){

logger.info("doC called");

return "result";

}


}


5.2.3.3 만들어진 결과 데이터를 전달해야하는 경우

컨트롤러를 제작하면서 가장 많이 해야하는 작업은 다른 객체의 도움을 받아 만들어진 데이터를 뷰로 전달하는 것이다. 이때는 스프링 MVC 모델 객체를 사용해서 간편하게 처리할 수 있습니다.

Model 클래스는 스프링 MVC에서 기본적으로 제공되는 클래스 입니다. 이 클래스의 용도는 뷰에 원하는 데이터를 전달하는 일종의 컨테이너나 상자역활을 한다고 생각하시면 됩니다.

.addAttribute("이름", 객체) :객체에 특별한 이름을 부여해 뷰에서 이름값을 이용해 객체 처리

.addAttribute(객체) : 이름을 지정하지 않는 경우에는 자동으로 저장되는 개체의 클래스명 앞글자를 소문자로 처리한 클래스명을 이름으로 간주

5.2.3.4 리다이렉트 해야 하는 경우

5.2.3.5 JSON 데이터를 생성하는 경우

pom.xml을 사용해서 Jackson-databind 라이브러리를 추가해야합니다.

@ResponseBody 애노테이션을 추가해 주는 작업만 하면 됩니다.

5.3 WAS없이 컨트롤러를 테스트하기

spring-test를 사용해서 실행할 때 가능하면 WAS의 Servlet스펙 버전을 일치시켜서 테스트 하는 것이 좋습니다. 예를 들어 Tomcat을 이용하면서 Servlet 스펙 3.1을 이용한다면 테스트환경에서도 같이 맞춰주는것이 좋습니다.

스프링 MVC를 테스트하기위해서는 pom.xml의 javax.servlet 라이브러리의 버전을 변경해야만 올바르게 사용할 수 있습니다.

[before]

<groupId>javax.servlet</groupId>

<artifactId>servlet-api</artifactId>

<version>2.5</version>

<scope>provided</scope>

[after]

<groupId>javax.servlet</groupId>

<artifactId>javax.servlet-api</artifactId>

<version>3.1.0</version>

라이브러리의 수정이 제대로 이루어 지지 않았을 때 테스트 코드를 실행할 경우 다음과 같은 에러 메시지가 출력됩니다.

java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig

[테스트 코드]

package org.zerock.web;


import javax.inject.Inject;


import org.junit.Before;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.test.context.web.WebAppConfiguration;

import org.springframework.test.web.servlet.MockMvc;

import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import org.springframework.web.context.WebApplicationContext;


@RunWith(SpringJUnit4ClassRunner.class)

@WebAppConfiguration

@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class SampleControllerTest {

private static final Logger logger = LoggerFactory.getLogger(SampleControllerTest.class);

@Inject

private WebApplicationContext wac;

private MockMvc mockMvc;

@Before

public void setup(){

this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();

}


@Test

public void test() throws Exception{

mockMvc.perform(MockMvcRequestBuilders.get("/doA"));

}


}

[테스트 결과 메시지]

INFO : org.springframework.mock.web.MockServletContext - Initializing Spring FrameworkServlet ''

INFO : org.springframework.test.web.servlet.TestDispatcherServlet - FrameworkServlet '': initialization started

INFO : org.springframework.test.web.servlet.TestDispatcherServlet - FrameworkServlet '': initialization completed in 18 ms

INFO : org.zerock.web.SampleControllerTest - setup...

INFO : org.zerock.web.SampleController - doA called


06 스프링 + MyBatis

MyBatis 는 JDBC 에서 개발자가 직접처리하는 PreparedStatement의 '?'에 대한 설정이나 ResultSet을 이용한 처리가 이루어지기 때문에 기존 방식에 비해 개발의 생산성이 좋아집니다.

MyBatis 는 애노테이션을 지원하고 인터페이스와 애노테이션을 통해서 SQL 문을 설정하고 처리할 수 있는 형태로 발전하였습니다.

XML만을 이용해서 SQL문을 설정, DAO에서는 XML을 찾아서 실행하는 코드를 작성하는 방식

애노테이션과 인터페이스만을 이용해서 SQL문을 작성

-장점 : 별도의 DAO없이도 개발이 가능하기 때문에 생산성이 크게 증가

-단점 : SQL문을 애노테이션으로 작성하므로, 매번 수정이 일어나는 경우 다시 컴파일

인터페이스와 XML로 작성된 SQL 문의 활용

국내의 대부분 프로젝트는 XML만을 이용해서 SQL문을 작성하고, 별도의 DAO를 만드는 방식을 선호합니다.

MyBatis 를 XML을 사용해서 작성하는 경우 코딩순서

6.1 테이블 생성 및 개발 준비

6.1.1 데이터베이스의 테이블 생성

6.1.2 도메인 객체를 위한 클래스 설계

계발을 할 때 가장 중요한 용어가 될 만한 명사를 흔히 '도메인'이라고 표현합니다.

개발자에게 도메인 클래스는 일반적으로 특정 테이블과 유사한 속성을 가지는 클래스를 의미합니다.

6.2 DAO 인터페이스 작성

package org.zerock.persistence;


import org.zerock.domain.MemberVO;


public interface MemberDAO {

public String getTime();

public void insertMember(MemberVO vo);


}

6.3 XML Mapper의 작성

- XML로 작성된 Mapper의 위치(저장경로) 결정

- XML Mapper 파일을 작성하고 필요한 DTD 추가

- SQL 작성

6.3.1 Mapper 파일의 저장경로

src/main/resources/mappers

6.3.2 XML Mapper의 작성

<?xml version="1.0" encoding="UTF-8"?>


<!DOCTYPE mapper

PUBLIC "-//mybatis.org//DTD Mapper 3.0 //EN"

"HTTP://mybatis.rog/dtd/mybatis-3-mapper.dtd"

>


<mapper namespace="org.zerock.mapper.MemberMapper">

<select id="getTime" resultType="string">

select now()

</select>

<insert id="insertMember">

insert into tbl_member (userid, userpw, username, email) values (#{userid}, #{userpw}, #{username},#{email})

</insert>


</mapper>

6.3.3 myBatis-Spring에서 XML Mapper 인식

root-content.xml을 이용해서 아래와 같이 설정을 변경합니다.

<property name="mapperLocations" value="classth:mappers/**/*Mapper.xml"></property>

설정의 핵심은 mapperLocations라는 속성을 추가하고, 작성된 mappers 폴더 내에 어떤 폴더건 관계없이 파일의 이름이 Mapper.xml로 끝나면 자동으로 인식하도록 설정하는 것입니다.

6.4 DAO 인터페이스의 구현

6.4.1 SqlSessionTemplete의 설정

myBatis-Spring에서 제공하는 SqlSessionTemplate은 MyBatis의 SqlSession 인터페이스를 구현한 클래스로 기본적인 트랜잭션의 관리나 처리 안정성 등을 보장해주고 데이터베이스의 연결과 종료를 책임집니다.

SqlSessionTemplate은 SqlSessionFactory를 생성자로 주입해서 설정합니다.

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">

<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>

</bean>

6.4.2 구현 클래스 작성하기

@Repository는 DAO를 스플링에 인식시키기 위해 사용합니다

package org.zerock.persistence;


import javax.inject.Inject;


import org.apache.ibatis.session.SqlSession;

import org.springframework.stereotype.Repository;

import org.zerock.domain.MemberVO;


@Repository

public class MemberDAOImpl implements MemberDAO{

@Inject

private SqlSession sqlSession;

private static final String namespace = "org.zerock.mapper.MemberMapper";


@Override

public String getTime() {

// TODO Auto-generated method stub

return sqlSession.selectOne(namespace+".getTime");

}


@Override

public void insertMember(MemberVO vo) {

// TODO Auto-generated method stub

sqlSession.insert(namespace+".insertMember", vo);

}

}

MyBatis의 SqlSession에는 SQL문을 처리할 수 있는 다음과 같은 기능들이 존재합니다.

<T> T selectOne(String statement, Object parameter)

<E> List<E> selectList(String statement, Object parameter)

<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)

int insert(String statement, Object parameter)

int update(String statement, Object parameter)

int delete(String statement, Object parameter)

6.5 스프링에 빈으로 등록하기

MemberDAOImple이 @Repository 어노테이션을 설정되더라도 스프링에서 해당 패키지를 스캔하지 않으면 제대로 스프링의 빈으로 등록되지 못합니다. 이 처리는 root-content.xml을 이용해서 아래와 같이 설정합니다.

<context:component-scan base-package="org.zerock.persistence"></context:component-scan>

6.6 테스트 코드의 작성

6.7 MyBatis의 로그 log4jdbc-log4j2

[pom.xml]

<dependency>

<groupId>org.bgee.log4jdbc-log4j2</groupId>

<artifactId>log4jdbc-log4j2-jdbc4</artifactId>

<version>1.16</version>

</dependency>

[root-context.xml]

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>

<property name="url" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/book_ex"/>

<property name="username" value="zerock"/>

<property name="password" value="zerock"/>

</bean>

<?xml version="1.0" encoding="UTF-8"?>

[logback]

<configuration>

<include resource="org/springframework/boot/logging/logback/base.xml"/>


<!-- log4jdbc-log4j2 -->

<logger name="jdbc.sqlonly"        level="DEBUG"/>

<logger name="jdbc.sqltiming"      level="INFO"/>

<logger name="jdbc.audit"          level="WARN"/>

<logger name="jdbc.resultset"      level="ERROR"/>

<logger name="jdbc.resultsettable" level="ERROR"/>

<logger name="jdbc.connection"     level="INFO"/>

</configuration>

[log4jdbc.log4j2.properties]

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator



6.8 MyBatis의 #{} 문법

#{}는 다음과 같은 규칙이 적용됩니다.

- 파라미터가 여러 속성을 가진 객체인경우 '#{num}은 getNum()혹은 setNum()을 의미합니다.

- 파라미터가 하나이고, 기본자료형이나 문자열인 경우 값이 그대로 전달됩니다.

- 파라미터가 Map 타입인 경우 '#{num}은 Map 객체의 키 값이 'num'인 값을 찾습니다.

=====================================================================================================

Part 2


java 1.7 로함...


프로젝트에 필요한 라이브러리를 pom.xml을 이용해서 추가합니다.


추가된 라이브러리는 MySQL 드라이버, MyBatis, MyBatis-Spring, log4jdbc-log4j2 관련 설정입니다.

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>${org.springframework-version}</version>

</dependency>


<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.38</version>

</dependency>

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>${org.springframework-version}</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.3.1</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.2.4</version>

</dependency>

<dependency>

<groupId>org.bgee.log4jdbc-log4j2</groupId>

<artifactId>log4jdbc-log4j2-jdbc4</artifactId>

<version>1.16</version>

</dependency>


테스트를 올바르게 진행하기 위해서 Junit 버전과 Servlet 버전을 변경합니다.



logback.xml, log4jdbc.log4j2.properties, mybatis-config.xml 복사

1.4 개발 전 준비 - 데이터베이스 관련

1.4.1 DataSource 등록 

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>

<property name="url" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/book_ex"/>

<property name="username" value="zerock"/>

<property name="password" value="zerock"/>

</bean>



1.4.2 DataSource의 테스트

package org.zerock.test;


import static org.junit.Assert.fail;


import java.sql.Connection;


import javax.inject.Inject;

import javax.sql.DataSource;


import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class DataSourceTest {

@Inject

DataSource ds ;


@Test

public void test() {

try (Connection con = ds.getConnection()){

System.out.println(con);

} catch (Exception e) {

// TODO: handle exception

e.printStackTrace();

fail("exeption");

}

}

}


1.4.3 개발 패키지 구성

- ~.domain : Value Object 가 사용하는 패키지

- ~.service : 서비스 인터페이스와 구현 클래스 패키지

- ~.persistence : MyBatis의 DAO 패키지

- ~.controller : 스프링 MVC의 컨트롤러 패키지

- resources/mappers 폴더 : MyBatis XML Mapper 위치

1.4.4 테이블 생성 작업

create table tbl_board(

bno INT NOT NULL AUTO_INCREMENT,

title VARCHAR(200) NOT NULL,

content TEXT NULL,

writer VARCHAR(50) NOT NULL,

regdate TIMESTAMP NOT NULL DEFAULT now(),

viewcnt INT DEFAULT 0,

PRIMARY KEY (bno)

);

1.4.5 테스트를 위한 SQL 준비

insert into tbl_board (title, content, writer) values('제목입니다.', '내용입니다', 'user00');


select * from tbl_board where bno =1;


update tbl_board set title='수정된 제목' where bno=1;

1.5 스프링의 UTF-8 처리 필터링 등록

<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-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

2 영속(persistence) 계층, 비지니스 계층

데이터베이스 관련된 작업을 먼저해서 좋은 점 중 하나는 그 사이에 

변경되는 화면의 설계를 미룰수 있다는 점.

WAS 없이 테스트 할 수 있다는 점

2.1 BoardVO 작성

package org.zerock.domain;


import java.util.Date;


public class BoardVO {

int bno;

String title;

String content;

String wirter;

Date regdate;

int viewcnt;

...

}

2.2 DAO 생성과 XML Mapper 작업

2.2.1 XML 네임스페이스의 추가

root-conext.xml -> 아래쪽의 탭 매뉴 중에서 namespace를 선택합니다.

필요한 namespace는 beans, context, mybatis-spring 이므로 체크하고 저장합니다.

2.2.2 SessionFactory, SqlSessionTemplate의 추가

part 1에서 설정했던  MyBatis의 SqlSessionFactory, SqlSessionTemplate을 등록합니다.

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>

<property name="url" value="jdbc:log4jdbc:mysql://127.0.0.1:3306/book_ex"/>

<property name="username" value="zerock"/>

<property name="password" value="zerock"/>

</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="dataSource"/>

<property name="configLocation" value="classpath:/mybatis-config.xml"/>

<property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml"/>

</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">

<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>

</bean>

2.2.3 BoardDAO의 생성

package org.zerock.persistence;


import java.util.List;


import org.zerock.domain.BoardVO;


public interface BoardDAO {


public void create(BoardVO vo) throws Exception;

public BoardVO read(Integer bno) throws Exception;

public void update(BoardVO vo) throws Exception;

public void delete(BoardVO vo) throws Exception;

public List<BoardVO> lisetAll() throws Exception;

}


2.2.4 XML Mapper에서의 SQL 처리

[boardMapper 작성]

<?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="org.zerock.mapper.boardMapper">

 

 <insert id="create">

 insert into tbl_board (title, content, writer)

 values (#{title}, #{content}, #{writer})

 </insert>

 

 <select id="read" resultType="org.zerock.domain.BoardVO">

 select bno, title, content, writer, regdate, viewcnt

 from tbl_board

 where bno = #{bno}

</select>

 

<update id="update">

update tbl_board set title=#{title}, content=#{content}

where bno =#{bno}

</update>

 

<delete id="delete">

delete from tbl_board where bno=#{bno}

</delete>

 

<select id="selectAll" resultType="org.zerock.domain.BoardVO">

<![CDATA[

select bno, title, content, writer, regdate, viewcnt

from tbl_board

where bno>0

order by bno desc, regdate desc

]]>

</select>

 

<select id="listPage" resultType="BoardVO">

<![CDATA[

select bno, title, content, writer, regdate, viewcnt

from tbl_board

where bno>0

order by bno desc, regdate desc

limit #{page} , 10

]]>

</select>

 

<select id="listCriteria" resultType="BoardVO">

<![CDATA[

select bno, title, content, writer, regdate, viewcnt

from tbl_board

where bno>0

order by bno desc, regdate desc

limit #{pageStart}, #{perPageNum}

]]>

 

</select>

 

<select id="countPaging" resultType="int">

<![CDATA[

select count(bno)

from tbl_board

where bno>0

]]>

</select>


 </mapper>

2.2.5 BoardDAO의 구현 클래스 BoardDAOImpl

BoadDAOImpl 클래스를 작성한 후에는 반드시 스프링의 빈으로 제대로 등록 되었는지 root-context.xml 을 선택하고 Beans Graph 탭을 이용해서 확인하도록합니다.

package org.zerock.persistence;


import java.util.List;


import javax.inject.Inject;


import org.apache.ibatis.session.SqlSession;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Repository;

import org.zerock.domain.BoardVO;

import org.zerock.domain.Criteria;


@Repository

public class BoardDAOImpl implements BoardDAO{

Logger logger = LoggerFactory.getLogger(BoardDAOImpl.class);

@Inject

private SqlSession session;

private static String namespace = "org.zerock.mapper.boardMapper";


@Override

public void create(BoardVO vo) throws Exception {

session.insert(namespace+".create", vo);

}


@Override

public BoardVO read(Integer bno) throws Exception {

return session.selectOne(namespace+".read", bno);

}


@Override

public void update(BoardVO vo) throws Exception {

System.out.println("update(");

session.update(namespace+".update", vo);

}


@Override

public void delete(Integer bno) throws Exception {

session.delete(namespace+".delete", bno);

}


@Override

public List<BoardVO> listAll() throws Exception {

logger.info("listAll()------------------------------------------------------------------");

return session.selectList(namespace+".selectAll");

}


@Override

public List<BoardVO> listPage(int page) throws Exception {

if(page<= 0){

page =1;

}

page=(page-1)*10;

logger.info("page : "+page);

return session.selectList(namespace+".listPage", page);

}


@Override

public List<BoardVO> listCriteria(Criteria cri) throws Exception {

return session.selectList(namespace+".listCriteria", cri);

}


@Override

public int countPaging(Criteria cri) throws Exception {

return session.selectOne(namespace+".countPaging", cri);

}

}


2.2.6 BoadDAO의 테스트

[

spring autowire란 

http://blog.naver.com/PostView.nhn?blogId=sky881012&logNo=220465432829

autowire는 의존관계자동설정 어노테이션이라는 것인데, 의존으로 자동설정을 하려는 어노테이션 입니다~

]

[

@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})에서 오타 남.

>>Injection of autowired dependencies failed

]

2.2.7 <typeAliases>의 적용

XML Mapper를 사용하는데 있어서 매번 parameterType이나 resultType을 패키지까지 포함된 클래스명을 작성하는 일이 번거롭다면 MyBatis의 설정 파일인 mybatis-config.xml을 사용해서 <typeAliases>를 작성해 줄 수 있습니다.

<typeAliases>

<package name="org.zerock.domain"/>

</typeAliases>


2.3 계층별 구현 - 비지니스 계층

스프링에서 비지니스 영역은 일반적으로 서비스(Service)라는 이름을 칭합니다.

1)요구사항을 XXXService 인터페이스를 정의 하고

2) XXXServiceImple이라는 구현 객체를 만들어 주는 순서로 진행됩니다.

2.3.1 비지니스 계층의 구현

비지니스 계층은 쉽게 말해서 컨트롤러와 DAO사이의 접착제 역활을 합니다.

- 비지니스 계층은 고객마다 다른 부분을 처리할 수 있는 완충장치 역활을 합니다.

- 각 회사마다 다른 로직이나 규칙을 데이터베이스에 무관하게 처리할 수 있는 완충 영역으로 존재할 필요가 있습니다.

- 컨트롤러와 같은 외부 호출이 영속 계층에 종속적인 상황을 막아 줍니다.

- 만일 컨트롤러가 직접 영속계층의 데이터베이스를 이용하게 되면 트랜잭션의 처리나 예외의 처리등 모든 로직이 컨트롤러로 집중됩니다. 비지니스 계층은 컨트롤러로 하여금 처리해야하는 일을 분업하게 만들어 줍니다.

2.3.1.1

2.3.2 비지니스 계층의 테스트

/**

*/

package org.zerock.service;


import static org.junit.Assert.fail;


import javax.inject.Inject;


import org.junit.Test;

import org.junit.runner.RunWith;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.zerock.domain.BoardVO;


/**

* @author changnam

*

*/

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class BoardServiceTest {

Logger logger = LoggerFactory.getLogger(BoardServiceTest.class);

@Inject

BoardService service;


/**

* Test method for {@link org.zerock.service.BoardService#regist(org.zerock.domain.BoardVO)}.

*/

@Test

public void testRegist() {

BoardVO board = new BoardVO();

board.setTitle("새로운 title을 넣습니다.t");

board.setWriter("user00");

board.setContent("새로운 Content을 넣습니다t");

try {

service.regist(board);

} catch (Exception e) {

fail();

// TODO Auto-generated catch block

e.printStackTrace();

}

}


/**

* Test method for {@link org.zerock.service.BoardService#read(java.lang.Integer)}.

*/

@Test

public void testRead() {

try {

service.read(2);

} catch (Exception e) {

fail();

// TODO Auto-generated catch block

e.printStackTrace();

}

}


/**

* Test method for {@link org.zerock.service.BoardService#modify(org.zerock.domain.BoardVO)}.

*/

@Test

public void testModify() {

BoardVO board = new BoardVO();

board.setTitle("새로운 title 수정.");

board.setWriter("user00");

board.setContent("새로운 Content을 넣습니다.");

try {

service.modify(board);

} catch (Exception e) {

fail();

// TODO Auto-generated catch block

e.printStackTrace();

}

}


/**

* Test method for {@link org.zerock.service.BoardService#remove(java.lang.Integer)}.

*/

@Test

public void testRemove() {

try {

service.remove(2);

} catch (Exception e) {

// TODO Auto-generated catch block

fail();

e.printStackTrace();

}

}


/**

* Test method for {@link org.zerock.service.BoardService#listAll()}.

*/

@Test

public void testListAll() {

logger.info("testListAll()------------------------------------------------------------------");

try {

service.listAll();

} catch (Exception e) {

// TODO Auto-generated catch block

fail();

e.printStackTrace();

}

}


}

03 등록 구현

- 컨트롤러와 프리젠테이션 계층

3.1.1.1 공통 경로의 처리와 호출방식

Spring 2.5 버전 이상의 경우 특별한 경우가 아니면 모듈의 개수에 맞게 컨트롤러의 숫자를 제작하게 됩니다.

컨트롤러를 설계할 때 가장 중요한 점은 'URI를 어떤 방식으로 사용하게 할 것인가'에 대한 고민입니다.

Spring MVC의 경우 애노테이션을 통해서 URI를 분기하거나 GET/POST 방식을 지정하기 때문에 위와 같은 결정과 더불어 해당 작업의 URI를 미리 결정해 두는 작업이 필요합니다.

3.1.1.2 리다이렉트이 처리방식

최근 웹 페이지는 Ajax 등의 방식을 많이 사용해서 화면 전환이 최소화 되거나 슬라이드 이동 등의 기능을 사용해서 화면에 보이는 내용이 달라지는 방식을 많이 사용합니다.

 

3.1.2 컨트롤러의 선언

스프링 MVC를 이용하는 경우에는 컨트롤러 역시 @Controller라는 애노테이션을 추가하는 것만으로 설정이 완료됩니다.

3.1.2.1 등록 작업과 파라미터의 결정

스프링 MVC는 메소드의 파라미터와 리턴 타입이 상당히 유연하기 때문에 개발자는 설계할 때 모든 것을 결정해야 합니다.

파라미터를 결정하는 것은 다음과 같은 사항을 고려해서 설계해야 합니다.

- 파라미터의 수집은 스프링 MVC에서 자동으로 이루어지므로, 파라미터의 수집일 필요하면 원하는 객체를 파라미터로 선언합니다.

- 특별한 경우가 아니라면 VO 클래스 혹은 DTO 클래스를 파라미터로 사용하는 것이 편리합니다.

- 브라우저에서 들어오는 요청(request)이 자동으로 파라미터로 지정한 클래스의 객체 속성값으로 처리되는 데 이를 바인딩이라고 합니다.

- 스프링 MVC의 모델 객체는 해당 메소드에서 뷰(jsp등)에 필요한 데이터를 전달하는 용도로 사용됩니다. 그러므로 메소드 내에서 뷰로 전달할 데이터가 있다며 Model을 파라미터로 선언해 주는 것이 편리합니다. 

3.1.2.2 등록 작업의 메소드

1. 등록을 위한 입력 페이지를 보는 경우(GET 방식으로 처리)

2. 실제로 데이터를 처리하는 부분(POST 방식으로 처리)

value와 method 속성은 둘 다 배열로 여러가지 속성값을 지정할 수 있습니다.

package org.zerock.controller;


import javax.inject.Inject;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.zerock.domain.BoardVO;

import org.zerock.service.BoardService;


@Controller

@RequestMapping("/board/*")

public class BoardController {

private static final Logger logger = LoggerFactory.getLogger(BoardController.class);

@Inject

private BoardService service;

@RequestMapping(value="/register", method=RequestMethod.GET)

public void registGET(BoardVO board, Model model) throws Exception{

logger.info("register get...");

}

@RequestMapping(value="/register", method=RequestMethod.POST)

public String registPOST(BoardVO board, Model model) throws Exception{

logger.info("regist post");

logger.info(board.toString());

service.regist(board);

model.addAttribute("result", "success");

return "/board/success";

}

}

실제 경로는 '/WEB-INF/views/board/success.jsp'가 됩니다.

3.2 컨트롤러의 동작확인과 루트 경로지정

3.3 뷰(view)의 구현 - 등록

- 필요한 jsp 페이지 작성

- jsp 페이지 내에 필요한 데이터 전달 확인

- jsp 페이지 내에서의 출력

3.3.1 컨트롤러에서 데이터 전달

/board/register (GET) => BoardController => board/register.jsp =>/board/register(POST) ->BoardController => WEB-INF/views/board/success.jsp

3.3.2 결과 페이지의 문제점-새로 고침

도배 발생

3.3.3 리다이렉트(redirect)와 결과 데이터

사용자가 새로고침을 이용하는 것을 방지하려면 자동으로 다른 페이지로 이동하는 작업이 필요합니다.

@RequestMapping(value="/register", method=RequestMethod.POST)

public String registPOST(BoardVO board, Model model) throws Exception{

logger.info("regist post");

logger.info(board.toString());

service.regist(board);

model.addAttribute("result", "success");

// return "/board/success";

return "redirect:/board/listAll";

}

@RequestMapping(value="/listAll", method=RequestMethod.GET)

public void listAll(Model model) throws Exception{

logger.info("show all list....");

}

registPOST()의 리턴값이 'redirect'로 시작되도록 수정하였습니다.

3.3.4 RedirectAttribute를 이용한 숨김 데이터의 전송

Spring의 RedirectAttribute 객체는 리다이렉트 시점에 한 번만 사용되는 데이터를 전송할 수 있는 addFlashAttribute()라는 기능을 지원합니다.

@RequestMapping(value="/register", method=RequestMethod.POST)

public String registPOST(BoardVO board, RedirectAttributes rttr) throws Exception{

logger.info("regist post");

logger.info(board.toString());

service.regist(board);

rttr.addFlashAttribute("msg", "success");

return "redirect:/board/listAll";

}

04 전체 목록 구현

4.1 컨트롤러의 완성 및 JSP의 완성

BoardController에서는 BoardService와의 연결 작업을 진행합니다.

Model을 이용해서 모든 게시물을 JSP로 전송하는 작업을 model.addAttribute()로 처리하고 있습니다.

@RequestMapping(value="/listAll", method=RequestMethod.GET)

public void listAll(Model model) throws Exception{

logger.info("show all list....");

model.addAttribute("list", service.listAll());

}

4.1.1 각목록에서 링크 처리하기

<a href='/board/read?bno=${boardVO.bno}'>${boardVO.title}</a>

4.2 목록에 추가로 구현해야 하는 사항들

- 페이징 처리

- 검색 기능

05 조회 구현

- 조회 기능을 위한 BoardDAO의 처리

- BoardService, BoardController의 처리

- 조회 페이지의 작성

- 수정, 삭제 링크 처리

5.1 BoardController의 기능 추가와 뷰 처리

조회된 결과 계시물을 JSP로 전달해야 하기 때문에 Model 객체를 사용합니다.

스프링의 Model은 addAttribute 작업을 할 때 아무런 이름 없이 데이터를 넣으면 자동으로 클래스의 이름을 소문자로 시작해서 사용하게 됩니다.(BoardVO -> boardVO)

5.1.1 조회용 페이지 작성

5.2 수정, 삭제로의 링크처리

변경된 화면의 버튼 처리는 jQuery를 사용해서 처리합니다.

<form role="form" method="post">

<input type='hidden' name='bno' value="${boardVO.bno}">

</form>

[role은 HTML5 속성인듯...]

06 삭제/수정 처리

6.1 삭제 처리

@RequestMapping(value="/remove", method=RequestMethod.POST)

public String remove(@RequestParam("bno") int bno, RedirectAttributes rttr) throws Exception{

service.remove(bno);

rttr.addFlashAttribute("msg","SUCCESS");

return "redirect:/board/listAll";

}

6.2 수정 처리

@RequestMapping(value="/modify", method=RequestMethod.GET)

public void modifyGET(int bno, Model model) throws Exception{

model.addAttribute(service.read(bno));

}

@RequestMapping(value="/modify",method=RequestMethod.POST)

public String modifyPOST(BoardVO board, RedirectAttributes rttr) throws Exception{

service.modify(board);

rttr.addFlashAttribute("msg", "SUCCESS");

return "redirect:/board/listAll";

}

07 예외 처리

7.1 예외 처리에 대한 팁

Spring MVC를 사용할 때 Controller 쪽에서 Exception을 처리하기 위해서는 다음과 같은 방식들을 이용합니다.

- @Exception Handler 애노테이션을 이요한 처리

- @Controller Advice를 이용한 처리

- @ResponseStatus를 이용한 Http 상태 코드 처리

가장 범용적으로 사용할 수있는 방법은 @ContorllerAdvice를 이용한 처리 방식입니다. 이 방식은 공통의 Exception처리 전용객체를 사용하는 방식입니다.

스프링 MVC에서 제공하는 @ControllerAdvice는 호출되는 메소드에서 발생하는 Exception을 모두 처리하는 역활을 합니다.

- 클래스에 @ControllerAdvice라는 애노테이션 처리

- 각 메소드에 @ExceptionHandler를 이용해서 적절한 타입의 Exception을 처리

package org.zerock.controller;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;


@ControllerAdvice

public class CommonExceptionAdvice {

private static final Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);

@ExceptionHandler(Exception.class)

public String common(Exception e){

logger.info(e.toString());

return "error_common";

}

}


7.1.1 Exception을 화면으로 전달하기

<%@ page language="java" contentType="text/html; charset=EUC-KR"  pageEncoding="EUC-KR"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">

<title>Insert title here</title>

</head>

<body>


<h4>${exception.getMessage()}</h4>


<ul>

<c:forEach items="${exception.getStackTrace()}" var="stack">

<li>${stack.toString()}</li>


</c:forEach>

</ul>


</body>

</html>

08 페이징 처리 - 영속(persistence) 계층, 비지니스 계층

1) 사용자에게 필요한 만큼의 데이터 전송

2) 서버에서 최대한 빠르게 결과를 만들어내야

8.1 페이징 처리 방식

<a> 태그

장점 : 검색엔진에 노출이 쉬워진다.

단점 : 많은 양의 반복적인 링크 정보가 생성

<form> 태그

장점 : 간결, 빠르게 개발, 

8.1.1 페이징 처리 원칙

- 페이징 처리는 반드시 GET방식만을 이용해서 처리합니다. (페이지는 다른 사람에게 URL로 전달하는 경우가 많기 때문에,)

- 페이징 처리가 되면 조회하면에서 반드시 목록가기가 필요합니다.

- 페이징 처리에는 반드시 필요한 페이지 번호 만을 제공합니다.

8.2 페이징 처리 개발에 필요한 지식

- 페이징 처리를 위한 SQL

- 데이터 갯수 파악을 위한 SQL

- 자바 스크립트 혹은 <a> 태그를 통한 이벤트 처리

개발 순서

- 단순히 페이지 데이터를 화면에 출력하는 작업

- 화면 하단에 페이지 번호가 출력되고, 이를 클릭할 경우 제대로 이동하는 작업

- 조회 페이지에서 목록가기를 선택하면 보던 페이지 정보를 유지한채로 이동하는 작업

8.2.1 MySQL의 limit를 이용한 페이지 출력 SQL

8.2.1.1 충분한 양의 데이터 넣기

넣기

insert into tbl_board (title, content, writer) (select title, content, writer from tbl_board);

확인 

select count(*) as total from tbl_board;

8.3 MyBatis의 BoadDAO 처리

@Test

public void testListPage() throws Exception{

int page =3;

List<BoardVO> list = dao.listPage(page);

for(BoardVO board: list){

logger.info(board.getBno()+ ":"+board.getTitle());

}

}

8.3.1 BoardDAO, XML Mapper, BoardDAOImpl의 처리

8.3.2 페이징 처리의 SQL 테스트

페이징 처리는 단순히 SQL만으로 결과를 확인할 수 있기 때문에..

8.4 DAO 처리를 도와줄 Criteria 클래스 만들기

파라미터가 여러 개로 늘어나면 관리하기 어려워지기 때문에 아예 클래스로 만들어서 객체로 처리하는 것이 더 바람직합니다.

8.4.1 BoardDAO 인터페이스의 수정

listCriteria() 추가

8.4.2 XML Mapper 수정

DAO 인터페이스에 새로운 메소드가 추가됐으므로, XML에도 SQL문을 추가합니다.

8.4.3 BoardDAOImpl의 수정된

XML Mapper를 이용하는 BoardDAOImple은 아래와 같이 매소드로 구현합니다.

@Override

public List<BoardVO> listCriteria(Criteria cri) throws Exception {

return session.selectList(namespace+".listCriteria", cri);

}

8.4.4 BoardDAOTest에서의 테스트 작업

@Test

public void testListCriteria() throws Exception{

Criteria  cri = new Criteria();

cri.setPage(2);

cri.setPerPageNum(20);

List<BoardVO> list = dao.listCriteria(cri);

for(BoardVO board:list){

logger.info(board.getBno()+":"+board.getTitle());

}

}

8.5 BoardService 수정하기

BoadDAO의 테스트 작업이 완료되면, 이제 서비스 계층을 수정합니다.

09 페이징 처리 - 컨트롤러와 프리젠테이션 계층

9.1 1차 화면 테스트

9.2 화면 하단의 페이징 처리

9.2.1 endPage 구하기

int endPage = (int)(Math.ceil(cri.getPage()/(double)displayPageNum)*disPlaypageNum)

9.2.2 startPage 구하기

startPage = (endpage - disPlaypageNum) +1;

9.2.3 totalCount와 endPage구하기.

int tempEndPage = (int)(Math.ceil(cri.getPage()/(double)displayPageNum)*disPlaypageNum);

if(endPage>tempEndPage){

endPage = tempEndPage;

}

9.2.4 prev와 next 계산

prev = startPage==1?false:true;

next = endpage*cri.getPerPageNum() >= totalCount? false:true;

9.3 페이징 처리용 클래스 설계하기

위의 계산을 매번 jsp등에서 처리 할 수도 있겠지만, 좀 더 편리하게 사용하기 위해서는 별도의 클래스를 설계해서 처리하는 것이 좋습니다.

클래스의 작성을 위해서 필요한 데이터 점검

- 외부에서 입력되는 데이터

- DB에서 계산되는 데이터

- 계산을 통해서 만들어지는 데이터 : startPage, endPage, prev, next

9.4 BoardController와 뷰 처리

9.4.1 listPage.jsp의 처리

9.5 페이징을 위한 SQL 문의 처리

9.5.1 BoardDAO 수정

9.5.2 XML Mapper 수정

<select id="countPaging" resultType="int">

<![CDATA[

select count(bno)

from tbl_board

where bno>0

]]>

</select>

 

 

9.5.3 BoardDAOImpl의 수정

9.5.4 BoardService/BoardServiceImpl의 수정

9.5.5 BoardController의 수정

@RequestMapping(value="/listPage",method=RequestMethod.GET)

public void listPage(Criteria cri, Model model) throws Exception{

logger.info("listPage");

logger.info(cri.toString());

model.addAttribute("list", service.listCriteria(cri));

PageMaker pageMaker = new PageMaker();

pageMaker.setCri(cri);

// pageMaker.setTotalCount(131);

int totalCount = service.listCountCriteria(cri);

logger.info("listPage - total count : "+totalCount);

pageMaker.setTotalCount(service.listCountCriteria(cri));

model.addAttribute("pageMaker",pageMaker);

}

9.6 페이징 처리 걔선을 위한 Tip

perPageNum을 전달 못하는 문제가 있음

9.6.1 스프링 MVC의 UriComponentsBuilder를 이용하는 방식

9.6.2 JavaScript를 이용하는 링크의 처리

9.7 목록 페이지와 정보 유지하기

화면의 페이징 처리가 되면 다음 단계는 특정한 페이지를 보다가 조회 페이지로 이동하는 것에 대한 처리가 필요합니다.

9.7.1 BoardController의 수정

조회 시점에 전달되는 파라미터가 늘어나면서 BoardController의 메소드는 수정될 필요가 있습니다.

9.7.2 readPage.jsp

9.7.3 수정 페이지와 삭제 페이지의 처리

9.7.3.1 삭제처리

9.7.3.2 수정 처리

10 검색처리와 동적 SQL

검색처리의 가장 어려운 부분은 JSP쪽이 아닌 MyBatis를 이용하는 SQL문의 처리영역입니다.

이를 위해서

MyBatis의 동적 SQL(Dynamic SQL)을 이용하는 방법

@SelectiveProvider라는 것을 이용하는 방법

10.1 검색에 필요한 데이터와 SearchCriteria

- 현재 페이지의 번호

- 페이지당 보여지는 데이터의 수

- 검색의 종류

- 검색의 키워드

10.2  SearchBoardController의 작성

10.2.1 JSP 페이지의 준비

10.3 검색에 필요한 JSP 수정

10.3.1 searchType과 keyword 링크 처리

10.3.1.1 브라우저를 통한 검색과 페이징 확인

10.3.2 검색버튼의 동작처리

10.4 MyBatis 동적 SQL

화면에서의 검색 조건에 따라 검색해야 하는 SQL문이 달라지기 때문에 이를 처리하기 위해서는 MyBatis에서 사용하는 Mapper에서 SQL을 상황에 맞게 처리할 수 있는 기능이 필요합니다.

MyBatis가 가지는 표현식 : if, choose(when, otherwise, trim, foreach)

MyBatis에서 사용하는 SQL문을 애노테이션을 이용해서 처리하는 경우보다 XML로 관리하는 형태가 압도적으로 많은 가장큰 이유 중에 하나는 동적 SQL을 애노테이션으로 처리하기가 더 복잡하기 때문입니다.

동적 SQL의 적용이 필요한 메소드의 설정

XML Mapper를 이용한 SQL문 처리

동적 SQL 문의 생성 확인 및 테스트

10.4.1 BoardDAO의 수정

가장 먼저 할 일은 동적 SQL문을 적용하기 위한 메소드를 설정해 주는 것입니다.

10.4.2 XML Mapper 수정

10.4.3 BoardDAOImpl의 수정

10.4.4 BoadDAO의 테스트

10.4.5 동적 SQL문의 추

10.5 BoardService/SearchBoardController의 수정

10.6 조회, 수정, 삭제 페이지의 처리

10.6.1 게시물의 조회 처리

[SearchBoardController]

10.6.2 게시물의 삭제 처리

[SearchBoardController]

10.6.3 게시물의 수정 처리

[SearchBoardController]

10.7 등록 페이지 처리

10.8 최종적인 결과 확인

10.9 정리

Part 3 Ajax 댓글 처리


Chapter 01 RestController와 Ajax

REST는 Representational State Transfer의 약어로 하나의 URI는 하나의 고유한 리소스를 대표하도록 설계된다는 개념입니다.

REST Architecture 6가지 원칙

– 균일한 인터페이스 Uniform interface

– 상태없음 Stateless

– 캐시 Cache

– 클라이언트/서버 Client/Server

– 계층 시스템 Layered system

– (조건부) Code on demand

[ http://bcho.tistory.com/953

 http://bcho.tistory.com/954

 https://spoqa.github.io/2012/02/27/rest-introduction.html  의 표]

REST API는 외부에서 위와 같은 방식으로 특정 URI를 통해서 사용자가 원하는 정보를 제공하는 방식입니다.

REST 방식으로 제공되는 외부 연결 URI를 REST API라고 하고,  REST 방식의 서비스 제공이 가능한 것을 Restful 하다고 표현합니다.

스프링은 3버전 부터 @ResponseBody 애노테이션을 지원하면서 본격적으로 REST방식의 처리를 지원하였고

스프링4에 들어와서 @RestController가 본격적으로 소개되었습니다.


1.1 @RestController의 소개

특정한 JSP와 같은 뷰를 만들어 내는 것이 목적이 아닌 REST 방식의 데이터 처리를 위해서 사용하는 애노테이션입니다.

1.2 예제 프로젝트의 생성

1.3 테스트용 컨트롤러 생성하기

1.3.1 @RestController의 용도

@RestController는 JSP와 같은 뷰를 만들어 내지 않는 대신에 데이터 자체를 반환하는데, 이 때 주로 사용되는 것은 간단한 문자열과 JSON, XML등으로 나눠 볼 수 있습니다.

1.3.2 단순 문자열의 경우

@RestController에서 문자열 데이터는 기본적으로 브라우저에서는 'text/html'타입으로 처리 됩니다.

클래스 선언부에 @RestController 애노테이션을 이용하는 것은 해당 컨트롤러의 모든 뷰 처리가 JSP가 아니라는 것을 의미합니다.

@RestController 애노테이션이 사용된 클래스의 모든 메소드는 @ResponseBody 가 없어도 동일하게 동작합니다. (생략되었고 생각해도 무방합니다.)

1.3.3 루트 컨텍스트로 실행하기

프로젝트 추가 이후 Module 메뉴를 이용해서 실행시 Path를 루트 경로('/')를 이용하도록 수정합니다.

1.3.4 객체를 JSON으로 반환하는 경우

@RestController의 경우 별도의 처리가 없어도 객체는 자동으로 JSON으로 처리될 수 있습니다.

1.3.4.1 jackson-databind 라이브러리의 추가


<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.7.3</version>

</dependency>


응답헤더의 내용에 응답타입 역시 application/json으로 되어있는 것을 확인할 수 있습니다.

1.3.5 컬렉션 타입의 객체를 반환하는 경우

결과 데이터로 List 나 Map 탑입은 흔히 사용되므로, 이에 댛나 확인 작업도 필요합니다.

Map의 경우 JavaScript의 JSON 형식으로 보여집니다.

1.3.6 ResponseEntity 타입

@RestController는 별도의 뷰(view)를 제공하지 않는 형태로 서비를 실행하기 때문에 때로는 결과 데이터가 예외적인 상황에서 문제가 발생할 수 있습니다.

웹의 경우 HTTP 상태코드가 이러한 정보를 나타내는 데 사용합니다.

- 100번대 : 현재 데이터의 처리중인 상태

- 200번대 : 정상적인 응답

- 300번대 : 다른 URL 처리

- 400번대 : 서버에서 인식할 수 없음.

- 500번대 : 서버 내부의 문제

스프링에서 제공하는 ResponseEntity 타입은

개발자가 결과 데이터 + HTTP의 상태 코드를 제어할 수 있는 클래스 입니다.

좀더 세밀한 제어가 필요한 경우에 사용해 볼 수 있습니다.

@RequestMapping("/sendErrorAuth")

public ResponseEntity<Void> sendListAuth(){

return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

}

@RequestMapping("/sendListNot")

public ResponseEntity<List<SampleVO>> sendListNot(){

System.out.println("sendListNot()");

List<SampleVO> list = new ArrayList<>();

for(int i=0;i<10; i++){

System.out.println("sendListNot() "+i);

SampleVO vo = new SampleVO();

vo.setFirstName("길동");

vo.setLastName("홍");

vo.setMno(i);

list.add(vo);

}

return new ResponseEntity<>(list, HttpStatus.NOT_FOUND);

}



Chaper 02 댓글 처리와 REST

REST 방식을 사용하는 원칙

- URI가 원하는 리소스를 의미합니다.

- URI에는 식별할 수 있는 데이터를 같이 전달하는 것이 일반적입니다.

가장 많이 사용되는 형태는 '작업대상/PK'와 같은 방식의 접근이 무난합니다.

2.1 Adavanced REST Client를 이요한 테스트

2.2 REST와 Ajax

웹을 통해서 작업할 때 REST 방식이 가장 많이 쓰이는 형태는 Ajax와 같이 결합된 형태입니다.

Ajax를 가장 쉽게 설명하는 방법은 화면의 전환이나 까빡임 없이 서버에서 데이터를 받는 방법이라고 설명하는 것이 가장 쉬울 것입니다.

비동기화된의 의미는 결과의 데이터를 기다리는 방식이 아닌, 결과를 통보받는 형식이라고 볼 수 있습니다.

REST 방식이 데이터를 호출하고, 사용하는 방식을 의미한다면 Ajax는 실제로 그 이용하는 수단에 가깝다고 할 수있습니다.

2.3 댓글 처리를 위한 준비

가장 중요한 작업은 컨트롤러의 처리와 호출방식에 대한 결정과 구현이라고 할 수 있습니다.

2.3.1 전달 방식과 처리 방식의 결정

| URI | 전송방식 | 설명 |

|replies/all/123 | GET | 게시물 123번의 모든 댓글 리스트 |

|replies/ + 데이터 | POST | |

|replies/456 + 데이터 | PUT/PATCH | |

|replies/456 | DELETE | |

2.3.2 데이터 전송 방식의 결정

최근에 모바일이 발전하면서 REST 방식으로 데이터를 제공하는 일이 점차 늘고 있습니다.

REST에서 많이 사용되는 JSON을 기준으로 해서 작성했습니다.

2.3.3 댓글을 위한 테이블 설정

CREATE TABLE `book_ex`.`tbl_reply` (

 `rno` INT NOT NULL AUTO_INCREMENT,

 `bno` INT NOT NULL DEFAULT 0,

 `replytxt` VARCHAR(1000) NOT NULL,

 `replyer` VARCHAR(50) NOT NULL,

 `regdate` TIMESTAMP NOT NULL DEFAULT now(),

 `updatedate` TIMESTAMP NOT NULL DEFAULT now(),

 PRIMARY KEY (`rno`));


2.3.4 댓글을 위한 도메인 객체 설정

package org.zerock.domain;


import java.util.Date;


public class ReplyVO {

private Integer rno;

private Integer bno;

private String replytext;

private String replyer;

private Date regdate;

private Date updatedate;


}


2.3.5 ReplyVO

2.3.5.1 ReplyDAO 인터페이스 작성

2.3.5.2 XML Mapper의 작성

2.3.5.3 ReplyDAOImpl의 작성

[ 이전에 "BoadDAOImpl 클래스를 작성한 후에는 반드시 스프링의 빈으로 제대로 등록 되었는지 root-context.xml 을 선택하고 Beans Graph 탭을 이용해서 확인하도록합니다."라고 했었음 그래서 테스트 까지 해봄.]

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class ReplyDAOTest {

Logger logger = LoggerFactory.getLogger(ReplyDAOTest.class);

@Inject

private ReplyDAO dao;


@Test

public void testCreate() throws Exception{

ReplyVO vo = new ReplyVO();

vo.setBno(3);

vo.setReplyer("replyer01");

vo.setReplytext("replytext replytext replytext");

dao.create(vo);

}


}


2.3.5.4 ReplyService/ReplyServiceImpl 작성

@Service

public class ReplyServiceImpl implements ReplyService {

Logger logger = LoggerFactory.getLogger(ReplyServiceImpl.class);

@Inject

ReplyDAO dao;


@Override

public void addReply(ReplyVO vo) throws Exception {

dao.create(vo);

}


@Override

public List<ReplyVO> listReply(Integer bno) throws Exception {

// TODO Auto-generated method stub

return dao.list(bno);

}


@Override

public void modifyReply(ReplyVO vo) throws Exception {

dao.update(vo);

}


@Override

public void removeReply(Integer rno) throws Exception {

dao.delete(rno);

}


}

[테스트]

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/spring/**/*.xml"})

public class ReplyServiceImplTest {

Logger logger = LoggerFactory.getLogger(ReplyServiceImplTest.class);

@Inject

ReplyService service;


@Test

public void testAddReply() throws Exception{

ReplyVO vo = new ReplyVO();

vo.setBno(3);

vo.setReplyer("replyer003");

vo.setReplytext("Replytext.Replytext.ReplytextReplytext");

service.addReply(vo);

}

@Test

public void testListReply() throws Exception{

service.listReply(3);

}

@Test

public void testModifyReply() throws Exception{

ReplyVO vo = new ReplyVO();

vo.setRno(3);

vo.setReplyer("replyer003");

vo.setReplytext(" Updated Replytext. Updated Replytext. Updated Replytext.");

service.modifyReply(vo);

}

@Test

public void testRemoveReply() throws Exception{

service.removeReply(3);

}

03 REST 방식의 ReplyController 작성

작성하는 ReplyController의 경우 @RestController를 이용해서 작성하과, 이에 대한 테스트 작업은 Advanced REST Client를 이용해서 작업하도록 합니다.

스프링 MVC의 경우 REST 방식의 처리에서 사용되는 특별한 애노테이션은 다음과 같습니다.

@PathVariable - URI 경로에서 원하는 데이터를 추출하는 용도로 사용

@RequestBody - 전송된 JSON데이터를 객체로 변환해주는 애노테이션으로 @ModelAttribute와 유사한 역활을 하지만 JSON에서 사용된다는 점이 차이

3.1 등록처리

등록작업은 'replies' URI로 처리되고 POST 방식으로 전송됩니다.

데이터 전송받은 JSON 포멧을 이용할 것이므로 이를 처리하는 @RequestBody 애노테이션이 필요합니다.

@RequestMapping(value="", method=RequestMethod.POST)

public ResponseEntity<String> register(@RequestBody ReplyVO vo){

System.out.println("ReplyController.register()");

ResponseEntity<String> entity = null;

try{

service.addReply(vo);

entity = new ResponseEntity<String>("SUCCESS", HttpStatus.OK);

}catch(Exception e){

entity = new ResponseEntity<String>("SUCCESS", HttpStatus.BAD_REQUEST);

}


return entity;

}


3.2 특정 게시물의 전체 댓글 목록의 처리

특정 게시물의 전체 댓글 목록은 GET 방식으로 처리돼야 하고, 반드시 특정 게시물의 번호(bno)를 필요로 하기 때문에 다음과 같은 형태로 작성합니다.

3.3 수정 처리

REST 방식에서 update 작업은 PUT, PATCH 방식을 이용해서 처리합니다.

일반적으로 전체 데이터를 수정하는 경우에는 PUT을 이용하고, 

일부 데이터를 수정하는 경우에는 PATCH를 이용합니다.

3.3.1 HiddenMethod의 활용

브라우저에 따라서 PUT, PATCH, DELETE를 지원하지 않는 경우가 발생할 수 있습니다.

3.4 삭제 처리

3.5 페이징 처리

3.5.1 ReplyDAO 처리

3.5.2 XML Mapper 처리

04. 화면에서의 Ajax 호출

@RestController를 이용하는 경우에는 데이터만 만들어져서 전송되기 때문에, 사실상 개발의 대부분은 이를 처리하는 화면으로 집중됩니다.

4.1 개발 순서의 결정

Ajax를 이용하는 뎃글처리는 개발 대부분이 화면에서의 처리가 많으므로, 간단한 페이지를 만들어서 결과 데이터의 처리를 먼저하도록 합니다.

4.1.1 테스트를 위한 컨트롤러와 JSP

part4 AOP와 트랜잭션 처리

스프링은 개발자가 비지니스 로직에만 전념할 수 있는 다양한 기법이 존재하고, 이를 문법적으로 지원합니다.

01 Spring의 AOP와 트랜잭션 관리

Aspect Oriented Programming

이 장에서는 스프링 프레임워크가 가진 AOP를 사용해서 개발할 때 공통적이고, 반복적인, 그러나 비지니스 로직의 핵심이 아닌 부분을 어떻게 처리하는지 알아보려고 합니다.

AOP와 트랜잭션 처리를 통해서 이장에서는 다음과 같은 기능들을 완성할 수 있습니다.

- 개발자가 원하는 코드를 실행하는데 얼마의 시간이 소모되는지 쉽게 알 수있다.

- 개발자는 메소드에 전달되는 파라미터나 리턴값 에대해서 로그에 기록할 수 있다.

- 데이터베이스 상에서 트랜잭션 처리를 설정하여, 성공하는 경우에만 모든 데이터가 완전하게 처리되는 방벙을 적용할 수 있다.

1.1 AOP라른 용어와 의미

AOP의 Aspect라는 용어가 실제 프로그램 개발에서 의미하는 것은 '비지니스 로직은 아니지만 반드시 해야하는 작업'정도로 해석할 수 있습니다. 

1.1.1 AOP와 관련된 용어

Proxy 패턴이라는 방식을 통해서 구현합니다.

외부에서 특정한 객체(target)를 호출하면, 실제 객체를 감싸고 있는 바깥쪽 객체(Proxy)를 통해서 호출이 전달됩니다.

Proxy객체는 AOP기능이 적용된 상태에서 호출을 받아 사용되고, 실제 객체와 동일한 타입으로 자동으로 생성할 수 있기 때문에 외부에서를 실제 객체와 동일한 타입으로 호출 할 수있습니다.

AOP를 이해하려면 어쩔 수 없이 다음과 같은 용어들을 이해할 필요가 있습니다.

- Aspect : 공통 관심사에대한 추상적인 명칭, 예를 들어 보안이나 트랜잭션과 같은 기능 자체에 대한 용어

- Advice : 실제로 기능을 구현한 객체 (실제 적용시키고 싶은 코드 자체. 개발자가 만드는 것은 Aspect가 아닌 클래스를 제작하고, @Advice를 적용하는 것임.)

- Join Point : 공통관심사를 적용할 수 있는 대상, Spring AOP에서는 각 객체의 메소드가 이에 해당 (작성된 Adivce가 활약할 수 있는 위치를 의미)

- PointCuts : 여러 메소드 중 실제 Adivce가 적용될 대상 메소드(여러 Join Point 중에서 Advice를 적용할 대상을 선택하는 정보. 이를 통해서 특정 메소드는 Adivce가 적용된 형태로 동작함)

- target : 대상 메소드를 가지는 개겣

- Proxy : Advice가 적용되었을 때 만들어지는 객체

- Introduction : target에 없는 새로운 메소드나 인스턴스 변수를 추가하는 기능

- Weaving : Advice와 target이 결합되어서 프록시 객체를 만드는 과정

1.1.2 Adivce의 종류

- Before Advice : target 메소드 호출 전에 적용

- After returning : target 메소드 호출 이후에 적용

- After throwing : 

- After

- Around

02 샘플 프로젝트의 생성과 AOP의 적용 준비

2.1 추가해야하는 라이브러리와 설정

2.1.1 root-context.xml의 설정

aop와 tx 네임스페이스를 추가합니다.

2.2 샘플용 테이블 설계

2.3 샘플용 도메인 객체, DAO, XML Mapper, 서비스

2.3.1 도메인 객체 MessageVO

2.3.2 DAO 설정

2.3.2.1 MessageDAO 인터페이스의 설계

2.4 서비스 객체 설정

MessageSerivce가 기존의 서비스 객체와 다른 점은 두 개의 DAO를 같이 활용해서 하나의 비지니스 로직을 완성하는 형태로 사용하기 때문입니다.

03 AOP 연습하기

DAO를 설정하고 서비스 객체를 생성하는 것까지는 기존의 개발 방식과 완전히 동일하지만, 스프링의 AOP기능을 활용하기 위해서는 추가로 Advice객체를 생성하고 이를 어노테이션이나 XML을 이용해서 원하는 기능에 적용하는 설정이 필요합니다.

3.1 Advice 생성하기

3.1.1 SampleAdvice의 작성

@Aspect 애노테이션

@Component는 스프링의 빈으로 인식되기 위해서 설정해 줬습니다.

3.2 컨트롤러의 작성과 테스트하기

3.3 실행시에 전달되는 파라미터 파악하기

04 Spring의 트랜잭션 처리


4.1 트랜잭션에 대한 기본 설명

트랜잭션은 쉽게 말해서 하나의 업무에 여러개의 작은 업무들이 같이 묶여 있는 것을 의미합니다.

시스템에서 가장 흔하게 처리되는 트랜잭션의 상황

- 회원이 특정 게시판에 게시글을 추가하면 회원의 포인트가 올라가야하는 상황

- 원글에 댓글이 추가 되면 댓글 테이블에 게시물이 등록되고, 원글에는 댓글의 숫자가 업데이트 돼야하는 상황

- 문의 게시판에 글을 등록하는 데이터베이스에도 글이 등록되지만, 담당자에게도 메일이 발송돼야하는 상황

4.1.1 트랜잭션의 기본원칙

원자성(Atomicity)

일관성(Consistency)

격리(Isolation)

영속성(Durability)

4.1.2 트랜잭션을 처리하는 상황 이해하기

4.2 @Transactional 애노테이션

@Transactional 애노테이션은 몇가지 중요한 속성을 가지고 있음

- 전파(Propagation) 속성

- 격리(Isolation) 레벨

- Read-Only 속성

- Rollback-for-예외

- No-rollback-for-예외

4.3 트랜잭션 매니저의 설정

4.3.1 root-context.xml의 처리

4.5 @Transactional의 적용 순서

스프링의 간단한 트랜잭션 매니저의 설정과 @Transactional 애노테이션을 이용한 설정만으로 애플리케이션 내의 트랜잭션에대한 설정을 처리 할 수 있습니다.

@Transactional 애노테이션의 경우 메소드에 설정하는 것도 가능하지만, 클래스나 인터페이스에 선언하는 것 역시 가능합니다.

애노테이션의 우선순위

- 메소드의 @Transactional

- 클래스의 @Transactional

- 인터페이스의 @Transactional

chapter 05 게시물의 뎃글에 따른 트랜잭션 처리


part5 게시물의 첨부파일 기능


chapter 01 스프링 MVC의 파일 업로드

스프링의 경우 파일 업로드에 필요한 기능을 지원하지만, 약간의 설정과 라이브러리가 필요합니다.

1.1 파일 업로드의 활용

1.2 예제 프로젝트의 생성

1.2.1 실습을 위한 라이브러이 추가

1.2.2 파일 업로드 관련 <bean> 설정

웹에서 파일업로드는 일반적으로 'multipart/form-data'라는 방식으로 데이터를 전송합니다.

스프링 MVC에서 파일 업로드를 처리하기 위해서는 파일 업로드로 들어오는 데이터를 처리하는 객체가 필요합니다.

스프링에서 multipartResolver라고하는 이 객체의 설정은 웹과 관련 있기 때문에 root-context.xml 이 아닌 servlet-context.xml을 이용해서 설정합니다.

1.3 일반적인 파일 업로드 이해하기

1.3.1 POST 방식의 파일 업로드 처리

MultipartFile은 POST 방식으로 들어온 파일 데이터를 의미합니다.

MultipartFile 객체를 이용하면 전송된 파일이름이나 파일의 크기 파일의 MINE타입등과 같은 정보를 추출할 수 있습니다.

1.3.2 업로드 파일의 저장

1.3.2.1 서버의 파일 저장 경로 설정

파일을 저장할 경로는 상수처럼 사용되기 때문에 servlet-context.xml 파일을 이용해서 특정 경로를 문자열로 설정합니다.

<beans:bean id="uplaodPath" class="java.lang.String">

<beans:constructor-arg value="c:\\zzz\\upload">

</beans:constructor-arg>

</beans:bean>

작성된 경로의 문자열은 UploadController에 주입해서 사용합니다.

1.3.2.2 UploadController의 파일 저장

uploadFile() 내부에서 실제 파일 처리는 스프링에서 제공하는 FileCopyUtils를 이용합니다.

FileCopyUtils는 'org.springframework.util' 패키지에 설정된 클래스 입니다.

1.3.3 <iFrame>을 이용한 파일 업로드의 결과 처리

1.3.3.1 UploadController의 수정

1.3.3.2 결과 페이지의 작성

1.3.3.3 <iFrame>으로 <form>데이터 처리

1.4 Ajax 방식의 파일 업로드

단일 파일 업로드와 

chapter 02 전송된 파일의 저장


chapter 04 전송된 파일을 화면에 표시하기

4.1 파일데이터 전송하기

4.1.1 UploadController의 파일 전송 기능 구현

4.1.2 일반 파일인 경우의 다운로드 테스트

4.2 JSP에서 파일 출력하기

chapter 05 첨부파일의 삭제


chapter 06 게시물 등록의 파일 업로드


Part 6 인터셉터를 활용하는 로그인 처리

세션 트래킹이라 불리는 로그인 처리는 웹페이지에서 필수적으로 처리 되어야 하는 기능입니다.

- HttpSession을 이용한 로그인

- Cookie와 HttpSession을 이용한 자동로그인

Chapter 01 Spring MVC의 인터셉터

1.1 Filter와 인터셉터의 공통점과 차이점

1.2 Spring AOP기능과 HandlerIntercepter의 차이

특정 객체 동작의 사전 혹은 사후 처리는 HandlerInterceptor 인터페이스 혹은 HandlerInterceptorAdaptor 클래스를 활용하는 경우가 더 많습니다.

AOP의 Adivce와 HandlerIntercepter의 가장 큰 차이는 파라미터의 차이라고 할 수 있습니다.

Advice의 경우 JoinPoint나 ProceedingJoinPoint등을 활용해서 호출 대상이 되는 메소드의 파라미터 등을 처리하는 방식입니다.

HandlerInterceptor는 Filter와 유사하게 HttpServletRequest, HttpServletResponse를 파라미터로 받는 구조입니다.

HandlerInterceptor에는 다음과 같은 메소드들이 존재합니다.

- preHandler(request, response, handler)

- postHandler(request, response, handler, modelAndView) : 지정된 컨트롤러 동작이후, 화면처리하기 전

- afterCompletion(request, response, handler, exception) : 화면처리가 완료된 상태에서

1.2.1 HandlerInterceptorAdapter 클래스

인터페이스를 구성한 추상클래스


appendix 02 스프링 부트를 이용한 프로젝트 생성

2.1 STS 상에서 스프링부트 프로젝트 생성하기

프로젝트 생성 시 'Spring Starter Project'를 생성합니다.

프로젝트 생성시 War 파일을 선택하고 Package는 'org.zerock'으로 지정합니다.

처음 예제는 Web 만을 선택하고 생성합니다.

MySQL의 경우 반드시 DataSource 에 대한 설정이 필요하기 때문에  최초로 작성할 때는 하지 않는 것이 좋습니다.

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-tomcat</artifactId>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

2.1.1 프로젝트의 실행

프로젝트명+Application 의 main()을 선택하고 Run As 메뉴의 Spring Boot App을 이용해서 프로젝트를 실행합니다.

/* 버전이 안 맞는 지 어노테이션을 바꿔줌..*/

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;


@ComponentScan

@Configuration

@EnableAutoConfiguration

public class Ex04Application {


public static void main(String[] args) {

SpringApplication.run(Ex04Application.class, args);

}

}


2.2 Controller 작성

@RestController 를 이용해서 데이터를 처리하는 컨트롤러를 작성해 보겠습니다.

우선 org.zerock.controller 패키지를 작성합니다 /* com.example 로 프로젝트를 생성해 버려서 com.example.controller 로 작성함 */

package com.example.controller;


import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;


@RestController

public class SampleController {


@RequestMapping("/hello")

public String hello(){

return "hello";

}

}

2.3 스프링 부트에서의 데이터베이스 설정

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jdbc</artifactId>

</dependency>

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<scope>runtime</scope>

</dependency>

2.3.1 application.properties 파일 설정

프로젝트 구조

/src/main/resources/static

/src/main/resources/templates

/src/main/resources/aplication.properties : 애플리케이션내의 설정 파일

스프링 부트는 기본적으로 XML을 이용하지 않고, 프로젝트의 설정을 하도록 생성됩니다.

2.3.2 DataSource 의 설정과 테스트

스프링 부트의 경우 DataSource를 처리하기 위해서 크게 두 가지 방식을 사용합니다.


application.properties

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url = jdbc:mysql://localhost/book_ex

spring.datasource.username=zerock

spring.datasource.password=zerock

2.3.3 DataSource의 테스트

설정된 DataSource의 테스트는 프로젝트 생성 시 만들어진 /src/test/java의 테스트 파일을 이용하면 됩니다.

작성한 코드는 DataSource를 @Autowired로 주입 받도록 합니다.

/*

@Autowired, @Resource, @Inject의 차이

http://dev-eido.tistory.com/entry/Autowired-Resource-Inject%EC%9D%98-%EC%B0%A8%EC%9D%B4

*/

2.4 스프링 부트에서 MyBatis 설정

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-spring</artifactId>

<version>1.2.2</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis</artifactId>

<version>3.2.8</version>

</dependency>

[ 1.3.0, 3.4.0 으로 했다가 Exception 남.. 버전을 잘 맞춰야하는 듯. ]


2.4.1 SqlSessionFactoryBean 설정

스프링 부트는 실행시 DataSource 객체를 메소드의 실행시 주입해서 결과를 만들고, 그 결과를 스프링 내 빈으로 사용하게 됩니다.

@Bean

public SqlSessionFactoryBean sqlSessionFactory(DataSource datasource) throws Exception{

SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();

sessionFactoryBean.setDataSource(datasource);

return sessionFactoryBean;

}

작성된 코드는 다시 한 번 테스트를 통해서 정상적으로 스프링에 등록 되었는지 확인 합니다.

'Spring' 카테고리의 다른 글

part 5 게시물의 첨부파일 기능  (0) 2016.05.10
스프링을 이용한 RESTful 웹서비스 구축하기  (0) 2016.04.21