티스토리 뷰

반응형

1. MyBatis 로 질의하기

Spring Boot 에서 JdbcTemplate 으로 질의를 쉽게 할 수 있는건 사실이지만, 그래도 전통적으로 이용되어온 MyBatis 는 현재까지도 그 사용범위가 매우 넓고 익숙해지는데 큰 비용이 지출되지 않고 사용자 층 역시 매우 두터워서 프로젝트 시작 전에 따로 교육을 하지 않아도 되는 경우가 많습니다.

그래서 Spring Boot 에서는 최근까지도 MyBatis 에 대한 지원을 포함하지 않고 있다가 최근에 SPRING INITIALIZR 의 SQL 영역에 MyBatis 을 추가하였습니다. 예전에는 iBatis 와 Spring 에서 Spring Framework 통합 라이브러리를 만들었지만, 현재는 MyBatis 에서 제공하는 라이브러리를 이용해야만 Spring Framework 에 통합할 수 있습니다. 이제는 SPRING INITIALIZR 에서 그 라이브러리를 선택할 수 있기 때문에 사용하기 더 편리해졌습니다.

(1) MyBatis 추가

Spring Boot 프로젝트 생성 시 MyBatis 을 선택하면 아래와 같이 의존성이 추가됩니다.

<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>

(2) application 설정

application.yml (혹은 application.properties) 파일에 다음과 같은 내용을 추가합니다.

mybatis:
  mapper-locations: classpath*:mapper/**/*.xml

매우 간단한 내용인데, mapper/ 이하 디렉토리의 모든 XML 확장자를 가진 파일을 MyBatis 의 Mapper 파일로 추가하기 위한 설정입니다.

NOTE: MyBatis 에 대한 자세한 설명은 여기서는 하지 않습니다. http://www.mybatis.org/spring/ko/index.html 에서 공식 한글 문서를 찾아볼 수 있습니다.

(3) Mapper 파일 생성

Spring Boot 프로젝트의 src/main/resources/ 디렉토리에서 mapper/ 디렉토리를 생성하고 TestMapper.xml 파일을 아래와 같은 내용으로 생성해보겠습니다.

<?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="mapper.TestMapper">
	<select id="getName" parameterType="int" resultType="HashMap">
select name from test where seq = #{seq}
	</select>
</mapper>

Spring Boot JDBC 을 이용해 DB 에 질의(query)해보기 문서에서 사용했던 질의를 MyBatis 으로 그대로 옮겨왔습니다.

(4) DAO 에서 Mapper 호출하기

Spring Boot JDBC 을 이용해 DB 에 질의(query)해보기 문서에서 JdbcTemplate 으로 질의를 했던 TestDao 을 아래와 같이 수정해보겠습니다.

package com.example.demo.dao;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class TestDao {

	@Autowired
	private SqlSession sqlSession;

	public Map<String, Object> getName(int seq) throws Exception {
		return sqlSession.selectOne("mapper.TestMapper.getName", seq);
	}

}

이제 프로그램을 실행한 뒤 /hello 웹페이지를 호출하면 이전과 동일한 결과가 출력되는 것을 알 수 있습니다.

NOTE: 객체 연동, Annotation 방식 등을 이용하고자 한다면 MyBatis-Spring 공식 문서를 참고하세요.

2. JOOQ 로 질의하기

JOOQ (Java Object Oriented Querying) 은 SQL 문법의 질의를 프로그래밍 언어인 Java 로 생성할 수 있도록 API 을 제공하는 라이브러리입니다. JdbcTemplate 나 MyBatis 와 같이 질의를 직접 문자열로 프로그램에서 관리할 경우 오타 등의 문제가 있는 문장이라도 실제 실행을 했을 때가 되어서야 오류를 발견하는 문제가 있습니다. 하지만, 이렇게 Java 코드로 질의를 생성할 경우 자동 생성되는 메서드와 별칭 등으로 인해 Compile 단계에서 오류가 발견되거나 사용자의 오타로 인해 별칭이 잘못 지정되는 등의 문제가 발생하지 않을 가능성이 높습니다.

이러한 라이브러리를 DSL (Domain-Specific Language) 이라고 부릅니다(언어라고 하기엔 좀 그렇지만). 경쟁자로 QueryDSL 이 유명합니다. 여기서 JOOQ 에 대한 것을 모두 이야기 하기엔 무리가 있지만, 보통 복잡한 DB 구조에서 읽기 구조(통계 등)가 복잡한 경우 SQL 이, 쓰기 구조가 복잡한 경우 ORM (Object-Relational Mapping) 이 유리하다면, 중간 규모와 중간 복잡성을 가지는 DBMS 에 질의를 할 때에 DSL 이 적합하다고 알려져 있습니다.

Spring Boot 에서는 이러한 DSL 중에서 JOOQ 을 기본 지원합니다. 다만, JOOQ 는 오픈소스 버전과 유료 버전에 따라 기능 차이가 존재합니다. 가장 큰 차이는 오픈소스 버전에서는 유료 RDBMS 을 지원하지 않습니다.

(1) JOOQ 추가

Spring Boot 프로젝트 생성 시 JOOQ 을 선택하면 아래와 같이 의존성이 추가됩니다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jooq</artifactId>
</dependency>

(2) 코드 생성

MyBatis 은 Mapper 파일에 SQL 형태의 질의를 직접 작성하고, Java 의 표준 ORM 인 JPA 는 프로그램이 실행될 때 객체(Object)와 DB Schema 의 구조가 일치하는지 확인하는 형태이지만, JOOQ 은 미리 DB Schema 를 읽어 질의를 만들 수 있는 메서드의 집합이 정의된 Class 을 생성합니다. 기본적으로는 jooq-codegen 라이브러리를 이용해 별도의 설정파일을 만들어서 DB 의 구조를 읽어 코드를 생성합니다. 하지만, Maven 에서 코드를 생성할 수 있는 방법도 제공되고 있습니다.

NOTE: 현재 이 글을 적는 현재 Spring Boot 의 최신버전은 2.0.3-RELEASE 이고, spring-boot-starter-parent 에 포함된 JOOQ 는 3.10.7 버전이며, Java 는 10 버전까지 나온 상태입니다. 하지만, Java 10 에서 jooq-codegen 이 제대로 실행되려면 3.11 이상 버전이 되어야 하기 때문에 <jooq.version> 을 재정의해 3.11 이상을 이용하도록 해야 합니다.

Spring Boot JDBC 을 이용해 DB 에 질의(query)해보기 문서에서 사용했던 DB Table 구조를 jooq-codegen 으로 객체를 생성하기 위해서 Maven 설정 파일인 pom.xml 에 다음과 같은 코드를 추가합니다.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<jooq.version>3.11.0</jooq.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jooq</artifactId>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.jooq</groupId>
				<artifactId>jooq-codegen-maven</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>generate</goal>
						</goals>
					</execution>
				</executions>
				<dependencies>
				</dependencies>
				<configuration>
					<jdbc>
						<driver>com.mysql.jdbc.Driver</driver>
						<url>jdbc:mysql://localhost/dev</url>
						<user>zepinos</user>
						<password>testpassword</password>
					</jdbc>
					<generator>
						<database>
							<name>org.jooq.meta.mysql.MySQLDatabase</name>
							<includes>.*</includes>
							<excludes></excludes>
							<inputSchema>dev</inputSchema>
						</database>
						<target>
							<packageName>com.example.demo.jooq</packageName>
							<directory>target/generated-sources/jooq</directory>
						</target>
					</generator>
				</configuration>
			</plugin>
		</plugins>
	</build>


</project>

<plugin> 에서 jooq-codegen-maven 의 <goal> 을 generate 로 지정했기 때문에 Maven 실행 시 jooq-codegen:generate 을 실행하면 아래와 같이 DB 에서 정보를 수집하여 코드를 생성합니다. <configuration> 에 많은 정보를 입력할 수 있는데, MySQL (org.jooq.meta.mysql.MySQLDatabase) 에 접속해서 모든 테이블 (<includes> 의 .* 와 <excludes> 가 비어있음) 을 읽어 <packageName> 에 선언된 Package 정보로 소스를 생성하여 <directory> 에 선언된 위치 (target/generated-sources/jooq) 에 Java 파일을 생성하게 됩니다. 이 때 IntelliJ IDEA 등의 IDE 들은 <directory> 경로를 Source Folders 에 자동으로 포함시켜 줘서 개발 시 생성된 코드를 인식해서 사용할 수 있게 해줍니다.

Maven 으로 실행해보면 다음과 같이 로그 내용을 출력하면서 소스를 생성해줍니다.



DB 연결 정보를 application.yml 에서 불러오지 않는지 의문을 가지는 분들이 계실 겁니다. 아쉽게도 pom.xml 을 이용해 Maven 을 동작시키는 것은 Spring Boot 와는 별개의 행동입니다. 그렇기 때문에 설정 정보를 가져올 수 없습니다. 다만, pom.xml 에서 설정 정보를 등록한 뒤 application.yml 에서 읽어오는 방법은 존재합니다. 하지만, Spring Boot 소스 내에 정보가 그대로 노출되거나 외부 파일을 읽어서 처리하더라도 application.yml 보다 방법이 거추장스럽기 때문에 추천하지 않는 방법입니다.

(3) DAO 적용

이제 JOOQ 을 이용해서 DB 에 질의를 할 수 있도록 기존에 만들었던 TestDao 파일을 아래와 같이 수정해봅시다.

package com.example.demo.dao;

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

import java.util.Map;

import static com.example.demo.jooq.tables.Test.TEST;

@Component
public class TestDao {

	@Autowired
	private DSLContext create;

	public Map<String, Object> getName(int seq) throws Exception {
		return create.select(TEST.NAME)
				.from(TEST)
				.where(TEST.SEQ.equal(seq))
				.fetchOneMap();
	}

}

Spring Boot 에서 DSLContext 을 주입받아 질의를 DSL 형태로 생성할 수 있습니다.
그리고 codegen 에서 com.example.demo.jooq 패키지에 Java 코드를 생성했기 때문에 하위에 Test 테이블에 대한 객체가 존재합니다. 그 객체를 간편하게 이용하기 위해서 import static 으로 TEST 을 이용할 수 있도록 관례적으로 처리했습니다.

getName() 메서드에서 String 형태의 테이블명, 컬럼명을 쓰지 않고, 비교 역시 equal() 메서드를 이용하기 때문에 만약 오타가 있다면 컴파일(Complie) 단계에서 오류를 검출할 수 있습니다. Type-Safe 한 특징이고, 사람의 실수(human error)를 줄이는데 크게 기여합니다.

NOTE: JPA 등의 ORM 은 객체 그 자체를 프로그램 실행 시 DB 와 비교하지만, JOOQ 등의 DSL 라이브러리들은 codegen 등을 이용해 미리 생성해두기 때문에 Type-Safe 한 특징을 다른 방식으로 실현한다고 볼 수 있습니다.


반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함