Java SpringBoot 입문 Part.01

SpringBoot FrameWork 사용을 위한 기초

Featured image

Spring은 엔터프라이즈 급의 웹 애플리케이션 개발을 위한 라이브러리를 제공하는 프레임워크 이다.



Chapter.01 기본 프로젝트 시작

pom.xml 설정 파일 에 새로 입력한 소스 목록이다.

새로 입력한 부분 주석 부분이 원래 존재하던 소스 말고 새로 입력한 부분이다.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>hajiboot</artifactId>
  <packaging>jar</packaging>
  <version>1.0.0-SNAPSHOT</version>
  <name>hajiboot</name>
  <url>http://maven.apache.org</url>
  
  <!-- 새로 입력한 부분5 -->
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <!-- end -->

  <!-- 새로 입력한 부분1 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.2.4.RELEASE</version>
  </parent>
  <!-- end -->

  <dependencies>
    <!-- 새로 입력한 부분2 --> 
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- end -->

    <!-- 새로 입력한 부분3 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- end -->

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

    <!-- 새로 입력한 부분4 -->
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    <!-- end -->
</project>
번호 설명
1번 부분 스프링 부트의 설정 정보를 상속합니다.

여기서 지정한 버전이 스프링 부트의 버전이 됩니다.

스프링 부트의 버전을 올리려면 태그 안에 있는 설정 값을 변경합니다.
2번 부분 스프링 부트로 웹 어플리케이션을 만들 떄 참조할 기본 라이브러리 정보를 설정합니다.

웹 어플 제작에 필요한 스프링 프레임워크 관련 라이브러리와 서드파티 라이브러리를 이용할 수 있게됨
3번 부분 스프링 부트로 제작하는 과정에서 유닛 테스트에 필요한
라이브러리 참조 정보를 설정합니다(일반적인 라이브러리 이므로 추가)
4번 부분 스프링 부트로 제작한 애플리케이션을 간단하게 빌드하고 실행하기 위해 메이븐 플로그인을 설정합니다.
5번 부분 자바 8을 사용할 수 있도록 설정합니다.

Chapter.02

HelloWorld 띄워보기

// App.java

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Hello world!
 *
 */

@RestController // 이 클래스가 웹 에서 요청을 받아들이는 컨트롤러 클래스임을 나타냄
@EnableAutoConfiguration // 스프링 부트에서 대단히 중요한 요소임 다양한 설정이 자동으로 수행되고 기존의 스프링 어플에 필요했던 설정 파일들이 필요 없게 됨
// @SpringBootApplication // Configuration, enableautoconfig, componentScan 등 프로그래머가 자주 사용하는 어노테이션을 기본값 으로 결합한 어노테이션 SpringBoot 버전 1.2.0 부터 사용 가능.
public class App 
{
	
	@RequestMapping // 이 메서드가 HTTP요청을 받아들이는 메소드임을 나타냄
	String home() {
		return "Hello World!"; // http응답 반환 컨트롤러 어노테이션 클래스에 속한 메서드에서 문자열을 반환하면 해당 문자열이 그대로 HTTP 응답이 되어 출력됨.
	}
	
    public static void main( String[] args )
    {
       // 스프링부트 어플을 실행하는 데 필요한 처리를 main() 메소드 안에 작성
       // @EnableAutoConfiguration 어노테이션이 붙은 클래스를 아래 클래스 메서드의 첫번째 인자로 지정합니다.
       SpringApplication.run(App.class, args);
    }
}
<!-- 빌드 안에다가 입력 해야 한다-->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				
				<!-- 추가내용 -->
				<dependencies>
					<dependency>
						<groupId>org.springframework</groupId>
						<artifactId>springloaded</artifactId>
						<version>1.2.8.RELEASE</version>
					</dependency>
				</dependencies>
				<!-- 추가 끝 -->
			</plugin>
		</plugins>
	</build>

Chapter.03 스프링 프레임워크에서 구현하는 DI


Chapter.03-1 맛보기 만들기

// 인터페이스 생성
public interface Calculator {
	public int calc(int a, int b);
}

/****************************************/

// Calculator 구현 클래스
public class AddCalculator implements Calculator {

	@Override
	public int calc(int a, int b) {
		return a + b;
	}
}

AppConfig 클래스

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

@Configuration // 이 클래스가 JavaConfig용 클래스임을 컴파일러에게 알립니다.
public class AppConfig {
	@Bean // DI컨테이너가 관리할 Bean을 생성하는 메서드에는 이 어노테이션을 붙힙니다.
	public Calculator calculator() {
		// @Bean 어노테이션으로 인해 기본적으로 메서드 이름이 Bean이름이 됩니다.
		// 또한 기본적으로 이 메소드가 생성한 이름은 싱글톤 형태로 DI컨테이너 별로 한 개만 생성 됩니다.
		return new AddCalculator(); // Calculator 타입의 싱글톤 객체
	}
}

App 클래스

import java.util.Scanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(AppConfig.class) // javaConfig흫 읽어들이기 위해 @Cunfiguration 어노테이션이 붙은 클래스를 지정
public class App {
	public static void main(String[] args) {
		
		// try() 안에 정의하는 방법을 try-with-resources이다. 처리가 끝나면 
		// 자동으로 close() 메소드가 호출되어  DI컨테이너가 소멸하고 어플리케이션을 종료합니다.
		try(ConfigurableApplicationContext context = 
				SpringApplication.run(App.class, args)) {
			
			// SpringApplication.run으로 @EnableAutoConfiguration을 붙힌 클래스를 지정
			// 이 메소드의 반환값은 DI컨테이너의 본체인 타입으로 반환 받습니다.
			Scanner sc = new Scanner(System.in); // 데이터 입력
			System.out.println("Enter 2 number like 'a b' : ");
			int a = sc.nextInt();
			int b = sc.nextInt();
			
			// getBean() 메소드를 이용하여 DI 컨테이너에서 Calculator 인스턴스를 반환 받는다
			// 실제 인스턴스는 DI가 알아서 찾아주므로 어플리케이션 쪽에서는 신경쓰지 않아도 됩니다.
			Calculator calculator = context.getBean(Calculator.class);
			int result = calculator.calc(a, b);
			System.out.println("result = " + result);
			
			sc.close(); // Scanner 닫기
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Chapter.03-2 어플리케이션 추상화하기

// 인터페이스 부분
import java.io.InputStream;

public interface ArgumentResolver {
	
	Argument resolve(InputStream is);
}

// Argument 클래스 구현 부분
import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data // class파일을 생성할때 각 필드의 setter/getter, toString, equals, hashCode 메소드가
// 생성되므로 소스코드가 간결해진다. 아래 필드는 final형태라 setter/getter가 생성되지 않는다.
@RequiredArgsConstructor
public class Argument {
	private final int a;
	private final int b;
}
// ArgumentResolver의 본체
import java.io.InputStream;
import java.util.Scanner;

// Resolver : 결의, 결심, 결단
public class ScannerArgumentResolver implements ArgumentResolver{
	
	@Override
	public Argument resolve(InputStream is) {
		Scanner scanner = new Scanner(is);
		
		int a = scanner.nextInt();
		int b = scanner.nextInt();
		return new Argument(a, b);
	}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 이 클래스가 JavaConfig용 클래스임을 컴파일러에게 알립니다.
public class AppConfig {
	
	@Bean // DI컨테이너가 관리할 Bean을 생성하는 메서드에는 이 어노테이션을 붙힙니다.
	public Calculator calculator() {
		... AppConfig와 동일
	}
	
  // 이 부분을 추가해줌 의존성을 낮추고 독립성을 높힘
	@Bean // DI컨테이너가 관리할 Bean을 생성하는 메서드
	ArgumentResolver argumentResolver() {
		return new ScannerArgumentResolver();
	}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(AppConfig.class) // javaConfig흫 읽어들이기 위해 @Cunfiguration 어노테이션이 붙은 클래스를 지정
public class App {
	public static void main(String[] args) {
		
		// try() 안에 정의하는 방법을 try-with-resources이다. 처리가 끝나면 
		// 자동으로 close() 메소드가 호출되어  DI컨테이너가 소멸하고 어플리케이션을 종료합니다.
		try(ConfigurableApplicationContext context = 
				SpringApplication.run(App.class, args)) {
			
			// SpringApplication.run으로 @EnableAutoConfiguration을 붙힌 클래스를 지정
			// 이 메소드의 반환값은 DI컨테이너의 본체인 타입으로 반환 받습니다.
			System.out.println("Enter 2 numbers like 'a b' : ");
			ArgumentResolver ar = context.getBean(ArgumentResolver.class);
			
			Argument argument = ar.resolve(System.in);
			Calculator calculator = context.getBean(Calculator.class);
			
      // @Data 어노테이션
			int result = calculator.calc(argument.getA(), argument.getB());
			System.out.println("result = " + result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Chapter.03-3 오토 와이어링을 이용한 DI

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

public class Frontend {

	@Autowired // 어노테이션을 붙인 필드는 DI 컨테이너가 주입해야 할 필드를 의미합니다.
	ArgumentResolver argumentResolver; // 자동으로 객체를 넣어줌
	@Autowired
	Calculator calculator; // 자동으로 객체를 넣어줌
	// DI컨테이너는 관리하고 있는 객체 중에서 @Autowired 어노테이션이 붙은 필드에 맞는 객체를 자동으로 찾아내어 주입.
	// 이를 오토 와이어링 이라고 부릅니다.
	
	public void run() {
		System.out.print("Enter 2 number like 'a b' : ");
    // 오토 와이어링 으로 인해 자동으로 객체를 찾아줌.
		Argument argument = argumentResolver.resolve(System.in);
		int result = calculator.calc(argument.getA(), argument.getB());
		System.out.println("result = " + result);
	}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 이 클래스가 JavaConfig용 클래스임을 컴파일러에게 알립니다.
public class AppConfig {
	
  ... 다른 Bean 메소드
	
  // 아래는 추가 부분
	@Bean // DI컨테이너가 관리할 Bean을 생성하는 메서드
	Frontend frontend() {
		return new Frontend();
	}
}

오토와이어링 을 적용한 후 의 DI 컨테이너

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(AppConfig.class) // javaConfig흫 읽어들이기 위해 @Cunfiguration 어노테이션이 붙은 클래스를 지정
//@Import(AppConfig.class) // ComponentScan으로 인해 필요 없습니다.
public class App {
	public static void main(String[] args) {
		
		// try() 안에 정의하는 방법을 try-with-resources이다. 처리가 끝나면 
		// 자동으로 close() 메소드가 호출되어  DI컨테이너가 소멸하고 어플리케이션을 종료합니다.
		// SpringApplication.run으로 @EnableAutoConfiguration을 붙힌 클래스를 지정
		// 이 메소드의 반환값은 DI컨테이너의 본체인 타입으로 반환 받습니다.
		try(ConfigurableApplicationContext context = 
				SpringApplication.run(App.class, args)) {
			
			Frontend frontend = context.getBean(Frontend.class);
			frontend.run();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Chapter.03-5 컴포넌트 스캔을 사용하여 자동으로 Bean 등록하기


Chapter.03-6 CommandLineRunner 사용하기

CommandLineRunner 인터페이스 구현으로 바뀐 App 클래스

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App implements CommandLineRunner{ // 앞서 구현한 FrontEnd 클래스의 Run메소드와 같은 역할
	
	@Autowired // 자동 오토 와이어링 으로 인해 자동으로 객체를 찾아주는 역할을 하라 하는 어노테이션
	Calculator calculator;
	@Autowired
	ArgumentResolver argResolver;
	
	@Override
	public void run(String... args) throws Exception {
		System.out.println("Enter 2 number like 'a b' : ");
		
		// 오토 와이어링 으로 인한 자동객체 대입
		Argument argument = argResolver.resolve(System.in);
		
		// 오토 와이어링...
		// Argument 에서 구현한 InputStream 으로 받은 키보드 인자값을 계산;
		int result = calculator.calc(argument.getA(), argument.getB());
		System.out.println("result : " + result);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

Chapter 정리

commandLineRunner


Chapter.03-7 레이어로 구성한 컴포넌트 주입하기

Annotation description
@Controller 웹 MVC 프레임워크인 스프링 MVC의 컨트롤러를 나타내는 어노테이션 입니다. 스프링4
에서는 REST웹 서비스용으로 @RestController를 추가했습니다.
@Service 서비스 클래스를 나타내는 어노테이션 입니다. 기능 면에서는 @Component와 다르지 않습니다.
@Repository 리포지토리 클래스를 나타내는 어노테이션 입니다. 이 어노테이션이 붙은 클래스
안에서 발생한 예외는 특수한 규칙에 따라 스프링이 제공하는 DataAccessException으로 교체 됩니다.

(엔트리 포인트/컨트롤러) -> (서비스) -> (리포지토리)
       ↑—————-↓ ↑———–↓
<—————  (DI 컨테이너)  —————–>


Chapter.04-1 리포지토리와 서비스를 이용한 간단한 고객 과닐 시스템 어플 만들기

SimpleServiceApp

//------------------------ Domain Class
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// @Data의 역할
// class파일을 생성할때 각 필드의 setter/getter, toString, equals, hashCode 메소드가
// 생성되므로 소스코드가 간결해진다. 아래 필드는 final형태라 setter/getter가 생성되지 않는다.
@Data
@AllArgsConstructor // 롬복이 제공하는 이 어노테이션들을 붙혀서 모든 필드를 인자로 받는 생성자를 만듭니다.
@NoArgsConstructor
public class Customer {
	private Integer id;
	private String firstName;
	private String lastName;
}

//------------------------ Repository Class
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.springframework.stereotype.Repository;

import com.example.demo.domain.Customer;

@Repository
public class CustomerRepository {
	private final ConcurrentMap<Integer, Customer> customerMap = 
			new ConcurrentHashMap<Integer, Customer>();
	
	// 전부 출력
	public List<Customer> findAll() {
		return new ArrayList<Customer>(customerMap.values());
	}
	
	// HashMap 요소 Key값 으로 찾기
	public Customer findOne(Integer customerId) {
		return customerMap.get(customerId);			
	}
	
	// HashMap 요소 추가
	public Customer save(Customer customer) {
		return customerMap.put(customer.getId(), customer);
	}
	
	// HashMap 요소 삭제
	public void delete(Integer customerId) {
		customerMap.remove(customerId);
	}
}

//------------------------ Service Class
import java.util.List;

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

import com.example.demo.domain.Customer;
import com.example.demo.repository.CustomerRepository;

@Service
public class CustomerService {

	@Autowired
	CustomerRepository customerRepository;

	public Customer save(Customer customer) {
		return customerRepository.save(customer);
	}

	public List<Customer> findAll() {
		return customerRepository.findAll();
	}
}

//------------------------ App Class
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.example.demo.domain.Customer;
import com.example.demo.service.CustomerService;

@SpringBootApplication
public class App implements CommandLineRunner{
	
	@Autowired
	CustomerService customerService;
	
	@Override
	public void run(String... args) throws Exception {
		// 데이터 추가
		customerService.save(new Customer(1,  "Nobita", "Nobi"));
		customerService.save(new Customer(2, "Shin", "SungHyeun"));
		customerService.save(new Customer(3, "Kim", "ShinHan"));
		
		// 데이터 표시
		customerService.findAll().forEach(System.out::println);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

Chapter.04-2 스프링 JDBC를 사용한 DB접속

새로운 프로젝트 생성후 pom.xml 의존 관계를 추가하자.

<!-- 밑의 의존관계를 추가하면 스프링 JDBC로 DB에 접속하는 기능에 필요한 lib가 추가 됩니다. -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- 이번 예제 에서는 h2 DB를 사용 합니다. -->
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId\>
</dependency>

Chpater.04-3 JdbcTemplate으로 DB 접속하기

App.java 구현 부분

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

@SpringBootApplication
public class App implements CommandLineRunner{
	
	/*
	 *	DI 컨테이너에 등록된 NamedParameter객체를 얻어옵니다.
	 *	SpringBoot는 자동 설정 이라는 기능을 통해 DataSource나 JdbcTemplate, NamedParameter... 을 자동으로 생성하여
	 *	DI 컨테이너에 등록 합니다.
	 *	따라서 SpringBoot는 의존 lib에 spring-boot-starter-jdbc와 JDBC 드라이버만 추가하면 다른 설정을 하지 않아도
	 *	위 템플릿을 사용할 수 있습니다. 이번 예제는 pom.xml에 H2의존 관계를 JDBC드라이버로 정의 했으므로 H2 데이터베이스용 DataSource가 
	 *	생성됩니다. H2 DB를 사용하여 DB의 URL을 지정하는 일이 없다면 기본값으로 인 메모리 DB가 만들어집니다. DB설정이 번거롭지 않으므로
	 *	동작을 확인하기에는 편리 합니다. 
	 */
	@Autowired
	NamedParameterJdbcTemplate jdbcTemplate;
	
	@Override
	public void run(String... args) throws Exception {
		String sql = "SELECT 1"; // 간단한 SQL입니다. 문법상 맞지않는 DBMS도 있지만 H2는 그대로 1을 반환합니다.
		
		/*
		 *  밑의 클래스로 SQL에 삽입할 param을 만듭니다 이번예제 에서는 param을 사용하지 않으므로
		 *	디폴트 생성자를 이용 합니다. 이것 대신 Map<String, Objecr>를 사용 가능 하지만
		 *	더 편리한 밑의 클래스를 사용합니다.
		 */	
		SqlParameterSource param = new MapSqlParameterSource();
		
		/*
		 * queryForObject() 메소드를 사용하여 쿼리 실행 결과를 오브젝트로 변환한 상태로 얻어옵니다.
		 * 첫번째 인자로 SQL, 두번째 인자로 param, 세번쨰 인자로 반환 값이 될 객체 클래스를 지정합니다.
		 * 이 메소드는 쿼리 반환 값이 단일 레코드가 아니면 IncorrectResult... 예외를 발생 시킵니다.
		 */
		Integer result = jdbcTemplate.queryForObject(sql, param, Integer.class);
		
		System.out.println("result = " + result);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
	@Override
	public void run(String... args) throws Exception {
		String sql = "SELECT :a + :b"; // :a와 :b 라는 플레이스 홀더 사용

		// 아래 param addValue() 메서드로 플레이스 홀더로 명시한 param값에  값을 넣어줌.
		SqlParameterSource param = new MapSqlParameterSource()
				.addValue("a", 100)
				.addValue("b", 200);

		Integer result = jdbcTemplate.queryForObject(sql, param, Integer.class);
		
		System.out.println("result = " + result);
	}
	
	// 콘솔창에 결과값 300 출력 됨.
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

import com.example.app.domain.Customer;

@SpringBootApplication
public class App implements CommandLineRunner{

	@Autowired
	NamedParameterJdbcTemplate jdbcTemplate;
	
	@Override
	public void run(String... args) throws Exception {
		// customers 테이블에서 키를 지정하여 정보를 얻는 SQL를 작성
		String sql = "SELECT id, first_name, last_name FROM customers WHERE id = :id";
		
		// 파라미터로 기본 키의 값을 설정합니다.
		SqlParameterSource param = new MapSqlParameterSource()
				.addValue("id", 1);
		
		// ResultSer에서 Customer 객체를 생성하는 RowMapper<Customer>를 구현합니다.
		Customer result = jdbcTemplate.queryForObject(sql, param, new RowMapper<Customer>() {
			@Override
			public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new Customer(rs.getInt("id"), 
									rs.getString("first_name"), 
									rs.getString("last_name"));
			}
		});
		
		System.out.println("result = " + result);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
-- src/main/resources/schema.sql 에 사용할 DDL
CREATE TABLE customers (id INT PRIMARY KEY AUTO_INCREMENT, first_name VARCHAR(30), last_name VARCHAR(30));
-- src/main/resources/data.sql 초기 데이터
INSERT INTO customers(first_name, last_name) VALUES('Nobita', 'Nobi');
INSERT INTO customers(first_name, last_name) VALUES('Nobita2', 'Nobi2');
INSERT INTO customers(first_name, last_name) VALUES('Nobita3', 'Nobi3');
INSERT INTO customers(first_name, last_name) VALUES('Nobita4', 'Nobi4');
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

import com.example.app.domain.Customer;

@SpringBootApplication
public class App implements CommandLineRunner{

	@Autowired
	NamedParameterJdbcTemplate jdbcTemplate;
	
	@Override
	public void run(String... args) throws Exception {
		// customers 테이블에서 키를 지정하여 정보를 얻는 SQL를 작성
		String sql = "SELECT id, first_name, last_name FROM customers WHERE id = :id";
		
		// 파라미터로 기본 키의 값을 설정합니다.
		SqlParameterSource param = new MapSqlParameterSource()
				.addValue("id", 1);
		
		// ResultSer에서 Customer 객체를 생성하는 RowMapper<Customer>를 구현합니다.
		// Lambda식 으로 코드를 간소화 합니다.
		Customer result = jdbcTemplate.queryForObject(sql, param, (rs, num) -> new Customer(
				rs.getInt("id"), rs.getString("first_name"), rs.getString("last_name")));
		
		System.out.println("result = " + result);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

Chapter.04-4 데이터 소스 설정을 명시적으로 변경하기

spring:
	datasource:
		driverClassName: org.h2.Driver
		url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
		username: sa
		password:
spring:
	datasource:
		driverClassName: org.h2.Driver
		url: jdbc:h2:file:/tmp/testdb
		username: sa
		password:
CREATE TABLE IF NOT EXISTS customers (id INT PRIMARY KEY AUTO_INCREMENT, first_name VARCHAR(30), last_name VARCHAR(30));

Chapter.04-5 Log4JDBC2

pom.xml 의존성 추가

<dependency>
	<groupId>org.bgee.log4jdbc-log4j2</groupId>
	<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
	<version>1.16</version>
</dependency>

application.properties 설정

# spring.datasource.url=jdbc:h2:file:C:/tmp/testdb
spring.datasource.url=jdbc:log4jdbc:h2:file:C:/tmp/testdb;AUTO_SERVER=TRUE
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy

# spring.datasource.url=jdbc:h2:mem:testdb

spring.datasource.data-username=sa
spring.datasource.data-password=

# base Properties ON/OFF
spring.h2.console.enabled=false

logback-spring.xml 설정

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/base.xml"/>
	
	<!-- 좀 더 보기 좋은 MyBatis 쿼리 Log : log4jdbc -->
	<logger name="jdbc.sqlonly" level="debug" />
	<logger name="jdbc.sqltiming" level="off" />
	<logger name="jdbc.audit" level="off" />
	<logger name="jdbc.resultset" level="off" />
	<logger name="jdbc.resultsettable" level="debug" />
	<logger name="jdbc.connection" level="off" />
</configuration>

Chapter.04-6 JdbcTemplate으로 리포지토리 클래스 구현하기

수정한 Customer리포지토리

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.example.app.domain.Customer;

@Repository
// 이러한 클래스를 DI컨테이너 에서 가져오고 해당 클래스에 속한 각 메소드를 다른 클래스에서 호출 하면 DB 트랜잭션이 자동으로 이루어 집니다.
// 메서드가 제대로 실행되면 DB 트랜잭션이 커밋됩니다.
// 실행 도중 오류가 발생하면 DB 트랜잭션이 롤백 됩니다.
// DI 컨테이너는 각 메소드 앞뒤에 처리를 추가한 클래스를 동적으로 생성합니다.
@Transactional 
public class CustomerRepository {
	@Autowired
	NamedParameterJdbcTemplate jdbcTemplate;
	
	// 템플릿을 이용하여 query() 메소드의 람다식을 이용하여 SQL 실행 결과를 자바 객체 리스트 형태로 가져옵니다.
	private static final RowMapper<Customer> customerRowMapper = (rs, i) -> {
		Integer id = rs.getInt("id");
		String firstName = rs.getString("first_name");
		String lastName = rs.getString("last_name");
		return new Customer(id, firstName, lastName);
	};
	
	// Select All Condition Block
	public List<Customer> findAll() {
		String sql = "SELECT id,first_name,last_name FROM customers ORDER BY id";
		
		// 리스트로 가져 옵니다. 이를 위해선 위 RowMapper 클래스의 query()를 구현 해야 합니다.
		// 위에선 람다식을 사용하여 구현 했습니다.
		List<Customer> customers = jdbcTemplate.query(
				sql, customerRowMapper);
		return customers;
	}
	
	// ID값을 기준으로 행을 가져 옵니다.
	// Select Condition Block
	public Customer findOne(Integer customerId) {
		String sql = "SELECT id,first_name,last_name FROM customers WHERE id=:id";
		
		// MapSqlParam... 클래스의 addValue() 메소드를 이용해 customerId 인자값을 기준으로 SELECT문을 수행합니다.
		SqlParameterSource param = new MapSqlParameterSource().addValue("id", customerId);
		return jdbcTemplate.queryForObject(sql, param, customerRowMapper);
	}
	
	// Update Block
	public Customer save(Customer customer) {
		String insert = "INSERT INTO customers(first_name, last_name) ";
		String values = "values(:firstName, :lastName)";
		
		String update = "UPDATE customer SET first_name=:firstName, last_name=:lastName WHERE id=:id";
		
		/*
		 * 업데이트 계열의 SQL을 실행하기 위해 템플릿의 Update() 메소드를 사용합니다.
		 * Customer 객체의 id필드가 null 이면 insert 문을 수행 합니다.
		 * 객체의 id필드가 null이 아니면 update 문을 수행 합니다.
		 */
		// BeanPropertySqlParameterSource를 사용하면 자바객체에 포함된 필드 이름과 
		// 값을 매핑한 SqlParameterSource가 자동으로 작성 됩니다.
		SqlParameterSource param = new BeanPropertySqlParameterSource(customer);
		if (customer.getId() == null) {
			jdbcTemplate.update(insert+values, param);
		} else {
			jdbcTemplate.update(update, param);
		}
		
		return customer;
	}
	
	// Delete Condition Block
	public void delete(Integer customerId) {
		String sql = "DELETE FROM customers WHERE id=:id";
		
		// delete문을 사용할 떄도 update문을 사용 합니다.
		SqlParameterSource param = new MapSqlParameterSource().addValue("id", customerId);
		jdbcTemplate.update(sql, param);
	}
}

App.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.example.app.domain.Customer;
import com.example.app.repository.CustomerRepository;

@SpringBootApplication
public class App implements CommandLineRunner{
	@Autowired
	CustomerRepository customerRepository;

	@Override
	public void run(String... args) throws Exception {
		// 데이터 추가
		Customer created = customerRepository.save(new Customer(null, "Hidetoshi", "Dekisugi"));
		System.out.println(created + " is created!");
		
		// 데이터 표시
		// forEach
		customerRepository.findAll().forEach(System.out::println);
	}
	
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
package com.example.app.repository;

import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.example.app.domain.Customer;

@Repository
// 이러한 클래스를 DI컨테이너 에서 가져오고 해당 클래스에 속한 각 메소드를 다른 클래스에서 호출 하면 DB 트랜잭션이 자동으로 이루어 집니다.
// 메서드가 제대로 실행되면 DB 트랜잭션이 커밋됩니다.
// 실행 도중 오류가 발생하면 DB 트랜잭션이 롤백 됩니다.
// DI 컨테이너는 각 메소드 앞뒤에 처리를 추가한 클래스를 동적으로 생성합니다.
@Transactional 
public class CustomerRepository {
	@Autowired
	NamedParameterJdbcTemplate jdbcTemplate;
	SimpleJdbcInsert insert;
	
	
	// 클래스 객체가 생성 될 떄, 아래 어노테이션을 명시한 
	// 메소드는 별도로 우선 초기화 한다.
	// 이 어노테이션은 WAS에 띄어질떄 init가 객체생성 된다.
	@PostConstruct
	public void init() {
		insert = new SimpleJdbcInsert(
				// SompleJdbcInsert에 JdbcTemplate을 설정해야 하므로 NamedParameterJdbcTemplate에 속한
				// JdbcTemplate 객체를 받아옵니다.
				(JdbcTemplate) jdbcTemplate.getJdbcOperations())
				// SimpleJdbcInsert는 INSERT SQL을 자동으로 생성하므로 테이블 이름을 명시적으로 지정.
				.withTableName("customers")
				// 자동으로 번호가 매겨지는 기본 키 칼럼명을 지정합니다.
				.usingGeneratedKeyColumns("id");
	}
	
	// 템플릿을 이용하여 query() 메소드의 람다식을 이용하여 SQL 실행 결과를 자바 객체 리스트 형태로 가져옵니다.
	private static final RowMapper<Customer> customerRowMapper = (rs, i) -> {
		Integer id = rs.getInt("id");
		String firstName = rs.getString("first_name");
		String lastName = rs.getString("last_name");
		return new Customer(id, firstName, lastName);
	};
	
	// Select All Condition Block
	public List<Customer> findAll() {
		String sql = "SELECT id,first_name,last_name FROM customers ORDER BY id";
		
		// 리스트로 가져 옵니다. 이를 위해선 위 RowMapper 클래스의 query()를 구현 해야 합니다.
		// 위에선 람다식을 사용하여 구현 했습니다.
		List<Customer> customers = jdbcTemplate.query(
				sql, customerRowMapper);
		return customers;
	}
	
	// ID값을 기준으로 행을 가져 옵니다.
	// Select Condition Block
	public Customer findOne(Integer customerId) {
		String sql = "SELECT id,first_name,last_name FROM customers WHERE id=:id";
		
		// MapSqlParam... 클래스의 addValue() 메소드를 이용해 customerId 인자값을 기준으로 SELECT문을 수행합니다.
		SqlParameterSource param = new MapSqlParameterSource().addValue("id", customerId);
		return jdbcTemplate.queryForObject(sql, param, customerRowMapper);
	}
	
	// Update Block
	public Customer save(Customer customer) {
		String update = "UPDATE customer SET first_name=:firstName, last_name=:lastName WHERE id=:id";
		
		/*
		 * 업데이트 계열의 SQL을 실행하기 위해 템플릿의 Update() 메소드를 사용합니다.
		 * Customer 객체의 id필드가 null 이면 insert 문을 수행 합니다.
		 * 객체의 id필드가 null이 아니면 update 문을 수행 합니다.
		 */
		
		// BeanPropertySqlParameterSource를 사용하면 자바객체에 포함된 필드 이름과 
		// 값을 매핑한 SqlParameterSource가 자동으로 작성 됩니다.
		SqlParameterSource param = new BeanPropertySqlParameterSource(customer);
		if (customer.getId() == null) {
			// executeAndReturnKey() 메소드를 호출하면 SQL을 실행하고 자동으로 번호가 매겨진 ID가 반환됩니다.
			Number key = insert.executeAndReturnKey(param);
			customer.setId(key.intValue());
		} else {
			jdbcTemplate.update(update, param);
		}
		
		return customer;
	}
	
	// Delete Condition Block
	public void delete(Integer customerId) {
		String sql = "DELETE FROM customers WHERE id=:id";
		
		// delete문을 사용할 떄도 update문을 사용 합니다.
		SqlParameterSource param = new MapSqlParameterSource().addValue("id", customerId);
		jdbcTemplate.update(sql, param);
	}
}

PART 2 에서 계속…