프로젝트를 시작하기에 앞서 어떤 언어를 사용할지 어떤 프레임웍, 라이브러리를 사용할지 고민하게 됩니다. 저도 지금까지 이런 고민을 할때마다 큰 규모의 프로젝트는 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 버전을 사용하였습니다.
프로젝트의 루트에 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]