개발 공부/Spring

(New)코드로 배우는 스프링 웹 프로젝트 - Part1 : 스프링 개발 환경 구축

maxlafe 2020. 7. 10. 13:02

코드로 배우는 스프링 웹 프로젝트 - 개정판

코드로 배우는 스프링 웹 프로젝트 - Part1 : 스프링 개발 환경 구축

html, css, javascript는 기억안나면 인프런으로 보충할 예정

2019년 7월 10일 인쇄판

 

Part1. 스프링 개발 환경 구축

Chapter 01. 개발을 위한 준비

 

1.1 개발 환경 설정

- JDK 1.8 버전 설치

- Eclise / STS 설치 및 프로젝트 생성(저는 Eclipse 위주로 갑니다)

- Tomcat 설치 및 연동

- 오라클 데이터베이스 / SQL Daveloper 설치 및 설정

- 스프링 프로젝트 생성 및 라이브러리 추가

- MyBatis / mybatis-spring 설정

- 스프링 MVC 개발 설정

 

1.1 개발 환경설정

1.1.1 JAVA 설치

생략

 

1.1.2 Eclipse 혹은 STS 설치

 

Eclipse IDE for JAVA EE Developers 설치.

 

 

1.1.3 Eclipse 실행 환경 편집

 

eclipse.exe가 있는 폴더에서 ecpise.ini 파일 상단에 내용 추가

javaw의 경로 추가.

 

1.1.4 workspace와 UTF-8 설정

 

Window > Preferences > General > Workspace

Web > Html, CSS, JavaScript에 대해서도 UTF-8로 설정.

 

 

 

1.1.5 STS 설정.

1.1.6 Eclipse에 스프링 플러그인 설치

 

 

https://spring.io/tools#suite-three

 

Spring Tools 4 is the next generation of Spring tooling

Largely rebuilt from scratch, Spring Tools 4 provides world-class support for developing Spring-based enterprise applications, whether you prefer Eclipse, Visual Studio Code, or Theia IDE.

spring.io

Eclipse MarketPlace에서 STS를 검색해 설치를 진행했다.

 

 

perspective에 spring이 추가되었다면 성공

 

1.1.7 Tomcat 9 설정

 

http://tomcat.apache.org/ 

 

Apache Tomcat® - Welcome!

The Apache Tomcat® software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. The Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket specifications ar

tomcat.apache.org

다운로드 후 압축 풀기

Preference > Server에 Tomcat9.0 추가, 다운로드 받은 파일 경로 설정.

 

 

 

1.2 스프링 프로젝트 생성

 

이클립에서 스프링 프로젝트를 생성하는 방식

1) 처음부터 스프링 프로젝트를 지정하고 생성하는 방식

2) Maven이나 Gradle 프로젝트를 생성한 후 프레임워크를 추가하는 방식

3) 직접 프레임워크 라이브러리를 추가하는 방식

 

1.2.1 'ex00' ㅍ프로젝트 생성

 

Perspective를 Spring으로 지정한 후 File > New > Spring Legacy Project

 

 

 

 

 

 

* 나도 오류가 나서 pom.xml에서 space 혹은 엔터 정도로 가볍게 저장이 가능하도록 한 뒤에 재저장을 하고,

프로젝트 오른쪽 마우스 > Maven > Update Projects를 시도함

 

1.1.2 스프링 버전 변경

 

 

5

5.0.7로 수정

 

 

1.2.3 JAVA version 변경

 

스프링 5.x는 JDK 1.8을 사용하는 것이 가장 좋다.

plugin 태그 중 maven-compiler-plugin의 내용을 1.6에서 1.8로 수정.

 

 

 

이후에 Maven > Update Project

프로젝트의 컴파일이나 실행환경이 JDK 1.8을 기준으로 설정된 것을 확인할 수 있다.

 

1.3 Tomcat을 이용한 프로젝트 확인

 

Run As > Run on Server

 

 

1.4 Lombok 라이브러리 설치

 

Lombok으로 getter/setter, toString, 생성자 등을 자동으로 생성해줄 수있따.

 

https://projectlombok.org/

 

Project Lombok

 

projectlombok.org

 

바로 실행 후

eclipse가 설치된 경로를 찾아주자.

 

install하면 STS의 실행 경로에 lombok.jar가 추가된다.

 

 

1.5 Java Configuration을 하는 경우

 

스프링 3버전 이후엔 JAVA를 이용하는 설정이 점점 증가하고 있다. 

예제는 XML을 기반으로 설정된 것이지만, Java 예제는 jex00으로 생성하자.

 

1.5.1 XML 파일 삭제

 

web.xml과 servlet-context.xml, root-context.xml 파일 삭제.

 

web.xml을 삭제하면 pom.xml에서 에러가 뜨는데 이것은 과거의 웹 프로젝트들이 기본적으로 web.xml을 사용하는 것을 기본 설정했기 때문.

pom.xml의 하단부 plugins 내에 아래의 설정 추가

 

이후 스프링과 컴파일 관련 버전 역시 수정 후 Update

 

<plugin>
            	<groupId>org.apache.maven.plugins</groupId>
            	<artifactId>maven-war-plugin</artifactId>
            	<version>3.2.0</version>
            	<configuration>
            		<failOnMissingWebXml>false</failOnMissingWebXml>
            	</configuration>
            </plugin>

 

 

1.5.2 @Configuration

JAVA 설정을 사용하는 경우 XML 대신 설정 파일을 직접 작성할 필요가 있다.

다행스럽게도 스프링은 @Configuration 어노테이션으로 해당 클래스의 인스턴스를 이용해 설정 파일을 대신한다.

 

org.zerock.config 폴더를 생성하고 RootConfig 클래스를 작성해두자.

package org.zerock.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class RootConfig {

}

 

1.5.3 web.xml을 대신하는 클래스 작성

 

XML을 사용하지 않으면 이 역할을 대신하는 클래스를 작성해 처리한다.

 

WebConfig.java

 

package org.zerock.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer{

	@Override
	protected Class<?>[] getRootConfigClasses() {
		// TODO Auto-generated method stub
		return new Class[] {RootConfig.class};
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected String[] getServletMappings() {
		// TODO Auto-generated method stub
		return null;
	}

}

 

이 때 서버 path 겹침은 Tomcat 서버 클릭 > Modules > Web Modules 표에서 Path 수정

 

 

Chpater2. 스프링의 특징과 의존성 주입

 

2.1 스프링의 간략한 역사

 

프레임워크란 뼈대나 근간을 이루는 코드들의 묶음이다.

프레임워크를 이용한다는 의미는 프로그램의 기본 흐름이나 구조를 정하고 모든 팀원이 이 구조에 자신의 코드를 추가하는 방식으로 개발하게 된다는 뜻.

최대 장점은 개발에 필요한 구조를 이미 코드로 만들어 놓았으므로 실력이 부족한 개발자라 해도 반쯤 완성한 상태에서 필요 부분을 조립하는 형태로 개발이 가능하다는 것.

 

스프링은 비교적 그 시작이 조금 늦은 프로젝트였지만 가장 성공적인 경량 프레임워크다.

 

- 복잡함에 반기를 들어서 만든 프레임워크

- 프로젝트의 전체 구조를 설계할 때 유용한 프레임워크

- 다른 프레임워크들의 포용

- 개발 생산성과 개발도구의 지원

 

 

2.1.1 스프링의 주요 특징

 

- Pojo 기반의 구성

성격 자체가 가벼운 프레임워크이지만 그 내부엔 객체 간의 관계를 구성할 수 있는 특징을 가지고 있다. 별도의 API를 사용하지 않는 POJO(plain old java object) 구성만으로 가능하게 제작되어 있다. 쉽게 말해 일반적인 자바만으로 스프링을 사용할 수 있다는 것

 

- 의존성 주입DI과 스프링

의존성이란 하나의 객체가 다른 객체 없인 제대로 된 역할을 할 수 없다는 것을 의미. A객체가 B객체 없인 동작이 불가능하다면 A가 B에 의존적이다고 표현

주입이란 외부에서 밀어넣는 것.

즉, 의존성 주입은 어떤 객체가 필요한 객체를 외부에서 밀어 넣는다는 의미가 된다.

 

주입을 받는 입장에선 어떤 객체인지 신경 쓸 필요가 없어지며 어떤 객체에 의존하든 자신의 역할은 변하지 않는다.

ApplicationContext라는 것이 필요한 객체들을 생성하고 필요한 객체들을 주입하는 역할을 해 준다.

 

 

- AOP의 지원

스프링은 반복적인 코드를 줄이고, 핵심 비즈니스 로직에만 집중할 수 있는 방법을 제공한다.

대부분의 시스템이 공통으로 가지고 있는 보안, 로그, 트랜잭션과 같이 비즈니스 로직은 아니지만 처리가 반드시 필요한 부분을 "횡단관심사cross-concern"이라고 한다. 스프링은 횡단 관심사를 분리해 제작하는 것이 가능하다.

AOP는 AspectJ 문법을 통해서 작성할 수 있는데 이를 통해 개발자는 1) 핵심 비즈니스 로직에만 집중해 코드를 개발할 수 있게 되고, 2) 각 프로젝트마다 다른 관심사를 적용할 때 코드의 수정을 최소한으로 할 수 있으며 3) 원하는 관심사의 유지보수가 수월한 코드를 구성할 수 있다

 

- 트랜잭션의 지원

스프링은 트랜잭션의 관리를 XML이나 어노테이션으로 할 수 있어 개발자가 매번 상황에 맞는 코드를 작성할 필요가 없도록 설계했다.

 

2.2 의존성 주입 테스트

 

스프링에선 생성자를 이용한 주입과 setter 메서드를 이용한 주입으로 의존성 주입을 구현한다.

주로 XML이나 어노테이션을 이용해 처리한다.

 

	<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
	<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.0</version>
			<scope>provided</scope>
		</dependency>
		

dependency에 추가.

Log4j는 1.2.15로 설정되어 있으므로 1.2.17버전 추가 혹은 주석처리.

아래 Test 디펜던시의 Junit도 4.7에서 4.12로 버전 변경

 

 

2.2.1 예제 클래스

 

org.zerock.sample 패키지 생성

package org.zerock.sample;

import org.springframework.stereotype.Component;

import lombok.Data;

@Component
@Data
public class Chef {

	
	
}

 

Restaurant 클래스

 

package org.zerock.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import lombok.Data;
import lombok.Setter;

@Component
@Data
public class Restaurant {

	@Setter(onMethod_ = @Autowired)
	private Chef chef;
}

 

작성된 코드에선 Restaurant 객체는 Chef 타입의 객체를 필요로 하는 상황.

@Component는 스프링에 해당 클래스가 스프링에서 관리해야 하는 대상임을 표시하는 어노테이션이고, @Setter는 자도응로 setChef()를 컴파일하는데 생성한다.

 

@Setter에 사용된 onMethod 속성은 생성되는 setChef()에 @Autowired 어노테이션을 추가하도록 한다. Lombok에서 생성된 클래스에 대한 정보는 이클립스를 통해 확인할 수 있다!

 

이 때 에러가 계속 발생한다면 User 폴더(C 드라이브 밑에 있는 폴더)에서 .m2를 삭제한 후 이클립스를 재실행한다.

.m2는 이클립스를 종료해야 삭제할 수 있으며 이클립스 재실행 후에도 메이븐 업데이트를 해주자.

 

2.2.2 XML을 이용하는 의존성 주입 설정

 

스프링에서 관리되는 객체를 흔히 Bean이라고 한다.

 

src 폴더 내에 root-context.xml은 스프링 프레임웤에서 관리해야 하는 객체를 설정하는 설정 파일이다.

아래 Namespaces 탭에서, context 항목 체크.

 

Source 탭을 선택해 아래의 코드를 추가한다.

 

<?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
    http://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 -->
	<context:component-scan base-package="org.zerock.sample">
	</context:component-scan>
</beans>

Bean Graph 탭을 선택하면 Beans가 생성된 걸 확인할 수 있다.

 

Java 설정을 이용하는 의존성 주입

 

RootConfig 클래스 이용

 

@Configuration

@ComponentScan(basePackages={"org.zerock.sample"})

public class RootConfig

 

추가.

 

 

2.3 스프링이 동작하면서 생기는 일

 

- 스프링 프레임워크가 시작되면 먼저 스프링이 사용하는 메모리 영역을 만들게 되는데 이를 컨텍스트라고 한다. 스프링에선 ApplicationContext라는 이름의 객체가 만들어진다

- 스프링은 자신이 객체를 생성하고 관리해야 하는 객체들에 대한 설정이 필요하다. root-context.xml이 바로 이 역할

- root-context.xml의 contxt:component-scan 태그를 통해 org.zerock.sample 패키지를 스캔.

- 해당 패키지에 있는 클래스들 중에서 @Component  어노테이션이 존재하는 클래스의 인스턴스 생성

- Restaurant 객체는 Chef 객체가 필요하다는 어노테이션(@Autowired) 설정이 되어 있으므로 스프링은  Chef 객체의 레퍼런스를 Restaurant에 주입한다.

 

2.3.1 테스트 코드를 통한 확인

 

src/test/java 안에 org.zerock.sample.SampleTests 클래스 추가.

package org.zerock.sample;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class SampleTests {
	@Setter(onMethod_ = { @Autowired })
	private Restaurant restaurant;
	
	@Test
	public void testExist() {
		assertNotNull(restaurant);
		log.info(restaurant);;
		log.info("-------------------");
		log.info(restaurant.getChef());
	}
}

 

@RunWith 어노테이션은 테스트 코드가 스프링을 실행하는 역할을 할 것이라는 걸 표시

 

@ContextConfiguration 어노테이션과 속성값인 문자열은, 지정된 클래스나 문자열을 이용해서 필요한 객체들을 스프링 내에 객체로 등록하게 된다. classpath나 file을 사용할 수 있고 이클립스에 자동으로 생성된 root-context.xml의 경로를 지정할 수 있다.

 

@Log4j는 Lombok을 이용해 로그를 기록하는 Logger 변수를 생성한다. 별도의 Logger 객체 선언이 없어도 Log4j 라이브러리와 설정이 존재한다면 바로 사용할 수 있다.

 

@Autowired는 해당 인스턴스 변수가 스프링으로부터 자동 주입해 달라는 표시이고 정상적으로 주입이 가능하다면 obj 변수에 Restaurant 타입 객체를 주입하게 된다.

testExist()에 선언되어 있는 @Test는 JUnit에서 테스트 대상을 표시하는 어노테이션이다. 해당 메서드를 선택하고 JUnit Test 기능을 실행한다. assertNotNull()은 restaurant 변수가 null이 아니어야만 테스트가 성공한다는 것을 의미한다.

 

Run As > Junit Test

 

여기서 initialization error가 났다면, 프로젝트 > Build Path > Add Library > JUnit 추가.

 

- new Restaurant()와 같이 객체를 생성한 적이 없는데도 객체가 만들어졌다 : 스프링은 관리가 필요한 객체를 어노테이션 등을 이용해 관리, 생성하는 컨테이너나 팩토리 기능을 가지고 있다

- Restaurant 클래스이 @Data 어노테이션으로 Lombok을 이용해 여러 메서드가 만들어진다 : Lombok은 자동으로 getter/setter 등을 만들어주는데, 스프링은 생성자 주입 혹은 setter 주입을 이용해 동작한다. Lombok을 통해 getter/setter 등을 자동생성하고 onMethod 속성을 이용해 작성된 setter에 @Autowired 어노테잇녀을 추가한다

- Restaurant객체의 Chef 인스턴스 변수에 Chef 타입의 객체가 주입되어 있다는 점. - 스프링은 @Autowired와 같은 어노테이션을 이용해 개발자가 직접 객체들과의 관계를 관리하지 않아도 자동으로 관리되도록 한다.

 

 

 

Java를 이용하는 경우 테스트 설정

 

package org.zerock.sample;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {RootConfig.class})
@Log4j
public class SampleTests {
	@Setter(onMethod_ = {@Autowired})
	private Restaurant restaurant;
	
	@Test
	public void testExist() {
		assertNotNull(restaurant);
		log.info(restaurant);
		log.info("-----------------------");
		log.info(restaurant.getChef());
	}
}

 

2.3.2 코드에 사용된 어노테이션들

 

Lombok 관련

 

Lombok은 컴파일 시 흔하게 코드를 작성하는 기능을 완성해주는 라이브러리.

@Setter 어노테이션은 setter 메서드를 만들어주는 역할.

 

@Setter의 속성은 3가지

value - 접근 제한 속성을 의미한다, default = lombok.AccessLevel.PUBLIC

onMethod - setter 메서드의 생성 시 메서드에 추가할 어노테이션을 지정.

onParam - setter 메서드의 파라미터에 어노테이션을 사용하는 경우

 

@Data는 가장 자주 사용되는 어노테이션

@ToString, @EqualsAndHashCode, @Getter/@Setter, @RequiredARgsConstructor를 모두 결합한 형태.

 

@Log4j 어노테이션은 로그 객체를 생성. 

 

@Log를 클래스 쪽에 붙여주면 내부적으로 static final로 Logger 객체가 생성되므로 개발 시 별도의 로그를 설정할 필요 없이 필요한 코드를 만들어낼 수 있다.

 

Spring 관련

 

@Component는 해당 클래스가 스프링에서 객체로 만들어 관리하는 대상임을 명시하는 어노테이션. @Component가 있는 클래스를 스프링이 읽어주도록 @ComponentScan을 통해 지정되어 있으므로 해당 패키지에 있는 클래스들을 조사하면서 클래스를 객체로 생성해 빈으로 관리하게 된다.

@Autowired는 스프링 내부에서 자신이 특정한 객체에 의존적이므로 자신에게 해당 타입의 빈을 주입해주라는 의미. 예제에선 Restaurant객체는 Chef 타입 객체가 필요하다는 것을 명시. 해당 객체는 빈으로 관리되고 있어야 함

 

테스트 관련

 

@ContextConfiguration은 스프링이 실행되며 어떤 설정 정보를 읽어 들여야 하는지 알 수 있다.

locations로 문자열 배열로 XML 설정 파일을 명시할 수 있고 classes 속성으로 클래스를 지정해줄 수 있다.

@RunWith는 테스트 시 필요 클래스 지정

스프링은 SpringJUnit4ClassRunner 클래스가 대상이 된다.

@Tests는 junit에서 해당 메서드가 jUnit 상에서 단위 테스트의 대상인지 알려줌.

 

 

2.4 스프링 4.3 이후 단일 생성자의 묵시적 자동 주입

 

크게 1) 생성자 주입과 2) Setter 주입을 사용한다. Setter 주입 시 앞의 예제와 같은 setXXX()와 같은 메서드를 작성 혹은 롬복으로 생성하고 @Autowired와 같이 어노테이션으로 스프링으로부터 자신이 필요한 객체를 주입해 주도록 한다.

 

생성자 주입은 생성자를 통해 주입을 처리한다. 객체 생성 시 의존성 주입이 필요하므로 좀 더 엄격하게 의존성 주입을 체크하는 장점이 있다.

예전에는 생성자를 정의하고 @Autowired를 추가해야만 생성자 주입이 가능했지만 이후에는 묵시적 생성자 주입이 가능하다.

 

 

 

package org.zerock.sample;

import org.springframework.stereotype.Component;

import lombok.Getter;
import lombok.ToString;

@Component
@ToString
@Getter
public class SampleHotel {
	private Chef chef;
	public SampleHotel(Chef chef) {
		this.chef = chef;
	}
}
package org.zerock.sample;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class HotelTests {
	@Setter(onMethod_ = {@Autowired})
	private SampleHotel hotel;
	
	@Test
	public void testExist() {
		assertNotNull(hotel);
		log.info(hotel);;
		log.info("---------------");
		log.info(hotel.getChef());
	}
}

 

롬복을 결합하면 생성자 대신 @AllArgsConstructor를 사용할 수 있다. 인스턴스 변수로 선언된 모든 것을 파라미터로 받는 생성자를 작성한다.

만일 여러 개의 인스턴스 변수들 중에서 특정한 변수에 대해서만 생성자를 받고 있다면, @NonNull과 @RequiredArgsConstructor 어노테이션을 사용하면 된다.

 

@RequiredArgsConstructor
public class SampleHotel {
	@NonNull
    private Chef chef;
}

@RequiredARgsConstructor는 @NonNull이나 final이 붙은 인스턴스 변수에 대한 생성자를 만들어낸다.

 

 

Chapter03. 스프링과 Oracle Database 연동

 

3.1 오라클 설치

 

https://www.oracle.com/kr/index.html

11g 버전으로 두개의 파일을 받아, 같은 폴더에서 "순서대로" 압축을 풀어준다.

 

데이터베이스 폴더가 생기며 setup.exe 실행.

보안갱신 구성 step에서 아무것도 입력하지 않고 지나갈 수 있다.

 

데이터 베이스 생성 및 구성 > 데스크톱 클래스

 

 

 

3.2 SQL Developer 설치

 

 

https://www.oracle.com/kr/tools/downloads/sqldev-v192-downloads.html

 

압축을 풀어준다.

Browse..를 클릭하고 JDK가 설치된 폴더를 찾아 선택한 후 OK

 

 

 

cmd 창에서 먼저 database에 접속해 사용자를 생성한다.

> sqlplus

 

> sys as sysdba

 

> create user max identified by 1234;

> grant connect, resource to max;

 

sql developer을 실행해 Oracle 접속 > 새로 만들기 / 데이터베이스 접속

 

테스트를 한 후 성공하면 접속

 

 

3.2.1 예제에 사용하는 계정 생성

 

create user book_ex identified by book_ex
default tablespace USERS
temporary tablespace TEMP;
grant connect, dba to book_ex;

 

3.2.2 8080 포트 변경

 

\

cmd에서 sysdba로 로그인하여 포트 8080으로 변경(권장사항)

 

 

3.3 프로젝트의 JDBC 연결

 

sql developer 하위 폴더에 jdbc > lib에 ojdbc8.jar가 있습니다.

예제 프로젝트 우측 클릭 > Properties > Java Build Path > Libraries > Add External JARs...

 

 

Deployment Assembly > Add > Java Build Path Entries > ojdbc8.jar

 

3.3.1 JDBC 테스트 코드

 

test 아래에 org.zerock.persistence 패키지 추가, JDBCTests 클래스 추가.

 

package org.zerock.persistence;

import static org.junit.Assert.fail;

import java.sql.Connection;
import java.sql.DriverManager;

import org.junit.Test;

import lombok.extern.log4j.Log4j;

@Log4j
public class JDBCTests {
	static {
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	@Test
	public void testConnection() {
		try(Connection con = 
				DriverManager.getConnection(
						"jdbc:oracle:thin:@localhost:1521:orcl",
						"book_ex",
						"book_ex")) {
			log.info(con);
		}catch(Exception e) {
			fail(e.getMessage());
		}
	}
}

3.4 커넥션 풀 설정

 

여러 명의 사용자를 동시에 처리해야 하는 웹 앱의 경우 database 연결을 이용할 때는 커넥션 풀을 이용하므로 아예 스프링에 커넥션 풀을 등록해 사용하는 것이 좋다.

 

3.4.1 라이브러리 추가와 DatabaseSource 설정

 

<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>2.7.4</version>
</dependency>

 

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
    http://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="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
		<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:orcl"></property>
		<property name="username" value="book_ex"></property>
		<property name="password" value="book_ex"></property>
	</bean>
	
	<!--  HikariCP configuration -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<constructor-arg ref="hikariConfig"/>
	</bean>
	<context:component-scan base-package="org.zerock.sample">
	</context:component-scan>
</beans>

 

스프링에서 root-context.xml은 스프링이 로딩되면서 읽어들이는 문서이므로 주로 이미 만들어진 클래스들을 이용해 스프링의 빈으로 등록할 때 사용한다. 일반적인 상황이라면 프로젝트에 직접 작성하는 클래스들은 어노테이션을 작성하는 경우가 많고, 외부 jar 파일 등으로 사용하는 클래스들은 bean 태그로 작성하는 게 대부분.

 

Java  설정을 이용하는 경우

 

package org.zerock.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration

@ComponentScan(basePackages={"org.zerock.sample"})

public class RootConfig {
	@Bean
	public DataSource dataSource() {
		HikariConfig hikariConfig = new HikariConfig();
		hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		hikariConfig.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:orcl");
		hikariConfig.setUsername("book_ex");
		hikariConfig.setPassword("book_ex");
	
		HikariDataSource dataSource = new HikariDataSource(hikariConfig);
	
	return dataSource;
	}
}

스프링이 시작되면 root-context.xml을 읽어 Spring의 context가 hikariConfig를 생성한다.

hikariConfig가 생성되면, 자동으로 DataSource가 hikariConfig의 의존성으로 주입된다.

 

DataSourceTests Class

 

package org.zerock.persistence;

import static org.junit.jupiter.api.Assertions.fail;

import java.sql.Connection;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class DataSourceTests {
	@Setter(onMethod_= {@Autowired})
	private DataSource dataSource;
	@Test
	public void testConnection() {
		try(Connection con = dataSource.getConnection()) {
			log.info(con);;
		}catch(Exception e) {
			fail(e.getMessage());
		}
	}
}
package org.zerock.persistence;

import static org.junit.jupiter.api.Assertions.fail;

import java.sql.Connection;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.config.RootConfig;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={RootConfig.class})
@Log4j
public class DataSourceTests {
	@Setter(onMethod_= {@Autowired})
	private DataSource dataSource;
	@Test
	public void testConnection() {
		try(Connection con = dataSource.getConnection()) {
			log.info(con);;
		}catch(Exception e) {
			fail(e.getMessage());
		}
	}
}

 

위는 자바로 하는 경우.

테스트 코드는 스프링에 빈으로 등록된 DataSource를 이용해 Connection을 제대로 처리할 수 있는지 확인하는 용도.

 

 

Chapter04. MyBatis와 스프링 연동

 

4.1 MyBatis

 

SQL 매핑 프레임워크로 분류되는데, JDBC 코드의 복잡하고 지루한 작업을 피하는 용도로 사용된다.

 

JDBC 프로그램 - MyBatis

직접 Connection을 맺고 마지막에 close() - 자동으로 Connection close() 가능

PreparedStatement 직접 생성 및 처리 - MyBatis 내부적으로 PreparedStatement 처리

PreparedStatement setXXX() 등에 대한 모든 작업을 개발자가 처리 - #{pop}과 같이 속성 지정하면 내부적으로 자동 처리

SELECT의 경우 직접 ResultSet처리 - 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet처리

 

 

4.1.1 MyBatis 관련 라이브러리 추가.

 

- spring-jdbc/spring-tx : 스프링에서 데이터베이스 처리와 트랜잭션 처리

- mybatis/mybatis-spring : MyBatis와 스프링 연동용 라이브

 

		      <!-- 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/org.springframework/spring-tx -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${org.springframework-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

4.1.2 SQL SessionFactory

 

MyBatis의 가장 핵심적인 객체는 SQLSession이라는 존재와 SQLSessionFactory이다. SQLSessionFactory는 이름에서 보듯 내부적으로 SQLSession을 만들어내는 것인데 개발에선 SQLSession을 통해 Connection을 생성하거나 원하는 SQL을 전달하고 결과를 리턴받는 구조로 작성하게 된다.

 

root-context.xml 일부.

 

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

스프링에 sqlSessionFactory를 등록하는 것은 sqlSessionFactoryBean을 이용한다. MyBatis의 패키지가 아니라 스프링과 연동 작업을 처리하는 mybatis-spring 라이브러리의 클래스임을 알 수 있다.

 

 

Java 설정을 이용하는 경우

 

RootConfig.java

 

	@Bean
	public SqlSessionFactory sqlSessionFacotory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
		sqlSessionFactory.setDataSource(dataSource());
		return (SqlSessionFactory) sqlSessionFactory.getObject();
	}

 

DataSourceTests.java

 

package org.zerock.persistence;

import static org.junit.jupiter.api.Assertions.fail;

import java.sql.Connection;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class DataSourceTests {
	@Setter(onMethod_= {@Autowired})
	private DataSource dataSource;
	
	@Setter(onMethod_= {@Autowired})
	private SqlSessionFactory sqlSessionFactory;
	
	@Test
	public void testConnection() {
		try(Connection con = dataSource.getConnection()) {
			log.info(con);;
		}catch(Exception e) {
			fail(e.getMessage());
		}
	}
	
	@Test
	public void testMyBatis() {
		try(SqlSession session = sqlSessionFactory.openSession();
			Connection con = session.getConnection();
			)
		{
			log.info(session);
			log.info(con);
		} catch(Exception e) {
			fail(e.getMessage());
		}
	}
}

4.2 스프링과의 연동 처리

 

SQLSessionFactory를 이용해 코드를 작성해도 직접 Connection을 얻어 JDBC 코딩이 가능하지만 좀 더 편하게 작업하기 위해 SQL을 어떻게 처리할 것인지 별도로 분리해주고 자동으로 처리되는 방식을 이용하는 게 좋다.

Mapper는 SQL과 그에 대한 처리를 지정하는 역할. MyBatis-Spring을 이용하는 경우 MApper를 XML과 인터페이스 + 어노테이션의 형태로 작성할 수 있다.

 

 

4.2.1 Mapper 인터페이스

 

Mapper를 작성하는 작업은 XML을 이용할 수도 있지만 Mapper 인터페이스를 만들어 보자.

org.zerock.mapper 패키지 > TimeMapper 인터페이스 추가.

 

package org.zerock.mapper;

import org.apache.ibatis.annotations.Select;

public interface TimeMapper {
	@Select("SELECT sysdate FROM dual")
	public String getTime();
}

 

Mapper 설정

 

Mapper를 작성했다면 Mapper를 인식할 수 있도록 root-context.xml에 추가 설정 필요.

root-context.xml을 열고 Namespaces 항목에서 mybatis-spring 탭 설정.

 

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<mybatis-spring:scan base-package="org.zerock.mapper"/>
	
	<context:component-scan base-package="org.zerock.sample">
	</context:component-scan>

 

JAVA 설정을 이용하는 경우

@ComponentScan(basePackages={"org.zerock.sample"})
@MapperScan(basePackages= {"org.zerock.mapper"})
public class RootConfig {

 

4.2.2 Mapper 테스트

 

MyBatis-Spring은 Mapper 인터페이스로 실제 SQL 처리가 되는 클래스를 자동 생성한다.

 

org.zerock.persistence 밑에 TimeMapperTests 클래스 생성.

 

package org.zerock.persistence;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.mapper.TimeMapper;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class TimeMapperTests {
	@Setter(onMethod_ = @Autowired)
	private TimeMapper timeMapper;
	
	@Test
	public void testGetTime() {
		log.info(timeMapper.getClass().getName());
		log.info(timeMapper.getTime());
	}
}

JAVA의 경우

package org.zerock.persistence;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.mapper.TimeMapper;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {org.zerock.config.RootConfig.class})
@Log4j
public class TimeMapperTests {
	@Setter(onMethod_ = @Autowired)
	private TimeMapper timeMapper;
	
	@Test
	public void testGetTime() {
		log.info(timeMapper.getClass().getName());
		log.info(timeMapper.getTime());
	}
}

TimeMapperTests 클래스는 TimeMapper가 정상 작동하는지 테스트하는 코드.

스프링 내부엔 TimeMapper 타입으로 만들어진 빈이 존재하게 됨.

timeMapper.getClass().getName()은 실제 동작하는 클래스의 이름을 확인해주는데 결과를 보면 interface만 만들어 주었던 것이 내부적으로 적당한 클래스로 만들어진 걸 확인할 수 있다.

 

여기서 스프링이 인터페이스를 이용해서도 객체를 생성한다는 것이다.

 

4.2.3 XML 매퍼와 같이 쓰기

 

MyBatis를 이용해 SQL을 처리할 때 어노테이션을 이용하는 방식이 편리하긴 하지만 SQL이 길어지면 XML을 더 선호한다.

 

XML 작성 시 XML 파일 위치와 XML 파일에 지정하는 namespace 속성이 중요한데 XML 파일 위치의 경우 src/main/resources 구조에 XML을 저장할 폴더를 생성할 수 있다.

 

package org.zerock.mapper;

import org.apache.ibatis.annotations.Select;

public interface TimeMapper {
	@Select("SELECT sysdate FROM dual")
	public String getTime();
	
	public STring getTime2();
}
<?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.TimeMapper">

<select id="getTime2" resultType="string">
SELECT sysdate FROM dual
</select>

</mapper>

인터페이스에 메소드를 추가하고 xml은 위와 같이 작성.

XML 매퍼에서 신경쓸 부분은 mapper 태그의 namespace 속성. 위와 같이 org.zerock.mapper.TimeMapper 인터페이스가 존재하고 XML에도 동일 이름의 namespace가 있다면 이를 병합해서 처리한다.

select 태그 id 값은 메서드 이름과 동일하게 returnType 속성도 마찬가지로 메서드와 동일하게 작성한다.

 

	@Test
	public void testGetTime2() {
		log.info("getTime2");
		log.info(timeMapper.getTime2());
	}

테스트 코드는 위와 같다.

 

 

4.3 log4jdbc-log4j2 설정

 

MyBatis는 내부적으로 JDBC의 PreparedStatement를 이용해 SQL을 처리한다.

따라서 SQL에 전달되는 파라미터가 ?로 치환되어 처리된다. 복잡한 SQL의 경우 ?로 나오는 값이 제대로 되었는지 확인하기 쉽지 않고 실행된 SQL문을 정확히 확인하기 어렵다.

SQL을 변환해서 PreparedStatement에 사용된 ?가 어떤 값으로 처리되었는지 확인하는 기능을 추가해보자.

 

<!-- https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4 -->
<dependency>
    <groupId>org.bgee.log4jdbc-log4j2</groupId>
    <artifactId>log4jdbc-log4j2-jdbc4</artifactId>
    <version>1.16</version>
</dependency>

pom.xml에 추가.

1) 로그 설정 파일을 추가하는 작업과 2) JDBC의 연결 정보 수정을 해야 함.

 

log4jdbc.log4j2.properties 파일을 resources 폴더 밑에 추가.

 

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

 

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"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans http://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="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
	<!-- <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property> -->
	<!--	<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:orcl"></property> -->
		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		<property name="jdbcUrl" value="jdbc:log4jdbc:oracle:thin:@localhost:1521:orcl"></property>
		<property name="username" value="book_ex"></property>
		<property name="password" value="book_ex"></property>
	</bean>
	
	<!--  HikariCP configuration -->
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<constructor-arg ref="hikariConfig"/>
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<mybatis-spring:scan base-package="org.zerock.mapper"/>
	
	<context:component-scan base-package="org.zerock.sample">
	</context:component-scan>
</beans>

 

후에 기존 테스트 코드 실행 시 아래와 같이 JDBC와 관련된 로그들이 출력된다.

 

 

JAVA 설정

package org.zerock.config;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration

@ComponentScan(basePackages={"org.zerock.sample"})
@MapperScan(basePackages= {"org.zerock.mapper"})
public class RootConfig {
	@Bean
	public DataSource dataSource() {
		HikariConfig hikariConfig = new HikariConfig();
		//hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		//hikariConfig.setJdbcUrl("jdbc:oracle:thin:@localhost:1521:orcl");
		hikariConfig.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
		hikariConfig.setJdbcUrl("jdbc:log4jdbc:oracle:thin:@localhost:1521:orcl");
		hikariConfig.setUsername("book_ex");
		hikariConfig.setPassword("book_ex");
	
		HikariDataSource dataSource = new HikariDataSource(hikariConfig);
	
	return dataSource;
	}
	
	@Bean
	public SqlSessionFactory sqlSessionFacotory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
		sqlSessionFactory.setDataSource(dataSource());
		return (SqlSessionFactory) sqlSessionFactory.getObject();
	}
}

 

4.3.1 로그의 레벨 설정

 

테스트 코드 로그와 관련된 설정은 src/test/resource 밑에 log4j.xml

 

 

 

log의 INFO는 log4j.xml 마지막 부분에 있는 설정에 영향을 받는다.

 

	<!-- Root Logger -->
	<root>
		<priority value="info" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

<logger>을 지정해서 처리하면 된다.

기본 설정의 로그는 info 레벨이므로 warn과 같이 좀 더 높은 레벨의 로그만 기록하게 수정할 수 있다.

 

자세한 내용은 https://logging.apache.org/log4j/2.x/manual/customloglevels.html 참고

 

<logger name="jdbc.audit">
	<level value="warn"/>
</logger>

<logger name="jdbc.resultset">
	<level value="warn"/>
</logger>

<logger name="jdbc.connection">
	<level value="warn"/>
</logger>