Spring Boot + MyBatis ์ค์ ๋ฐฉ๋ฒ(HikariCP, H2)
๐ ์์
1. ์คํ๋ง ๋ถํธ ํ๋ก์ ํธ ์์ฑ
2. ์ด๊ธฐํ ์คํฌ๋ฆฝํธ ์ค์ (schema.sql, data.sql)
3. DBCP/DataSource ์ค์ (HikariCP)
4. MyBatis ์ค์ (@MapperScan, XML ์์น, CamelCase, Alias, ๋ก๊ทธ๋ ๋ฒจ)
5. Model, Mapper ์์ฑ
6. ํ ์คํธ
1. ์คํ๋ง ๋ถํธ ํ๋ก์ ํธ ์์ฑ
MyBatis๋ฅผ ์ด์ฉํ DB ์ฐ๋์ ์ํ ์ ์คํ๋ง ๋ถํธ ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ค.
์คํ๋ง ๋ถํธ ํ๋ก์ ํธ๋ IDE๋ฅผ ์ด์ฉํ๋์ง, spring initializr(start.spring.io)๋ฅผ ์ด์ฉํด ์์ฑํ ์ ์๋ค.
์๋ฐ๋ 8๋ก, ์์กด์ฑ์ Spring Web, Spring Data JDBC, Spring Boot DevTools, MyBatis, H2, Lombok์ ๋ฃ์ด์ฃผ์๋ค.
๐ Spring Boot DevTools
์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ๋ ์ฌ๋ฌ ํธ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ.
์ฌ๊ธฐ์๋ H2 ์ฝ์์ ์ฌ์ฉํ๊ธฐ ์ํด ์ถ๊ฐํ์๋ค.
H2 ์ฝ์์ ์ฌ์ฉํ๊ธฐ ์ํ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ application.properties์ ๋ค์ ํ๋กํผํฐ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด๋ค.
spring.h2.console.enabled=true (์ดํ๋ฆฌ์ผ์ด์ ์ฌ์์ ํ์)
๐ H2 Database
์์ ๊ฐ์ด ์คํ๋ง ๋ถํธ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค ๋ H2 Database ์์กด์ฑ์ ์ถ๊ฐํ๋ฉด ๊ฐ๋จํ ์ฌ์ฉํ ์ ์๋ค.
์คํ๋ง ๋ถํธ๊ฐ ์ง์ํ๋ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ H2, HSQL, Derby๊ฐ ์๋ค.
์ด ์ค H2๊ฐ ์์ฃผ ์ฌ์ฉ๋๊ณ ์ถ์ฒ๋๋ ์ด์ ๋ ์ฝ์์ด ์ ๊ณต๋๊ธฐ ๋๋ฌธ์ด๋ค.
๐ Spring Data JDBC
Spring JDBC์ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ํด๋์คํจ์ค์ ์์ผ๋ฉด(= ์์กด์ฑ ์ค์ ์ด ๋์ด์์ผ๋ฉด) ์คํ๋ง ๋ถํธ๊ฐ DataSource, JdbcTemplate ๋น์ ์๋์ผ๋ก ์ค์ ํด์ค๋ค.
2. ์ด๊ธฐํ ์คํฌ๋ฆฝํธ ์ค์
/src/main/resources์ schema.sql, data.sql์ ์์ฑํ์ฌ ๋ณธ ์์ ์์ ์ฌ์ฉํ ํ
์ด๋ธ์ ์๋์ผ๋ก ์์ฑํ๊ณ ๋ฐ์ดํฐ๋ฅผ insertํ๋๋ก ํ๋ค.
schema.sql
DROP TABLE IF EXISTS Products;
CREATE TABLE Products
(
prod_id IDENTITY PRIMARY KEY,
prod_name VARCHAR(255) NOT NULL,
prod_price INT NOT NULL
);
data.sql
INSERT INTO Products (prod_name, prod_price) values ('๋ฒ ๋ฒ ์ฒ ๋ฌผํฐ์', 2700);
INSERT INTO Products (prod_name, prod_price) values ('์ฌ๋ฆ ํ ํผ', 35180);
INSERT INTO Products (prod_name, prod_price) values ('ํ์ดํฌ ์ญ์ค', 860);
INSERT INTO Products (prod_name, prod_price) values ('์ฐ์ฐ', 2900);
๐ ์คํ๋ง ๋ถํธ๋ DDL ์คํฌ๋ฆฝํธ์ DML ์คํฌ๋ฆฝํธ๋ฅผ ์ค์ ํ์ฌ ์คํค๋ง๋ฅผ ์๋์ผ๋ก ์์ฑํ๊ณ ์ด๊ธฐํํ ์ ์๋ค(DML ์คํฌ๋ฆฝํธ). ๊ธฐ๋ณธ์ ์ผ๋ก DDL ์คํฌ๋ฆฝํธ๋ schema.sql, DML ์คํฌ๋ฆฝํธ๋ data.sql์ ๋ก๋ํ๋ค.
์ฐธ๊ณ : docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-initialize-a-database-using-spring-jdbc
์ฐธ๊ณ - DataSource์ JdbcTemplate ๋น ์๋ ์ค์ ํ์ธ
์คํ๋ง ๋ถํธ ํ๋ก์ ํธ์ ์์กด์ฑ์ Spring JDBC์ H2์ ๊ฐ์ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ค์ ๋์ด ์์ผ๋ฉด AutoConfiguration์ ์ํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋์ ํ์ํ DataSource, JdbcTemplate ๋น์ด ์๋์ผ๋ก ์ฃผ์ ๋๋ฏ๋ก ํน์ datasource๋ฅผ ์ค์ ํ์ง ์์๋ ํด๋น ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฆ์ ์ฐ๋ํ ์ ์๋ค.
์ด๋ฅผ ์ง์ ํ์ธํด๋ณด๊ธฐ ์ํด ApplicationRunner๋ฅผ ์์ฑํ์ฌ @Autowired๋ก DataSource ๋น์ ์ฃผ์ ๋ฐ์ Connection์ ๋ฉํ ์ ๋ณด๋ฅผ ์ฐ์ด๋ณด์. ์ด ๋ถ๋ถ์ ์ฐธ๊ณ ์ฉ์ด๋ฏ๋ก ์คํตํด๋ ๋ฌด๋ฐฉํ๋ค.
@Slf4j
@Component
public class TestRunner implements ApplicationRunner {
@Autowired
DataSource dataSource;
@Override
public void run(ApplicationArguments args) throws Exception {
Connection connection = dataSource.getConnection();
log.info("Url: " + connection.getMetaData().getURL());
log.info("UserName: " + connection.getMetaData().getUserName());
}
}
ApplicationRunner์๋ @Component๋ฅผ ๋ถ์ฌ์ค์ผ ํจ์ ์ ์ํ์.
๐ฅ ์คํ ๊ฒฐ๊ณผ
...
INFO --- [ main] TestRunner : Url: jdbc:h2:mem:testdb
INFO --- [ main] TestRunner : UserName: SA
...
๐ ์คํ๋ง ๋ถํธ ์ธ๋ฉ๋ชจ๋ฆฌ DB ์ด๋ฆ ์ค์
์คํ๋ง ๋ถํธ 2.3๋ถํฐ ์ธ๋ฉ๋ชจ๋ฆฌ DB๊ฐ ๋งค๋ฒ ์๋ก์ด ์ด๋ฆ์ผ๋ก ๋ง๋ค์ด ์ง๋๋ก ๋ณ๊ฒฝ๋์๋ค. ์ด์ ๋ฒ์ ์์์ ๊ฐ์ด testdb๋ก ๊ณ ์ ํ๋ ค๋ฉด ๋ค์ ์ค์ ์ application.properties์ ์ถ๊ฐํ๋ฉด ๋๋ค.
spring.datasource.generate-unique-name=false
H2 ์ฝ์์ ํตํด DB ์ด๊ธฐํ ์คํฌ๋ฆฝํธ๊ฐ ์ ๋๋ก ์ ์ฉ๋์๋์ง ํ์ธํด๋ณด์.
๋ธ๋ผ์ฐ์ ์์ http://localhost:8080/h2-console์ ์ ์ํ๋ค.
์์ ๊ฐ์ด JDBC URL ์
๋ ฅ ํ Connect ํด๋ฆญ
์ถ๊ฐ๋ก JdbcTemplate๋ ์ฃผ์ ๋ฐ์ ์ฌ์ฉํด๋ด์ผ๋ก์จ ๋น์ด ์๋ ์ค์ ๋์๋์ง ํ์ธํด๋ณด์.
TestRunner.java
@Slf4j
@Component
public class TestRunner implements ApplicationRunner {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public void run(ApplicationArguments args) throws Exception {
// DataSource
Connection connection = dataSource.getConnection();
log.info("Url: " + connection.getMetaData().getURL());
log.info("UserName: " + connection.getMetaData().getUserName());
// JdbcTemplate
jdbcTemplate.execute("INSERT INTO Products (prod_name, prod_price) values ('๋ฒํทํ', 6900)");
}
}
3. DBCP/DataSource ์ค์
application.properties - datasource ์ค์
# DataSource
spring.datasource.url=jdbc:h2:mem:mybatis-test
spring.datasource.username=sa
spring.datasource.password=
์ฐธ๊ณ ๋ก spring.datasource.driver-class-name์ ์คํ๋ง ๋ถํธ๊ฐ url์ ๋ณด๊ณ ์ถ์ธกํ ์ ์์ผ๋ฏ๋ก ๋ช ์ํ์ง ์์๋ ๋๋ค.
์คํ๋ง ๋ถํธ์์๋ HikariCP, Tomcat CP, Commons DBCP2๋ฅผ ์ง์ํ๋ฉฐ ์คํ๋ง ๋ถํธ 2์ ๋์์๋ HikariCP๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ๋ค.
๋ฐ๋ผ์ ์ฌ๊ธฐ์๋ ๋ณ๊ฒฝํ์ง ์๊ณ HikariCP๋ฅผ ์ฌ์ฉํ๋๋ก ํ๊ฒ ๋ค.
ํ์ฌ ํ๋ก์ ํธ์๋ HikariCP์ ๋ณ๋๋ก ์ค์ ํ ๊ฒ์ ์์ผ๋ฏ๋ก ์ค์ ๋ฐฉ๋ฒ๋ง ๊ฐ๋จํ ๋ค๋ฃฌ๋ค.
HikariCP๋ application.properties์ spring.datasource.hikari.* ํ๋กํผํฐ๋ก ์ค์ ํ ์ ์๋ค.
์: HikariCP Maximum Pool Size๋ฅผ 4๋ก ์ค์
spring.datasource.hikari.maximum-pool-size=4
์ด์ datasource๊ฐ ์ ์์ ์ผ๋ก ์ค์ ๋๋์ง ํ์ธํด๋ณด์.
TestRunner.java
@Slf4j
@Component
public class TestRunner implements ApplicationRunner {
@Autowired
DataSource dataSource;
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public void run(ApplicationArguments args) throws Exception {
// DataSource
Connection connection = dataSource.getConnection();
log.info("DBCP: " + dataSource.getClass()); // ์ฌ์ฉํ๋ DBCP ํ์
ํ์ธ
log.info("Url: " + connection.getMetaData().getURL());
log.info("UserName: " + connection.getMetaData().getUserName());
// JdbcTemplate
jdbcTemplate.execute("INSERT INTO Products (prod_name, prod_price) values ('๋ฒํทํ', 6900)");
}
}
log.info("DBCP: " + dataSource.getClass()); ์ฝ๋๋ฅผ ์ถ๊ฐํ์๋ค.
๐ฅ ์คํ ๊ฒฐ๊ณผ
...
INFO [ restartedMain] TestRunner : DBCP: class com.zaxxer.hikari.HikariDataSource
INFO [ restartedMain] TestRunner : Url: jdbc:h2:mem:mybatis-test
INFO [ restartedMain] TestRunner : UserName: SA
...
๋ก๊ทธ๋ฅผ ํตํด ์ค์ ํ ๋๋ก h2 DB ์ด๋ฆ์ด mybatis-test๋ก ๋ฐ๋์๊ณ , HikariCP๋ฅผ ์ฌ์ฉํจ์ ์ ์ ์๋ค.
4. MyBatis ์ค์
์คํ๋ง ๋ถํธ ๋ฉ์ธ ์ดํ๋ฆฌ์ผ์ด์ ์ @MapperScan์ ์ด์ฉํด ์คํ๋ง ๋ถํธ๊ฐ @Mapper๊ฐ ๋ถ์ MyBatis ๋งคํผ๋ฅผ ์ค์บํ์ฌ ๋น์ผ๋ก ๋ฑ๋กํ ์ ์๋๋ก ํ๋ค.
import org.mybatis.spring.annotation.MapperScan;
@MapperScan(basePackageClasses = MybatisSampleApplication.class)
@SpringBootApplication
public class MybatisSampleApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisSampleApplication.class, args);
}
}
์ค์ ๋ฐฉ๋ฒ์ @ComponentScan๊ณผ ์ ์ฌํ๋ค.
application.properties์ MyBatis์ ๊ดํ 4๊ฐ์ง ์ค์ ์ ์ถ๊ฐํ๋ค.
# MyBatis
# mapper.xml ์์น ์ง์
mybatis.mapper-locations: mybatis-mapper/**/*.xml
# model ํ๋กํผํฐ camel case ์ค์
mybatis.configuration.map-underscore-to-camel-case=true
# ํจํค์ง ๋ช
์ ์๋ตํ ์ ์๋๋ก alias ์ค์
mybatis.type-aliases-package=com.atoz_develop.mybatissample.model
# mapper ๋ก๊ทธ๋ ๋ฒจ ์ค์
logging.level.com.atoz_develop.mybatissample.repository=TRACE
5. Model, Mapper(Interface, XML) ์์ฑ
(1) Model
model ํจํค์ง๋ฅผ ๋ง๋ค๊ณ ์์์ ๋ง๋ Proudcts ํ ์ด๋ธ๊ณผ ๋งคํ๋๋ Model์ ์์ฑํ๋ค.
Product.java
import lombok.*;
import org.apache.ibatis.type.Alias;
@NoArgsConstructor @RequiredArgsConstructor @Getter @Setter @ToString
public class Product {
private Long prodId;
@NonNull private String prodName;
@NonNull private int prodPrice;
}
application.properties์ ์ค์ ํ MyBatis ์ค์ ์ค
mybatis.configuration.map-underscore-to-camel-case=true
์ ์ํด ํ๋กํผํฐ์ ์นด๋ฉ ์ผ์ด์ค ๋ค์ด๋ฐ ์ปจ๋ฒค์ ์ ์ฌ์ฉํ ์ ์๋ค.
@NonNull์ @RequiredArgsConstructor๋ก ํด๋น ํ๋(prodName, prodPrice)๋ฅผ ๋ฐ๋ ์์ฑ์๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๋ถ์ฌ์ฃผ์์ผ๋, @NonNull์ ์ฌ์ฉํ์ง ์๊ณ final๋ก ์ ์ธํด์ค๋ ๊ฐ๋ฅํ๋ค.
private final String prodName;
private final int prodPrice;
(2) Mapper Interface
repository ํจํค์ง๋ฅผ ๋ง๋ค๊ณ ๋งคํผ ์ธํฐํ์ด์ค๋ฅผ ์์ฑํ์ฌ Prouducts ํ ์ด๋ธ๊ณผ ์ฐ๋ํ ๋ฉ์๋๋ฅผ ์ ์ํ๋ค.
ProuductMapper.java
@Mapper
public interface ProductMapper {
Product selectProductById(Long id);
List<Product> selectAllProducts();
void insertProduct(Product product);
}
@MapperScan์ ์ํด Mapper๋ก ์ค์บ๋ ์ ์๋๋ก @Mapper ์ ๋ ธํ ์ด์ ์ ๋ถ์ฌ์ค๋ค.
(3) XML
์์์ application.properties์ MyBatis XML ์์น๋ฅผ ์๋์ ๊ฐ์ด ์ค์ ํ์๋ค.
# mapper.xml ์์น ์ง์
mybatis.mapper-locations: mybatis-mapper/**/*.xml
ํด๋น ์์น์ XML ํ์ผ์ ์์ฑํ์ฌ ProductMapper ๋ฉ์๋ ํธ์ถ ์ ์ค์ ๋ก ๋์ํ SQL์ ์ค์ ํ๋ค.
ProudctMapper.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.atoz_develop.mybatissample.repository.ProductMapper">
<select id="selectProductById" resultType="Product">
SELECT prod_id
,prod_name
,prod_price
FROM products
WHERE prod_id = #{prodId}
</select>
<select id="selectAllProducts" resultType="Product">
SELECT prod_id
,prod_name
,prod_price
FROM products
</select>
<insert id="insertProduct" parameterType="Product">
INSERT INTO products (prod_name, prod_price)
VALUES (#{prodName}, #{prodPrice})
</insert>
</mapper>
application.properties์ ์ค์ ํ MyBatis ์ค์ ์ค
mybatis.type-aliases-package=com.atoz_develop.mybatissample.model
์ ์ํด type ๋งคํ ์ ํจํค์ง ๋ช ์ ์๋ตํ ์ ์๋ค.
6. ํ ์คํธ
Service ๋น๊ณผ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ์ค์ ๋ก ํ ์คํธํด๋ณด์.
service ํจํค์ง๋ฅผ ๋ง๋ค๊ณ ProudctService๋ฅผ ์์ฑํ๋ค.
ProductService.java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public Product getProductById(Long id) {
return productMapper.selectProductById(id);
}
public List<Product> getAllProducts() {
return productMapper.selectAllProducts();
}
@Transactional
public void addProduct(Product product) {
productMapper.insertProduct(product);
}
}
ProductMapper๋ฅผ ์ฃผ์ ๋ฐ์ ๊ฐ ์๋น์ค ๋ฉ์๋์์ ํธ์ถํ๋ค.
๋ค์์ผ๋ก ProductService์ ๋ํ ํ ์คํธ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
ProuductServiceTest.java
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProductServiceTest {
@Autowired
private ProductService productService;
@Test
public void getProductById() {
Product product = productService.getProductById(1L);
log.info("product : {}", product);
}
@Test
public void getAllProducts() {
List<Product> products = productService.getAllProducts();
log.info("products : {}", products);
}
@Transactional
@Test
public void addProduct() {
productService.addProduct(new Product("์ฟค๋ฌ ์ดํธ", 7900));
productService.addProduct(new Product("๋ง์คํฌํฉ", 1000));
productService.addProduct(new Product("ํฐ์
์ธ ", 5900));
}
}
์ด์ MyBatis ์ค์ ๊ณผ ํ ์คํธ ์ฝ๋ ์์ฑ์ด ์๋ฃ๋์๋ค.
ํ ์คํธ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณผ ์ ์๋ค.
๐ฅ ์คํ ๊ฒฐ๊ณผ
getProductById
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.selectProductById : ==> Preparing: SELECT prod_id ,prod_name ,prod_price FROM products WHERE prod_id = ?
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.selectProductById : ==> Parameters: 1(Long)
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectProductById : <== Columns: PROD_ID, PROD_NAME, PROD_PRICE
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectProductById : <== Row: 1, ๋ฒ ๋ฒ ์ฒ ๋ฌผํฐ์, 2700
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.selectProductById : <== Total: 1
INFO 956 --- [ main] c.a.m.service.ProductServiceTest : product : Product(prodId=1, prodName=๋ฒ ๋ฒ ์ฒ ๋ฌผํฐ์, prodPrice=2700)
getAllProducts
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : ==> Preparing: SELECT prod_id ,prod_name ,prod_price FROM products
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : ==> Parameters:
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Columns: PROD_ID, PROD_NAME, PROD_PRICE
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Row: 1, ๋ฒ ๋ฒ ์ฒ ๋ฌผํฐ์, 2700
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Row: 2, ์ฌ๋ฆ ํ ํผ, 35180
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Row: 3, ํ์ดํฌ ์ญ์ค, 860
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Row: 4, ์ฐ์ฐ, 2900
TRACE 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Row: 5, ๋ฒํทํ, 6900
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.selectAllProducts : <== Total: 5
INFO 956 --- [ main] c.a.m.service.ProductServiceTest : products : [Product(prodId=1, prodName=๋ฒ ๋ฒ ์ฒ ๋ฌผํฐ์, prodPrice=2700), Product(prodId=2, prodName=์ฌ๋ฆ ํ ํผ, prodPrice=35180), Product(prodId=3, prodName=ํ์ดํฌ ์ญ์ค, prodPrice=860), Product(prodId=4, prodName=์ฐ์ฐ, prodPrice=2900), Product(prodId=5, prodName=๋ฒํทํ, prodPrice=6900)]
addProudct
INFO 956 --- [ main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@327af41b testClass = ProductServiceTest, testInstance = com.atoz_develop.mybatissample.service.ProductServiceTest@7740b0ab, testMethod = addProduct@ProductServiceTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@6cb6decd testClass = ProductServiceTest, locations = '{}', classes = '{class com.atoz_develop.mybatissample.MybatisSampleApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@40005471, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@49438269, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@7770f470, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@71e9ddb4, org.springframework.boot.test.context.SpringBootTestArgs@1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]; transaction manager [org.springframework.jdbc.datasource.DataSourceTransactionManager@3eb9c575]; rollback [true]
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : ==> Preparing: INSERT INTO products (prod_name, prod_price) VALUES (?, ?)
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : ==> Parameters: ์ฟค๋ฌ ์ดํธ(String), 7900(Integer)
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : <== Updates: 1
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : ==> Preparing: INSERT INTO products (prod_name, prod_price) VALUES (?, ?)
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : ==> Parameters: ๋ง์คํฌํฉ(String), 1000(Integer)
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : <== Updates: 1
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : ==> Preparing: INSERT INTO products (prod_name, prod_price) VALUES (?, ?)
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : ==> Parameters: ํฐ์
์ธ (String), 5900(Integer)
DEBUG 956 --- [ main] c.a.m.r.ProductMapper.insertProduct : <== Updates: 1
INFO 956 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@327af41b testClass = ProductServiceTest, testInstance = com.atoz_develop.mybatissample.service.ProductServiceTest@7740b0ab, testMethod = addProduct@ProductServiceTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@6cb6decd testClass = ProductServiceTest, locations = '{}', classes = '{class com.atoz_develop.mybatissample.MybatisSampleApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@40005471, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@49438269, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@7770f470, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@71e9ddb4, org.springframework.boot.test.context.SpringBootTestArgs@1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]
References
mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/#
mybatis.org/mybatis-3/ko/getting-started.html
tech.javacafe.io/2018/07/31/mybatis-with-spring/
taetaetae.github.io/2019/04/21/spring-boot-mybatis-mysql-xml/
๋๊ธ