Tag Archives: Boot

Spring Boot에서 손쉽게 MySQL 사용하기

spring_title

프로젝트를 시작하기에 앞서 어떤 언어를 사용할지 어떤 프레임웍, 라이브러리를 사용할지 고민하게 됩니다. 저도 지금까지 이런 고민을 할때마다 큰 규모의 프로젝트는 Spring Framework를 사용하고 빠른 속도의 개발이 필요할때는 PHP를 선택했었습니다. 하지만 Spring Boot를 접하면서 많은 생각이 변하게 되었습니다. 이제는 빠른 개발이 필요할때는 Spring Boot를 사용하면 되겠다는 생각이 듭니다. Rails와 비교할때 아직 많은 부분 부족하다고 생각되지만 Spring의 그 무게감을 잘 유지하고 있으면서도 말도안되게 개발이 편해진것 같습니다. Play의 도입도 고민을 많이 해봤었지만 기존의 Spring에 익숙하시다면 해방된 느낌으로 편하게 사용하실 수 있다고 생각합니다.

이번에는 MySQL(MariaDB) 데이터베이스를 활용하는 프로젝트를 얼마나 손쉽게 구현할 수 있는지 알아보겠습니다. 이 프로젝트는 기존에 작성했던 [Gradle 기반 Spring Boot 프로젝트 구축하기]의 소스코드를 개선해 나가는 방향으로 진행하겠습니다. 우선 링크의 글을 먼저 읽어보시길 권장합니다.

먼저 build.gradle의 내용을 다음과 같이 수정합니다. 하는김에 커넥션풀도 구성해 보겠습니다.

dependencies {
  ...
  compile("org.springframework:spring-jdbc:+")
  compile("commons-dbcp:commons-dbcp:1.4")
  compile("mysql:mysql-connector-java:5.0.8")
}

프로젝트의 진행에 앞서 DB를 사용하는 프로젝트이므로 테스트용 DB와 테이블을 생성해 보겠습니다.

CREATE TABLE `blogs` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `content` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

이번엔 데이터베이스 설정파일을 만들어야 합니다. application.properties라는 파일을 프로젝트의 루트 또는 src/main/resources 디렉토리안에 넣어주시면 Spring Boot 구동시에 자동으로 읽어들여 프로젝트에 적용하게 됩니다.

spring.application.name=HelloSpringBoot
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/theeye?autoReconnect=true&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=
spring.datasource.maxIdle=20
spring.datasource.maxActive=400
spring.datasource.maxWait=10000
spring.datasource.validationQuery=SELECT 1

위의 내용은 Java 개발자분들께 익숙한 내용일테니 설명은 생략하도록 하겠습니다. 다음은 DAO(모델) 클래스를 만들어 보겠습니다.

@Repository
public class BlogDao {

    @Autowired
    private JdbcTemplate template;

    public void insertNewContent(String content) {
        template.update("INSERT INTO blogs(content) VALUES(?)", content);
    }
}

이제 컨트롤러에서 이 DAO를 호출해 보겠습니다.

@RestController
public class HelloController {

    @Autowired
    private BlogDao blogDao;

    @RequestMapping("/")
    public String index() {
        blogDao.insertNewContent("Hello Spring?");
        return "Greetings from Spring Boot!";
    }

}

여기까지가 끝입니다… 서버를 구동하고 페이지를 열어보시면 정상적으로 DB에 “Hello Spring?”이 저장된것을 확인할 수 있습니다. 느끼셨겠지만 DataSource를 생성하고 Connection Pool을 구축하는 과정이 생략되었습니다. build.gradle 설정에서 spring-jdbc를 추가해주기만 했는데 application.properties의 내용을 토대로 이러한 작업들을 자동화 해줍니다. 그래서 단지 JdbcTemplate을 @Autowired로 엮어줌으로써 DB를 사용할 수 있게 됩니다.

실제 서비스 환경에서 오류가 날경우

개발 환경에서는 위의 과정을 통해 사용이 정상적으로 되었습니다만 이상하게 실제 환경(java -jar로 실행)에서는 MySQL Connector를 로드하지 못하는 에러를 보였습니다. 다음과 같은 에러입니다.

org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.apache.commons.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.mysql.jdbc.Driver'
        at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
        at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:628)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:693)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:725)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:735)
        at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:803)
        at net.loungechat.server.database.UserDao.findUserByAccessToken(UserDao.java:25)
        at net.loungechat.server.processor.ChatServiceProcessor.authorize(ChatServiceProcessor.java:67)
        at net.loungechat.thrift.ChatService$Processor$authorize.getResult(ChatService.java:964)
        at net.loungechat.thrift.ChatService$Processor$authorize.getResult(ChatService.java:948)
        at org.apache.thrift.ProcessFunction.process(ProcessFunction.java:39)
        at org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:39)
        at org.apache.thrift.server.TThreadPoolServer$WorkerProcess.run(TThreadPoolServer.java:225)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)
Caused by: org.apache.commons.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.mysql.jdbc.Driver'
        at org.apache.commons.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1429)
        at org.apache.commons.dbcp.BasicDataSource.createDataSource(BasicDataSource.java:1371)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
        at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
        at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
        ... 15 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
        at org.springframework.boot.loader.LaunchedURLClassLoader.doLoadClass(LaunchedURLClassLoader.java:133)
        at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:103)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
        at org.apache.commons.dbcp.BasicDataSource.createConnectionFactory(BasicDataSource.java:1420)
        ... 19 common frames omitted

이 문제를 해결하기 위해 다양한 시도를 해보았지만 Gradle을 이용한 MySQL 원격 저장소 의존성 등록을 하지 않고 직접 라이브러리 파일을 다운받아 등록하는 방법으로 문제를 해결할 수 있었습니다.

[Download Connector/J]에서 적당한 라이브러리를 다운로드합니다. 신기하게도 저의 경우 최신버전을 다운받아 사용하면 오류가 발생했습니다. 그래서 5.0.8 버전을 사용하였습니다.

mysql_libs_dir

프로젝트의 루트에 libs 디렉토리를 생성하고 그안에 다운로드 받은 jar 파일을 넣어두었습니다. 이번엔 build.gradle의 설정을 변경하겠습니다.

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.0.0.RC1")
    compile("org.springframework:spring-jdbc:+")
    compile("commons-dbcp:commons-dbcp:1.4")
    //compile("mysql:mysql-connector-java:5.0.8")
    compile name: 'mysql-connector-java-5.0.8-bin'
    compile fileTree(dir: 'libs', includes: ['*.jar'])
}

우선 시도한 부분은 fileTree설정을 이용하여 libs 디렉토리안의 *.jar를 모두 읽어들이게 한것인데 신기하게 이것도 정상적으로 되지 않는 경우가 발생하였습니다. 그래서 compile name: 설정을 이용하여 정확한 파일명(.jar 확장자 제외)을 적어주면 됩니다.

이렇게 하므로써 환경을 타지 않고 DB를 활용할 수 있는 프로젝트의 구성이 완료되었습니다. 아마 이부분은 버그이지 않을까 생각되네요. 제작한 소스를 첨부하겠습니다. [HelloSpringBootWithMySQL]

Gradle 기반 Spring Boot 프로젝트 구축하기

Spring Boot는 기존의 복잡한 Spring 프레임워크를 활용한 개발을 매우 간단하게 활용할 수 있도록 만들어진 프레임워크입니다. Groovy기반의 Rails의 느낌이 날 정도의 개발 환경을 구축할 수 있지만 여기서는 Java 기반의 프로젝트 개발을 설명합니다. IntelliJ에서 진행되었으며 Eclipse도 크게 다르지 않을것이라 생각합니다.

687474703a2f2f626c6f672e737072696e67736f757263652e6f72672f77702d636f6e74656e742f75706c6f6164732f323031332f30382f737072696e672e706e67

잠깐밖에 다루어보지 않았지만 Spring Boot의 가장 큰 장점은 설정 파일이 사실상 전무하게 프로젝트를 당장 구동할 수 있다는 점인것 같습니다. 모든것을 어노테이션만으로 구성할 수 있습니다. 이미 Spring이 많은부분 이렇게 진화해왔지만 아예 시작부터 운영까지 단순화하는것이 목적으로 보여집니다. Spring의 경량화 버전이라기 보다는 그림과 같은 손쉽게 사용할 수 있는 인터페이스를 제공하고 있다고 보는것이 옳은것 같습니다.

2

IntelliJ를 실행한 뒤 Quick Start에서 Create New Project를 선택합니다. 위와 같은 화면이 뜨는데 Gradle를 선택해 줍니다.

3-2

이후에 Gradle 설정 화면이 나옵니다. 여기서 위와 같이 2개를 체크합니다. 위와 같은 옵션은 기본적으로 Gradle을 위한 프로젝트의 기본 형태를 자동으로 생성해 줍니다.

4

프로젝트가 생성되면 위와 같은 형태의 프로젝트 구조를 확인할 수 있습니다. 기존의 Eclipse 환경에 익숙한 저에게는 매우 특이한 디렉토리 구조라고 느껴집니다. 위는 Maven 프로젝트의 기본 구조이며 다음과 같은 의미를 가지고 있습니다.

디렉토리 설명
src/main/java 어플리케이션, 라이브러리 소스
src/main/resources 어플리케이션, 라이브러리 리소스
src/main/filters 리소스 필터 파일
src/main/assembly 어셈블리 디스크립터
src/main/config 설정 파일
src/main/scripts 어플리케이션, 라이브러리 스크립트
src/main/webapp 웹 어플리케이션 소스
src/test/java 테스트 소스
src/test/resources 테스트 리소스
src/test/filters 테스트 리소스 필터 파일
src/site 사이트
LICENSE.txt 프로젝트의 라이센스
NOTICE.txt 프로젝트 라이브러리에서 필요로 하는 주의사항 특징
README.txt 프로젝트의 리드미
buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-snapshot" }
        mavenLocal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC1")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'

sourceCompatibility = 1.7
version = '1.0'

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/libs-snapshot" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.0.0.RC1")
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

build.gradle 파일을 위와같이 수정해 줍니다. build.gradle 파일이 한번 수정되면 IntelliJ가 자동으로 필요한 라이브러리를 다운받고 의존성 문제를 해결해 줍니다. 하지만 곧바로 적용하고 싶으실 경우 다음과 같은 방법을 사용하시면 됩니다.

5

보시면 Gradle 탭이 존재합니다. 눌러 보시면 실행가능한 Task 리스트가 나옵니다. 탭의 왼쪽 상단을 보시면 강제 리프레시 버튼이 존재하는데 저걸 눌러주면 의존성 문제를 강제로 해결해 줍니다.

6

이제 부터 위와 같이 hello 패키지를 만들고 그안에 두개의 클래스를 추가할 것입니다. 먼서 HelloController부터 만들어보겠습니다.

package hello;

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

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

스프링 개발을 기존에 해오신 분들이라면 위의 의미를 바로 아실것입니다. 한가지만 짚고 넘어가자면 @RestController는 @Controller와 @ResponseBody를 합친 기능을 합니다. 그러므로 곧바로 렌더링할 스트링을 바로 반환하는것을 볼 수 있습니다. 이번에는 Application을 만들어 보겠습니다.

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        System.out.println("Spring Boot Started.");
    }

}

이제 끝났습니다. 예상이 되시겠지만 위에서 보여지는것들을 몇가지 정리해보자면 다음과 같습니다.

  • @Configuration 태그는 현재의 클래스가 Spring의 설정파일임을 어플리케이션 컨텍스트에게 알려주는 역할을 합니다.
  • @EnableAutoConfiguration 은 Spring Boot가 클래스패스 세팅, 다른 Bean들, 다양한 설정들에 의해 Bean을 추가하도록 합니다.
  • 일반적으로 기존의 Spring MVC 어플리케이션에서는 @EnableWebMvc 태그를 사용했지만 Spring Boot는 클래스패스에서 spring-webmvc를 발견할 경우 자동으로 추가합니다. 이러한 플래그는 DispatcherServlet을 세팅하는것과 같은 개발중인 어플리케이션을 웹어플리케이션으로 활성화 하는 핵심 키가 됩니다.
  • @ComponentScan 은 Spring에게 hello 패키지 안에서 다른 컴포넌트, 설정, 서비스를 찾도록 합니다. 이 설정을 통해 HelloController를 찾는것이 가능해집니다.

여기서 main() 메소드는 Spring Boot의 SpringApplication.run() 메소드를 실행함으로써 어플리케이션을 시작하게 됩니다. 여기까지 단 한줄의 XML 코드도 볼수 없었습니다. 심지어 web.xml도 보이지 않습니다. 이 어플리케이션은 100% 순수한 Java 어플리케이션이고 이 프로젝트를 구동하기 위한 어떤 인프라(WAS 설정등)의 구성 작업도 하지 않았습니다.

7

이제 어플리케이션을 구동해보겠습니다. Application 클래스에서 마우스 오른쪽 버튼이나 Ctrl + Shift + R을 눌러 실행합니다.

8

위와 같이 어렵지 않게 서비스가 구동됩니다. 8080포트로 구동이 되었네요. http://localhost:8080/으로 접속해서 확인해 보면 HelloController.index()가 실행된것을 확인할 수 있습니다. 지금까지 제작한 예제를 첨부해두겠습니다. [HelloSpringBoot]

참고 : https://spring.io/guides/gs/spring-boot/