์คํ๋ง ํ๋ก์ ํธ๋ฅผ ์งํํ ๋๋ง๋ค DB์์ ์ฐ๋์ ํ์์ ์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ค. ์ต๊ทผ์๋(๋๋์ด) JPA๋ฅผ ์ด์ฉํด ํ๋ก์ ํธ๋ฅผ ๋ง์ด ์งํํ์ง๋ง, ์ฌ์ ํ SQL์ ์ด์ฉํ ์ง๊ด์ ์ธ ๋งคํ๋ฐฉ์๋ ๋ง์ด ์ฌ์ฉ๋๊ณ ์๋ค. ๋จ์ํ ํ๋ก์ ํธ๋ผ๋ฉด SpringJdbc๋ฅผ ์ด์ฉํด์๋ ์ถฉ๋ถํ SQL์ ๊ตฌ์ฑํ ์ ์๋ค. ํ์ง๋ง ์ค๋ฌดํ๊ฒฝ์์๋ ๋ค์ํ ๊ฒฝ์ฐ์์๊ฐ ๋ฐ์ํ๊ณ ์ด์ ๋ฐ๋ฅธ SQL๋ ํจ๊ป ๋ณ๊ฒฝ๋์ด์ผ ํ๊ณ , ์ด๋ฌํ ๋ณต์กํ SQL์ด ํ์ํ ์ง์ ์์ MyBatis์ ๋์ SQL๊ด๋ฆฌ๋ฐฉ๋ฒ์ ํ๋ก์ ํธ์ ๋ณต์ก๋๋ฅผ ์ค์ด๋๋ฐ ํฐ ๋์์ด ๋๋ค.
์ด๋ฒ ํฌ์คํ ์์๋ spring boot๋ฅผ ์ด์ฉํ์ฌ ๊ฐ๋จํ๊ฒ MyBatis ์ํ ํ๋ก์ ํธ๋ฅผ ๊ตฌ์ฑํ๋ ๋ฒ์ ์์๋ณด๊ณ , ๋๋ฒ๊น ์ ์ํ SQL์ค์ ๋ฐ ํ์ฅํ๋ ๋ฐฉ๋ฒ์ ๊ณต์ ํ๊ณ ์ ํ๋ค.
MyBatis ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ spring boot์ฉ์ผ๋ก ์ ๊ณต๋๊ณ ์๋ค. ์ด๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ๋จํ๊ฒ mybatis์ค์ ์ด ๊ฐ๋ฅํ๋ค.
https://start.spring.io ์์ ์๋์ ๊ฐ์ด ํ๋ก์ ํธ ๋ผ๋๋ฅผ ๋ง๋ค์ด์ฃผ์
์์ฑ๋ ํ๋ก์ ํธ์์๋ ์๋์ ๊ฐ์ด 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>net.chndol.study</groupId>
<artifactId>mybatis-sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mybatis-sample</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>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
mybatis-spring-boot-starter
๋ฅผ ํตํด์ MyBatis ์์กด๊ด๊ณ๊ฐ ๋ค์ด๊ฐ๊ฒ ๋๋ฉฐ Spring ๊ด๋ จ ๊ธฐ๋ณธ์ค์ ์ด ๋ค์ด๊ฐ๊ฒ ๋๋ค.h2
๋ฅผ ์ด์ฉํ์๋ค. inmemory db๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋์ํ๋ ๊ฒฝ์ฐ์๋ง ๋์ํ๋ ํ ์คํธ์ฉ๋ DB์ด๋ค. ํ์ฌ ํ๋ก์ ํธ๋ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด ์ธ๋ถ ๋๋น ๋งคํ์์ด ๋์๊ฐ๋๋ก ์ค์ ํ์๋ค.
H2๋ ๊ธฐ์กด์ Database์ ์ ์ฌํ๋ฐฉ์์ผ๋ก DB์ค์ ์ด ๊ฐ๋ฅํ๋ค.
main/resources/application.properties
์ ์๋์ ๊ฐ์ด url, username, password๋ฅผ ์ค์ ํ์.
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
๊ทธ๋ฆฌ๊ณ ์์ ์ ์ฌ์ฉํ City
๋ผ๋ ํ
์ด๋ธ์ ๋ง๋ค์ด์ฃผ๋๋ก ํ์. ํด๋น ํ
์ด๋ธ์๋ ID
, NAME
(๋์๋ช
), COUNTRY
(๊ตญ๊ฐ), POPULATION
(์ธ๊ตฌ์) ์ปฌ๋ผ์ ๊ฐ์ง๋ค.
H2๋ ์ธ๋ฉ๋ชจ๋ฆฌ DB๋ก ์ ํ๋ฆฌ์ผ์ด์
์ด ๋์ํ๋ ๊ฒฝ์ฐ์๋ง DB๊ฐ ํจ๊ป ๋์ํ๋ค. Spring์ด ์์ํ๋ ์์ ์ scheme์ด ์์ฑ๋๋๋ก /src/main/resource/scheme.sql
์ ์์ฑํ์ฌ ์ฃผ๊ณ ์๋์ ๋ค์๊ณผ ๊ฐ์ด table ์์ฑ SQL๊ณผ ์์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ์ฌ ์ฃผ์.
DROP TABLE IF EXISTS CITY;
CREATE TABLE CITY (ID INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR, COUNTRY VARCHAR, POPULATION INT);
INSERT INTO CITY (NAME, COUNTRY, POPULATION) VALUES ('San Francisco', 'US', 10000);
INSERT INTO CITY (NAME, COUNTRY, POPULATION) VALUES ('์์ธ', 'KR', 20000);
INSERT INTO CITY (NAME, COUNTRY, POPULATION) VALUES ('ๆฑไบฌ', 'JP', 30000);
INSERT INTO CITY (NAME, COUNTRY, POPULATION) VALUES ('๋ถ์ฐ', 'KR', 40000);
scheme.sql์ ํ์ฉํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๊ธฐํ์ ๊ด๋ จ๋ ์์ธํ ์ค์ ์ ํด๋น ์คํ๋ง ๋ฌธ์ ์์ ํ์ธํ ์ ์๋ค.
mapper๋ฅผ ์ค์บํ ์ ์๋๋ก ์ค์ ํ์. SpringBoot Configuration ํ์ผ์๋จ์ ์๋์ ๊ฐ์ด ํ๋ก์ ํธ ํจํค์ง์ ๊ฒฝ๋ก๋ฅผ ๋ฃ์. ์ด๋ ๊ฒ ํ์ฌ ํ๋ก์ ํธ ํ์์ ์กด์ฌํ๋ Mapper
๋ค์ ๋น์ผ๋ก ๋ฑ๋กํ ์ ์๊ฒ ๋๋ค.
import org.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan(basePackages = "net.chandol.study.mybatissample")
public class MybatisSampleApplication {
....
main/resources/application.properties
์๋ ์ค์ ์ ์ถ๊ฐํ์. type์ ์ฝ๊ฒ ์ฐ๊ธฐ ์ํด์ model ํจํค์ง๋ฅผ type-aliaes์ ์ค์ ํ์๋ค. ๊ทธ๋ฆฌ๊ณ DB ๋ก๊น
์ ์ํด์ mapper ํจํค์ง๋ก๊น
์ TRACE๋ก ์ค์ ํ์๋ค.
# mybatis ๋งคํ type์ ์งง๊ฒ ์ฐ๊ธฐ ์ํ ์ค์
mybatis.type-aliases-package=net.chndol.study.mybatissample.model
# mapper ์ดํ๋ฅผ ๋ก๊น
์์น๋ก ์ค์ .
logging.level.net.chndol.study.mybatissample.mapper=TRACE
ํ๋ก์ ํธ๋ฅผ ์์ํ๋๋ฐ ์ด์ ๋๋ฉด ์ถฉ๋ถํ ์ธ๋งํ ๊ฒ์ด๋ค. spring boot starter๋ก ์ค์ ๋ mybatis๋ฅผ ์ฌ์ฉํ์์ผ๋ฏ๋ก, ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ํ ์ค์ ๊ฐ๋ค์ AutoConfigure๋ฅผ ํตํด์ ์๋์ผ๋ก ๋ฑ๋ก๋๋ค. mybatis์ ์ฐ๊ด๋ ์ข ๋ ์์ธํ ์ค์ ์ ์ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ๋ฉด ์น์ ํ๊ฒ ์๋ด๋์ด์๋ค.
์ด์ ์์์ ๋ง๋ค์ด๋์ CITY
ํ
์ด๋ธ๊ณผ ๋งคํ๋๋ model์ ์ค์ ํ์ฌ ๋ณด์
package net.chndol.study.mybatissample.model;
@Data
@Alias("city")
public class City {
private Long id;
private String name;
private String country;
private Long population;
public City() {
}
public City(String name, String country, Long population) {
this.name = name;
this.country = country;
this.population = population;
}
}
๊ทธ๋ฆฌ๊ณ ํด๋น Model๊ณผ ์ฐ๊ฒฐ๋ Mapper๋ฅผ ๊ตฌ์ฑํ์ฌ ์ฃผ์.
์ฐ์ mapper interface๋ ์๋์ ๊ฐ์ด ๊ตฌ์ฑํ์.
package net.chndol.study.mybatissample.repository;
import net.chndol.study.mybatissample.model.City;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CityMapper {
City selectCityById(Long id);
List<City> selectAllCity();
void insertCity(City city);
}
๊ทธ๋ฆฌ๊ณ SQL์ ๊ฐ์ง ํต์ฌ XML์ ์๋์ ๊ฐ์ด ๋ง๋ค์ด ์ฃผ์. ํด๋น ํ์ผ์ resource ํ์์ mapper ์ธํฐํ์ด์ค์ ๋์ผํ ํจํค์ง ๊ฒฝ๋ก ํ์์ ๋ฃ์ผ๋ฉด ๋๋ค.
<?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="net.chndol.study.mybatissample.repository.CityMapper">
<select id="selectCityById" resultType="city">
SELECT ID
,NAME
,COUNTRY
,POPULATION
FROM CITY
WHERE ID = #{id}
</select>
<select id="selectAllCity" resultType="city">
SELECT ID
,NAME
,COUNTRY
,POPULATION
FROM CITY
</select>
<insert id="insertCity">
INSERT INTO CITY (NAME, COUNTRY, POPULATION)
VALUES (#{name}, #{country}, #{population})
</insert>
</mapper>
MyBatis๋ ์ด์ธ์๋ mapper annotation์ ์ด์ฉํด SQL๋ฌธ์ ์ง์ ํ ์ ์๋ค. ํ์ง๋ง MyBatis๋ฅผ ์ด์ฉํ๋ ๊ฐ์ฅ ํฐ ์ด์ ์ค ํ๋์ธ XML์ ๊ธฐ๋ฐ์ผ๋ก SQL์ ๊น๋ํ๊ฒ ๊ด๋ฆฌํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ด์ง๋๊ฒ ๊ฐ์์ ๊ฐ์ธ์ ์ผ๋ก ์ ํธํ์ง๋ ์๋๋ค. ํด๋น ๋ฐฉ์์ ๋ํ ์ฝ๋๋ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ๋ผ.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก service์ test๋ฅผ ๋ง๋ค์ด๋ณด์.
@Service
@Transactional
public class CityService {
@Autowired
CityMapper cityMapper;
public City getCityById(Long id) {
return cityMapper.selectCityById(id);
}
public List<City> getAllCity() {
return cityMapper.selectAllCity();
}
public void addCity(City city) {
cityMapper.insertCity(city);
}
}
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CityServiceTest {
@Autowired
CityService service;
@Test
public void getCityById() {
City city = service.getCityById(1L);
log.info("city : {}", city);
}
@Test
public void getAllCity() {
List<City> cities = service.getAllCity();
log.info("cities : {}", cities);
}
@Test
public void addCities() {
service.addCity(new City("๋ด์", "๋ฏธ๊ตญ", 1_000_000L));
service.addCity(new City("๋ฐ๋", "์๊ตญ", 2_000_000L));
service.addCity(new City("ํ๋ฆฌ", "ํ๋์ค", 3_000_000L));
}
}
ํด๋น ํ ์คํธ๋ฅผ ์คํ์์ผ๋ณด๋ฉด ์๋์ ๊ฐ์ด ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์๋ค. SQL์ด ์ด๋ป๊ฒ ๋์ํ๋์ง, paramter๊ฐ ๋ฌด์์ธ์ง๋ฅผ ํ์ธํ ์ ์๋ค.
2018-07-30 12:21:44.371 DEBUG 25521 --- [ main] n.c.s.m.m.CityMapper.selectCityById : ==> Preparing: SELECT ID ,NAME ,COUNTRY ,POPULATION FROM CITY WHERE ID = ?
2018-07-30 12:21:44.390 DEBUG 25521 --- [ main] n.c.s.m.m.CityMapper.selectCityById : ==> Parameters: 1(Long)
2018-07-30 12:21:44.407 TRACE 25521 --- [ main] n.c.s.m.m.CityMapper.selectCityById : <== Columns: ID, NAME, COUNTRY, POPULATION
2018-07-30 12:21:44.408 TRACE 25521 --- [ main] n.c.s.m.m.CityMapper.selectCityById : <== Row: 1, San Francisco, US, 10000
2018-07-30 12:21:44.412 DEBUG 25521 --- [ main] n.c.s.m.m.CityMapper.selectCityById : <== Total: 1
2018-07-30 12:21:44.413 INFO 25521 --- [ main] n.c.s.m.service.CityServiceTest : city : City(id=1, name=San Francisco, country=US, population=10000)
2018-07-30 12:21:44.416 INFO 25521 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test:
MyBatis๋ฅผ ์ด์ฉํ ๊ฒฝ์ฐ ์ ํต์ (?)์ผ๋ก XML์ ํตํด์ SQL๋ฌธ์ ์ค์ ํ๋ค. ํ์ง๋ง Java์ฝ๋์ xmlํ์ผ์ด 1:1๋ก ๋งคํ๋จ์๋ ๋ถ๊ตฌํ๊ณ , ํด๋๊ฐ ์๋ก ๋ค๋ฅธ๊ณณ์ ์์นํด ๋์ ์๋ก ํ์ธํ๊ธฐ์ ๋ถํธํ๋ค. ์ด์๊ฐ ๋ฐ์ํ ๋๋ง๋ค . mapper interface ์ ๊ฐ์ ํด๋์์ ์์ผ๋ฉด ๋ณด๋ค ๋ ๊ด๋ฆฌํ๊ธฐ ํธ๋ฆฌํ ๊ฒ์ด๋ค.
ํ์ง๋ง xmlํ์ผ์ java์ ๊ฐ์ ํด๋๋ก ์ฎ๊ธฐ๋๋ผ๋ ๋ฉ์ด๋ธ ๋น๋ํ์์ javaํ์ผ์ ์ ์ธํ ๋ค๋ฅธ ํ์ผ๋ค์ ์์ธ๋ก ์ค์ ๋์ด ๋น๋๊ฒฝ๋ก๋ก ํ์ผ์ ์ฎ๊ธฐ์ง ์๋๋ค. ๋๋ฌธ์ ๋น๋์ xmlํ์ผ๋ ๊ฐ์ด ๋ณต์ฌ๋ ์ ์๋๋ก ๋ฉ์ด๋ธ ์ค์ ์ด ํ์ํ๋ค. ์๋์ฒ๋ผ build ํ๊ทธ ํ์์ resources ์ค์ ์ ์ถ๊ฐํ์ฌ ์ฃผ์.
<projec>
...
<build>
<resources>
<resource>
<filtering>false</filtering>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
...
</build>
</project>
์ด์ ๋ถํฐ xml๊ณผ interface๋ฅผ ํ๋์ ํด๋์์ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค!
์ง๊ธ๊น์ง ํด์ ์ ๋ฒ ์ธ๋งํ ์ค์ ๋ค์ ๋ง๋ค์ด๋์๋ค. ๊ฐ์ธ์ ์ผ๋ก mybatis๋ฅผ ์ด์ฉํ ๋ ๋ถํธํ๋ ์ ์ ๋์ SQL๋ก ๋ณํํ๊ณ ๋ ๋ค์์ ์์ฑ๋ SQL๊ณผ ํ๋ผ๋ฏธํฐ์ ๋งคํ๋๋ ๋ถ๋ถ์ด "?"๋ก ์ ๋ถ ์นํ๋๋ค๋ ์ ์ด์๋ค. ํด๋น ๊ฐ๋ค์ด ์ด๋ค ์๋ฏธ์๋์ง๋ฅผ ์๊ธฐ ์ํด์ ๋ค์ ํ ๋ฒ xmlํ์ผ์ ํตํด ํ์ธํ์ฌ์ผ ํ๋ค.
์ด๋ฌํ ๋จ์ ์ ๋ณด์ํ๊ธฐ ์ํด์ ์ถ๋ ฅ๋๋ SQL์ ? ๋ค์ ๋งคํํ๋ผ๋ฏธํฐ์ ์ด๋ฆ์ ์ฝ๋ฉํธ๋ก ๋จ๊ฒจ๋ณด๋๋ก ํ์. ์ด๋ ๊ฒ ํ ๊ฒฝ์ฐ ๋ฌธ์ ๊ฐ ์๊ธฐ๋๋ผ๋ ๋น ๋ฅด๊ฒ ๋๋ฒ๊น ์ด ๊ฐ๋ฅํ ๊ฒ์ด๋ค.
MyBatis์ XML์์ ์ค์ ํ ๋์ SQL๋ฌธ์ XMLLanguageDriver
๋ฅผ ํตํด์ JDBC์์ฌ์ฉ๋ SQL๋ฌธ์ผ๋ก ๋ณ๊ฒฝ๋๋ค. ์ด๋ฌํ ๊ณผ์ ์ค๊ฐ์ SQL๋ฌธ์ ๊บผ๋ด ์ฝ๋ฉํธ๋ฅผ ์ถ๊ฐํ์ฌ ๋ณด๋๋ก ํ์.
package net.chandol.study.mybatissample.config;
@Slf4j
public class EnhanceMybatisLanguageDriver extends XMLLanguageDriver {
public EnhanceMybatisLanguageDriver() {
}
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
addDebuggingComment(boundSql);
return super.createParameterHandler(mappedStatement, parameterObject, boundSql);
}
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
return super.createSqlSource(configuration, script, parameterType);
}
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
return super.createSqlSource(configuration, script, parameterType);
}
@SneakyThrows
private void addDebuggingComment(BoundSql boundSql) {
Field sqlField = BoundSql.class.getDeclaredField("sql");
sqlField.setAccessible(true);
String sql = (String) sqlField.get(boundSql);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
sql = addParameterComment(sql, parameterMappings);
sqlField.set(boundSql, sql);
}
private String addParameterComment(String sql, List<ParameterMapping> parameters) {
StringBuilder sqlInternalStringBuilder = new StringBuilder(sql);
int paramReverseIndex = parameters.size() - 1;
for (int idx = sql.length() - 1; idx > 0; idx--) {
char c = sql.charAt(idx);
if (c == '?') {
String commentedString = toCommentString(parameters.get(paramReverseIndex).getProperty());
sqlInternalStringBuilder.insert(idx + 1, commentedString);
paramReverseIndex = paramReverseIndex - 1;
}
}
return sqlInternalStringBuilder.toString();
}
private String toCommentString(String comment) {
return " /*" + comment + "*/ ";
}
}
XMLLanguageDriver
๋ฅผ ์๋กญ๊ฒ ํ์ฅํ์ฌ ์ฝ๋ฉํธ๋ฅผ ์ถ๊ฐํ๋ ๋ก์ง์ ๊ฐ์ง addDebuggingComment
๋ฉ์๋๋ฅผ ๋งค๋ฒ ํธ์ถํ๋๋ก ํ์๋ค. ํด๋น ๋ฉ์๋๋ ๋ฆฌํ๋ ์
์ ์ด์ฉํด ๋ด๋ถ์ SQL๋ฌธ์ ๋ฝ์๋ด์๊ณ , addParameterComment
์์๋ "?"์ ์์น๋ฅผ ์ฐพ์๋ด์ด paramter์ ID๊ฐ์ ๋งคํํ๋๋ก ๋ง๋ค์๋ค.
์ด๋ ๊ฒ ์๋กญ๊ฒ ํ์ฅํ LanguageDriver๋ ์๋์ ๊ฐ์ด application.properties๋ฅผ ํตํด์ ์ค์ ํ ์ ์๋ค.
mybatis.configuration.default-scripting-language=net.chandol.study.mybatissample.config.EnhanceMybatisLanguageDriver
์ด์ ํ ์คํธ๋ฅผ ๋๋ ค๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด "?" ๋ค์ ์ฝ๋ฉํธ๊ฐ ์ถ๊ฐ๋์ด ์ด๋ค ํ๋ผ๋ฏธํฐ์ ๋งคํ๋์๋์ง๋ฅผ ํ์ธํ ์ ์์ ๊ฒ์ด๋ค.
2018-07-30 23:17:06.718 DEBUG 29739 --- [ main] n.c.s.m.mapper.CityMapper.insertCity : ==> Preparing: INSERT INTO CITY (NAME, COUNTRY, POPULATION) VALUES (? /*name*/ , ? /*country*/ , ? /*population*/ )
2018-07-30 23:17:06.738 DEBUG 29739 --- [ main] n.c.s.m.mapper.CityMapper.insertCity : ==> Parameters: ๋ด์(String), ๋ฏธ๊ตญ(String), 1000000(Long)
2018-07-30 23:17:06.738 DEBUG 29739 --- [ main] n.c.s.m.mapper.CityMapper.insertCity : <== Updates: 1
2018-07-30 23:17:06.738 DEBUG 29739 --- [ main] n.c.s.m.mapper.CityMapper.insertCity : ==> Preparing: INSERT INTO CITY (NAME, COUNTRY, POPULATION) VALUES (? /*name*/ , ? /*country*/ , ? /*population*/ )
2018-07-30 23:17:06.739 DEBUG 29739 --- [ main] n.c.s.m.mapper.CityMapper.insertCity : ==> Parameters: ๋ฐ๋(String), ์๊ตญ(String), 2000000(Long)
2018-07-30 23:17:06.739 DEBUG 29739 --- [ main] n.c.s.m.mapper.CityMapper.insertCity : <== Updates: 1
์์ ๋์ํ๋, ์ค์ ๋ ์ฝ๋๋ ์๋ github ์ ์ฅ์์์ ์ฐพ์๋ณผ ์ ์๋ค.