마이바티스 연동을 위해서는 먼저 라이브러리를 추가해줘야 한다.
필요한 라이브러리는 Oracle Driver와 MyBatis Framework이다.

build.gradle
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4'
runtimeOnly 'com.oracle.database.jdbc:ojdbc11'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
Spring Initializr를 통해 두가지 Dependencies를 추가하고 EXPLORE 버튼을 클릭한다.
생성된 설정 추가 코드를 복사해서 build.gradle 파일에 붙여넣는다.

Java 프로젝트에서 JAR 파일을 라이브러리로 추가하는 방식은 다음과 같이 두가지가 있다.
작은 프로젝트와 같이 인터넷 연결 없이 단순히 특정 JAR 파일만 사용하는 경우, JAR 파일을 직접 추가해도 충분하다.
큰 프로젝트와 같이 의존성이 복잡하거나 자주 업데이트되는 경우 Gradle을 사용하는 것이 훨씬 효율적이다.
절차
장점
단점
절차
장점
단점

단순 문자열 방식 (build.gradle)
runtimeOnly 'com.oracle.database.jdbc:ojdbc11'
키-값 방식 (build.gradle)
runtimeOnly group: 'com.oracle.database.jdbc', name: 'ojdbc11', version: '23.3.0.23.09'
build.gradle에 의존성을 선언하는 형식은 위와 같이 두가지가 있다.
위 표와 같이 간단한 설정의 경우 단순 문자열 방식을 사용하는 것이 더 깔끔하다.
classifier, configuration과 같이 세부적인 설정이 필요하다면 키-값 방식을 사용한다.
문자열 방식에서 버전을 생략하거나 명시하면, Gradle의 의존성 해결 전략에 따라 버전이 결정된다. 키-값 방식에서는 명시적으로 버전을 지정하기 때문에 버전 충돌 위험이 줄어든다.

implementation: 프로젝트 주요 기능 등 일반적인 프로젝트 코드에 필요한 라이브러리testImplementation: 테스트 작성/실행에만 필요한 라이브러리 (ex. JUnit, Mockito, AssertJ 등 테스트 전용 라이브러리)runtimeOnly: 런타임 중에만 동적으로 로드될 라이브러리 (ex. JDBC 드라이버)testRuntimeOnly: 테스트 실행 중에만 필요한 런타임 라이브러리
실제로 implementation으로 정의한 spring-boot-starter-web의 경우 compile, runtime, test, testRuntime 모두의 ClassPath에 포함되어 있으나, testRuntimeOnly로 정의해놓은 junit 라이브러리는 testRuntime의 ClassPath에만 포함돼있는 것을 확인할 수 있다.

Gradle 예시 (build.gradle)
dependencies {
implementation 'org.apache.commons:commons-lang3:3.12.0'
}
Maven 예시 (pom.xml)
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
<scope>compile</scope>
</dependency>
Ivy 예시 (ivy.xml)
<dependency org="org.apache.commons" name="commons-lang3" rev="3.12.0" conf="compile->default"/>
컴파일 타임
- 코드를 실행하기 전에 발생
- 코드의 정확성과 참조를 검증하기 위해 필요한 라이브러리가 필요
런타임
- 프로그램 실행 중 발생
- 동적으로 리소스를 로드하거나, 런타임에만 필요한 작업을 수행
컴파일 타임 참조
런타임 동적 로딩
컴파일러는 동적 로딩에서 참조하는 클래스나 메서드를 확인하지 않기 때문에, 컴파일 시점에는 해당 라이브러리의 존재 여부를 검증하지 않는다.
다만, 런타임 시점에 해당 라이브러리가 없으면 ClassNotFoundException 또는 NoClassDefFoundError와 같은 오류가 발생할 수 있다.
컴파일러는 코드에서 직접 참조한 클래스나 메서드만 확인한다.
동적 로딩은 보통 리플렉션(Reflection)이나 Class.forName과 같은 메서드를 통해 런타임에 실행되기 때문에, 컴파일 시점에는 코드가 해당 라이브러리를 명시적으로 참조하지 않는다.
public class Example {
public static void main(String[] args) {
try {
// 클래스 이름을 문자열로 명시
Class<?> driverClass = Class.forName("com.mysql.cj.jdbc.Driver");
System.out.println("Driver loaded: " + driverClass.getName());
} catch (ClassNotFoundException e) {
System.err.println("Driver not found!");
}
}
}
위 코드는 컴파일러가 com.mysql.cj.jdbc.Driver 클래스의 존재를 확인하지 않으며, 런타임에 Class.forName이 해당 클래스를 동적으로 로드한다.
런타임에 JVM은 Class.forName 또는 리플렉션을 호출할 때 지정된 클래스가 클래스패스(Classpath)에 있는지 확인하며, 클래스가 없으면 ClassNotFoundException 예외가 발생한다.
장점
단점
dependencies {
runtimeOnly 'mysql:mysql-connector-java:8.0.34'
}
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.err.println("Required class not found!");
}
그 다음 환경 설정을 추가해줘야 한다.
가독성을 위해 application.properties 파일을 application.yml 파일로 변경한다.
application.yml
# server port 설정
server:
port: 8080 # 설정하지 않으면 기본으로 8080. 포트 번호 변경이 필요할 경우 사용
spring:
# application
application:
name: test
# database 접속 정보
datasource:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@localhost:1521/xe
username: c##dev_test
password: dev_test
# mybatis 관련 파일 경로
# mapper-location: mapper 파일(쿼리문 파일) 경로
# config-location: mybatis 설정 파일 경로
mybatis:
mapper-locations: classpath:mapper/*.xml
config-location: classpath:mybatis-config.xml
그 후 위와 같이 datasource, mybatis 관련 설정을 추가한다.
properties 파일과 YAML 파일 방식으로 환경 설정을 정의할 수 있다.
application.properties
spring.datasource.url=jdbc:h2:dev
spring.datasource.username=SA
spring.datasource.password=password
기본적으로 스프링부트는 application.properties 파일로 환경 설정을 수행할 수 있다.
application.properties 파일은 키-값 포맷을 가진다.
여기서 각각의 설정정보는 단일 구성이므로 키에 동일한 접두사를 사용하여 계층적 데이터를 표현해야 한다.
application.yml
spring:
datasource:
password: password
url: jdbc:h2:dev
username: SA
스프링부트에서 YAML 기반의 환경설정 파일도 사용할 수 있다.
YAML은 계층적 설정 데이터를 표현하기에 편리한 포맷이다.
properties 파일과 동일한 설정을 yml 파일에서는 위와 같이 정의할 수 있다.
중복된 접두사를 반복하지 않아도 되기 때문에 properties 형식보다 가독성이 좋다.
이외 자세한 설명은 다음 페이지에서 확인할 수 있다.
https://www.baeldung.com/spring-boot-yaml-vs-properties
app.name=MyApp
app.description=${app.name} is a Spring Boot application
또한 ${} 구문을 사용하면 통해 다른 키, 시스템 속성, 환경변수의 내용을 참조할 수 있다.
application.yml에 설정한 config-location의 위치와 설정파일명에 맞춰 마이바티스 설정파일을 설정해준다.
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 카멜 케이스 VO 매핑 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<typeAlias type="com.example.test.visualization.dao.Clsf" alias="clsf" />
</typeAliases>
</configuration>
이 설정파일에는 언더스코어와 카멜케이싱을 매핑시켜주는 설정과 사용할 타입의 별칭을 등록해준다.
mybatis:
mapper-locations: classpath:mapper/*.xml
config-location: classpath:mybatis-config.xml
application.yml에 위와 같이 마이바티스 설정을 추가해준 바 있다.
여기서 classpath는 무엇인가?
classpath는 Java 애플리케이션이 클래스 파일, 리소스 파일, 설정 파일 등을 찾는 경로이다.
그러면 이 classpath의 위치는 어떻게 되는가?
알아둬야 할 것은 빌드 전 클래스패스는 src/main/resources이고, 빌드 후 클래스패스는 Maven 프로젝트에서는 target/classes, Gradle 프로젝트는 build/resources라는 것이다.
빌드 전에는 보통 Maven이나 Gradle에서는 src/main/resources 또는 src/test/resources/main 폴더가 애플리케이션 리소스를 위한 기본 경로로 지정된다.
Maven 빌드 전 디렉토리 구조
project-name/
├── pom.xml # 프로젝트 설정 파일
├── src/
│ ├── main/
│ │ ├── java/ # Java 소스 파일들
│ │ └── resources/ # 리소스 파일들 (예: application.properties, XML 파일 등)
│ └── test/
│ ├── java/ # 테스트 소스 파일들
│ └── resources/ # 테스트 리소스 파일들
└── target/ # 빌드 후 결과물 디렉토리
Gradle 빌드 전 디렉토리 구조
project-name/
├── build.gradle # 프로젝트 설정 파일
├── src/
│ ├── main/
│ │ ├── java/ # Java 소스 파일들
│ │ └── resources/ # 리소스 파일들 (예: application.properties, XML 파일 등)
│ └── test/
│ ├── java/ # 테스트 소스 파일들
│ └── resources/ # 테스트 리소스 파일들
└── build/ # 빌드 후 결과물 디렉토리
src/main/resources/
├── mapper/
│ ├── UserMapper.xml
│ ├── ProductMapper.xml
├── mybatis-config.xml
resources 폴더 하위 마이바티스 관련 폴더 구조는 위와 같다.
Maven이나 Gradle 프로젝트에서 src/main/resources 폴더는 리소스 파일을 관리하는 기본 디렉토리이다.
빌드 전 단계에서는 src/main/resources 자체가 클래스패스처럼 동작한다.
그러므로 아래 설정 파일을 찾기 위해서는
classpath:mybatis-config.xml
아래 경로를 통해 설정 파일을 확인할 수 있다.
src/main/resources/mybatis-config.xml
Maven 빌드 후 디렉토리 구조
project-name/
├── pom.xml
├── src/
│ ├── main/
│ ├── test/
└── target/ # Maven 빌드 출력 디렉터리
├── classes/ # 컴파일된 클래스 파일들이 저장되는 디렉토리
│ ├── com/
│ │ └── example/
│ │ └── Application.class
│ ├── application.yml # 복사된 리소스 파일들
├── test-classes/ # 테스트 클래스파일
├── dependency/ # 의존성 JAR 파일 (옵션)
├── logs/ # 로그 파일 (빌드 도중 생성된 경우)
└── my-project-0.0.1-SNAPSHOT.jar # 최종적으로 빌드된 JAR 파일
target/classes/: src/main/java에서 컴파일된 클래스 파일들이 위치
target/classes/: src/main/resources에서 복사된 리소스 파일들이 위치
target/my-project-1.0-SNAPSHOT.jar: 최종적으로 빌드된 JAR 파일
Gradle 빌드 후 디렉토리 구조
project-name/
├── build.gradle
├── settings.gradle
├── src/
│ ├── main/
│ ├── test/
└── build/ # Gradle 빌드 출력 디렉터리
├── classes/ # 컴파일된 클래스 파일들이 저장되는 디렉토리
│ ├── java/
│ │ ├── main/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── Application.class
│ │ └── test/ # 테스트 클래스 파일
├── resources/ # 리소스 파일들이 저장되는 디렉토리
│ ├── main/
│ │ └── application.yml # 복사된 리소스 파일들
│ └── test/ # 복사된 테스트 리소스 파일
├── libs/ # 빌드된 JAR 파일이 위치하는 디렉토리
│ └── my-project-0.0.1-SNAPSHOT.jar # 최종적으로 빌드된 JAR 파일
├── reports/ # 테스트 보고서
└── tmp/ # 임시 파일 (빌드 도중 생성)
build/classes/: src/main/java에서 컴파일된 클래스 파일들이 위치
build/resources/main/: src/main/resources에서 복사된 리소스 파일들이 위치
build/libs/: 최종적으로 빌드된 JAR 파일
target/classes/
├── mapper/
│ └── UserMapper.xml
└── mybatis-config.xml
Maven 프로젝트를 빌드 하면 src/main/resources 폴더 하위 파일들을 target/classes 경로로 복사한다.
Maven 프로젝트에서 실행 시 JVM은 target/classes를 클래스패스로 인식하고 이 경로를 기준으로 파일을 검색한다.
그러므로 JVM은 아래 설정을 확인하면
classpath:mybatis-config.xml
아래 경로를 통해 파일을 검색하게 된다.
target/classes/mybatis-config.xml
왜 이렇게 동작하는 것일까?
소스 파일(src/main/resources)과 빌드 산출물(target/resources)을 분리하여 원본 파일을 안전하게 보호하고, 빌드된 파일을 실행 가능한 상태로 준비하기 위함이다.
JVM은 실행시 필요한 모든 파일은 클래스패스(target/classes)에 있어야 하며, 이를 통해 리소스와 클래스 파일을 통합적으로 관리한다.
Gradle 프로젝트를 빌드 하면 src/main/resources 폴더 하위 파일들을 build/resources/main 경로로 복사한다.
Gradle 프로젝트에서 실행 시 JVM은 build/resources/main을 클래스패스로 인식하고 이 경로를 기준으로 파일을 검색한다.
그러므로 JVM은 아래 설정을 확인하면
classpath:mybatis-config.xml
아래 경로를 통해 파일을 검색하게 된다.
build/resources/mybatis-config.xml

Maven과 Gradle 프로젝트의 빌드 전, 후 경로를 비교하면 위와 같다.
이제 mapper 경로에 mapper 파일을 생성해준다.
visualization-mapper.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="com.example.test.visualization.repository.VisRepository">
<select id="selectClsfList" resultType="clsf">
select
*
from clsf
</select>
</mapper>
매퍼 인터페이스를 등록하는 방식은 많지만, mapper xml에 namespace를 등록하여 repository와 연결해주었다.
@MapperScan: 지정된 패키지 내의 모든 매퍼 인터페이스를 자동으로 스캔하여 등록
@MapperScan("com.example.test.visualization.repository")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Mapper: 개별 인터페이스에 @Mapper 어노테이션을 추가하여 MyBatis가 이 인터페이스를 매퍼로 인식하게 함
@Mapper
public interface VisRepository {
List<Clsf> selectClsfList();
}
MapperScannerConfigurer를 사용하여 매퍼 인터페이스를 수동으로 등록할 수 있으며, 이 방법은 주로 Spring XML 설정에서 사용된다.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.test.visualization.repository" />
</bean>
매퍼 XML 파일 내에 namespace를 명시하여, 해당 XML이 특정 매퍼 인터페이스와 연결되도록 할 수 있다.
<mapper namespace="com.example.test.visualization.repository.VisRepository">
<select id="selectClsfList" resultType="com.example.test.visualization.vo.Clsf">
SELECT clsf_id FROM clsf WHERE rownum = 1
</select>
</mapper>
이 방식은 자동 스캔이나 수동 등록을 사용하지 않고, 매퍼 인터페이스와 XML 파일을 namespace를 통해 연결하는 방식이다. MyBatis는 이 XML 파일을 로드하면서 namespace 값에 해당하는 매퍼 인터페이스와 자동으로 연결한다.
그리고 사용한 MVC 패턴은 다음과 같다.
간단한 테스트가 목적이므로 편의상 ServiceImpl을 생성하지 않았다.
Controller
@RestController
@RequestMapping("/vis")
@RequiredArgsConstructor
public class VisController {
@Autowired
private VisService visService;
@GetMapping("/hello")
public String test() {
List<Clsf> s = visService.retrieveClsfList();
return "Hello, world!";
}
}
Service
@Service
public class VisService {
@Autowired
private VisRepository visRepo;
public List<Clsf> retrieveClsfList() {
return visRepo.selectClsfList();
}
}
Repository
@Repository
public interface VisRepository {
List<Clsf> selectClsfList();
}
Dao
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Clsf {
private String clsfId;
private String clsfNm;
private String upClsfId;
private String clsfLv;
}