MyBatis-Flex ์์กด์ฑ ์ค์ (Gradle/Maven), ์ํฐํฐ ๋งคํ, BaseMapper, QueryWrapper, Lambda ๋์ ์ฟผ๋ฆฌ, CTE ์ฌ๊ท ์ฟผ๋ฆฌ, ์ฐ๊ด๊ด๊ณ ๋ฐ ์ค๋ฌด ๋ ํผ๋ฐ์ค
## 1. ํ๋ก์ ํธ ์์กด์ฑ ์ถ๊ฐ (Gradle & Maven)
> [!NOTE]
> **Spring Boot ๋ฒ์ ๋ณ ์คํํฐ ๋งคํ**
> - **Spring Boot 4.x**: `mybatis-flex-spring-boot4-starter` (Java 17/21+, Spring Boot 4 ๋์)
> - **Spring Boot 3.x**: `mybatis-flex-spring-boot3-starter` (Java 17+, Jakarta ๋ค์์คํ์ด์ค)
> - **Spring Boot 2.x**: `mybatis-flex-spring-boot-starter` (Java 8/11+, javax ๋ค์์คํ์ด์ค)
> - **APT ํ๋ก์ธ์**: `mybatis-flex-processor` (๋ฒ์ ๊ณตํต, Q-Class ๋ฐ TableDef ์์ฑ)
> - *์ฃผ์: ๊ธฐ์กด `mybatis-spring-boot-starter`์ ์ค๋ณต ์ ์ธ ์ ์ถฉ๋์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก flex ์คํํฐ๋ง ๋ฑ๋กํฉ๋๋ค.*
### Gradle (Groovy DSL - `build.gradle`)
```groovy
dependencies {
// 1) Spring Boot 4.x ์คํํฐ (Spring Boot 4.0 ์ด์ ํ๊ฒฝ)
implementation 'com.mybatis-flex:mybatis-flex-spring-boot4-starter:1.11.8'
// 2) Spring Boot 3.x ์คํํฐ (Spring Boot 3.x ์ฌ์ฉ ์)
// implementation 'com.mybatis-flex:mybatis-flex-spring-boot3-starter:1.10.6'
// 3) Spring Boot 2.x ์คํํฐ (Spring Boot 2.x ๋ ๊ฑฐ์ ์ฌ์ฉ ์)
// implementation 'com.mybatis-flex:mybatis-flex-spring-boot-starter:1.10.6'
// APT ํ๋ก์ธ์ (Q-Class ๋ฐ TableDef ํด๋์ค ์๋ ์์ฑ์ ์ํด ํ์)
annotationProcessor 'com.mybatis-flex:mybatis-flex-processor:1.11.8'
// Lombok๊ณผ ํจ๊ป ์ฌ์ฉํ ๊ฒฝ์ฐ ์์: Lombok ์ ์ธ ํ mybatis-flex-processor ์ ์ธ
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
```
### Gradle (Kotlin DSL - `build.gradle.kts`)
```kotlin
dependencies {
// 1) Spring Boot 4.x ์คํํฐ
implementation("com.mybatis-flex:mybatis-flex-spring-boot4-starter:1.11.8")
// 2) Spring Boot 3.x ์คํํฐ ์ฌ์ฉ ์
// implementation("com.mybatis-flex:mybatis-flex-spring-boot3-starter:1.10.6")
// 3) Spring Boot 2.x ์คํํฐ ์ฌ์ฉ ์
// implementation("com.mybatis-flex:mybatis-flex-spring-boot-starter:1.10.6")
// APT ํ๋ก์ธ์ (TableDef ์๋ ์์ฑ)
annotationProcessor("com.mybatis-flex:mybatis-flex-processor:1.11.8")
// (์ ํ ์ฌํญ) Kotlin ํ์ฅ ๊ธฐ๋ฅ ๋ฐ DSL ์ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
implementation("com.mybatis-flex:mybatis-flex-kotlin:1.11.8")
}
```
### Maven (`pom.xml`)
```xml
<dependencies>
<!-- 1) Spring Boot 4.x ์คํํฐ -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot4-starter</artifactId>
<version>1.11.8</version>
</dependency>
<!-- 2) Spring Boot 3.x ์คํํฐ ์ฌ์ฉ ์ -->
<!--
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>1.10.6</version>
</dependency>
-->
<!-- 3) Spring Boot 2.x ์คํํฐ ์ฌ์ฉ ์ -->
<!--
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.10.6</version>
</dependency>
-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<!-- Lombok ์ฌ์ฉ ์ (์์ ์ค์: lombok -> mybatis-flex) -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- MyBatis-Flex APT ํ๋ก์ธ์ (TableDef ์๋ ์์ฑ) -->
<path>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<version>1.11.8</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
```
## 2. ์ํฐํฐ ๋งคํ & BaseMapper ๊ธฐ๋ณธ ๊ตฌ์กฐ
```java
// 1. ์ํฐํฐ ์ ์ (@Table, @Id, @Column)
@Table("tb_user")
@Getter
@Setter
public class User {
@Id(keyType = KeyType.Auto) // MySQL AUTO_INCREMENT
private Long id;
@Column("user_name")
private String name;
private Integer age;
@Column(isLogicDelete = true) // ๋
ผ๋ฆฌ ์ญ์ ์ปฌ๋ผ (์กฐํ ์ ์๋ ์ ์ธ)
private Boolean isDeleted;
@Column(version = true) // ๋๊ด์ ๋ฝ ๋ฒ์ ์ปฌ๋ผ (์์ ์ ์๋ +1)
private Long version;
}
// 2. Mapper ์ธํฐํ์ด์ค ์ ์ธ (BaseMapper ์์๋ง์ผ๋ก ๊ธฐ๋ณธ CRUD ์๋ ์์ฑ)
public interface UserMapper extends BaseMapper<User> {}
```
```java
// ๊ธฐ๋ณธ CRUD ๋ฉ์๋ ํธ์ถ ์์
userMapper.insert(user); // ๋จ๊ฑด ๋ฑ๋ก
userMapper.insertBatch(userList); // ๋๋ ๋ฐฐ์น ๋ฑ๋ก
userMapper.selectOneById(1L); // PK ๋จ๊ฑด ์กฐํ
userMapper.update(user); // ๋จ๊ฑด ์์ (null ํ๋ ์ ์ธ)
userMapper.deleteById(1L); // ๋
ผ๋ฆฌ ์ญ์ ๋๋ ๋ฌผ๋ฆฌ ์ญ์
```
## 3. Lambda ๊ธฐ๋ฐ Type-Safe ๋์ ์ฟผ๋ฆฌ (Q-Class ๋ถํ์)
```java
// Q-Class๋ ์ฝ๋ ์์ฑ ์์ด Java ๋ฉ์๋ ์ฐธ์กฐ(Lambda)๋ก 100% Type-Safe ์ฟผ๋ฆฌ ์์ฑ!
String searchName = "kim";
Integer minAge = 20;
QueryWrapper query = QueryWrapper.create()
.select(User::getId, User::getName, User::getAge)
.from(User.class)
.where(User::getName).like(searchName, hasText(searchName)) // ์กฐ๊ฑด์ด true์ผ ๋๋ง WHERE ์ ํฌํจ
.and(User::getAge).ge(minAge, minAge != null) // null-safe ๋์ ์กฐ๊ฑด
.orderBy(User::getId).desc();
List<User> users = userMapper.selectListByQuery(query);
```
## 4. APT ํ
์ด๋ธ ์ ์(Tables) ํ์ฉ (์ ํ ์ฌํญ)
```java
// APT(Annotation Processor) ์ค์ ์ ์์ฑ๋๋ ์ ์ ํ
์ด๋ธ ํด๋์ค(USER) ํ์ฉ ๊ฐ๋ฅ
import static com.example.entity.table.UserTableDef.USER;
QueryWrapper query = QueryWrapper.create()
.select(USER.ID, USER.NAME, USER.AGE)
.from(USER)
.where(USER.AGE.between(20, 30))
.and(USER.NAME.like("Lee"));
List<User> list = userMapper.selectListByQuery(query);
```
## 5. CTE (Common Table Expressions) & ์ฌ๊ท ์ฟผ๋ฆฌ (WITH)
```java
// MyBatis-Flex๋ ๋ณต์กํ ๊ณ์ธตํ ์กฐ์ง๋, ์นดํ
๊ณ ๋ฆฌ ํธ๋ฆฌ ์กฐํ๋ฅผ ์ํ ์ฌ๊ท CTE๋ฅผ ๋ค์ดํฐ๋ธ ์ง์!
QueryWrapper query = QueryWrapper.create()
.withRecursive("DeptTree")
.asSelect(
// Anchor Member (์์ ๋
ธ๋)
select(DEPARTMENT.ALL_COLUMNS)
.from(DEPARTMENT)
.where(DEPARTMENT.ID.eq(1L))
.unionAll(
// Recursive Member (ํ์ ๋
ธ๋ ํ์)
select(DEPARTMENT.ALL_COLUMNS)
.from(DEPARTMENT)
.join("DeptTree").on(DEPARTMENT.PARENT_ID.eq(column("DeptTree.id")))
)
)
.select()
.from("DeptTree");
List<Department> departments = deptMapper.selectListByQuery(query);
```
## 6. ์ฐ๊ด๊ด๊ณ ๋งคํ (@RelationOneToOne / ManyToOne / OneToMany)
```java
@Table("tb_order")
@Getter @Setter
public class Order {
@Id(keyType = KeyType.Auto)
private Long id;
private Long userId;
// N:1 ๊ด๊ณ - ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๋งคํํ์ฌ N+1 ๋ฌธ์ ์์ฒ ์ฐจ๋จ
@RelationManyToOne(selfField = "userId", targetField = "id")
private User user;
// 1:N ๊ด๊ณ
@RelationOneToMany(selfField = "id", targetField = "orderId")
private List<OrderItem> items;
}
```
```java
// ์ฐ๊ด ์ํฐํฐ๊น์ง ํจ๊ป ์กฐํ (WithRelations ๊ณ์ด ๋ฉ์๋)
Order order = orderMapper.selectOneWithRelationsById(orderId);
List<Order> orders = orderMapper.selectAllWithRelations();
```
## 7. ํ์ด์ง & ์ ๋ ฌ (Pagination)
```java
// 1. Page ๊ฐ์ฒด ์์ฑ (ํ์ด์ง ๋ฒํธ๋ 1๋ถํฐ ์์)
Page<User> page = Page.of(1, 10); // 1ํ์ด์ง, 10๊ฑด
// 2. ํ์ด์ง ์ฟผ๋ฆฌ ์คํ (์๋ COUNT ์ฟผ๋ฆฌ + LIMIT/OFFSET ์์ฑ)
QueryWrapper query = QueryWrapper.create()
.where(User::getAge).gt(18)
.orderBy(User::getId).desc();
Page<User> result = userMapper.paginate(page, query);
// 3. ๊ฒฐ๊ณผ ์ ๊ทผ
long totalRow = result.getTotalRow(); // ์ ์ฒด ๋ ์ฝ๋ ์
List<User> records = result.getRecords(); // ํ์ฌ ํ์ด์ง ๋ฐ์ดํฐ
long totalPage = result.getTotalPage(); // ์ ์ฒด ํ์ด์ง ์
```
## 8. UpdateChain (์์ ํ๊ณ ์ ์ฐํ ์กฐ๊ฑด๋ถ ์์ )
```java
// ์ํฐํฐ๋ฅผ ํต์งธ๋ก ์์ฑํ์ง ์๊ณ ์ํ๋ ํ๋๋ง ์ฆ์ ์
๋ฐ์ดํธ
boolean success = UpdateChain.of(User.class)
.set(User::getAge, 25)
.set(User::getName, "UpdatedName")
.where(User::getId).eq(100L)
.update();
// IN ์กฐ๊ฑด ๋๋ ์ํ ๋ณ๊ฒฝ
UpdateChain.of(Order.class)
.set(Order::getStatus, "SHIPPED")
.where(Order::getId).in(orderIds)
.update();
```
## 9. ๊ฐ์ฌ(Auditing) & ์๋ ์ปฌ๋ผ ์ฑ์ฐ๊ธฐ
```java
@Table("tb_article")
@Getter @Setter
public class Article {
@Id(keyType = KeyType.Auto)
private Long id;
private String title;
// 1. SQL ํจ์๋ก ์ธ์ํธ/์
๋ฐ์ดํธ ์ ์๋ ์ฑ์ฐ๊ธฐ
@Column(onInsertValue = "now()")
private LocalDateTime createdAt;
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updatedAt;
// 2. ๋๋ ๋ฆฌ์ค๋ ๋ฑ๋ก์ ํตํ ์ธ์
์ฌ์ฉ์ ID ์ฃผ์
@Column(onInsertValue = "sessionUserId()", onUpdateValue = "sessionUserId()")
private Long updatedBy;
}
```
## 10. Kotlin ์ง์ (Kotlin Extension & DSL)
```kotlin
// Kotlin์์๋ ํ๋กํผํฐ ์ฐธ์กฐ๋ฅผ ํ์ฉํ์ฌ ๋์ฑ ์ง๊ด์ ์ผ๋ก ์์ฑ
val query = query {
select(User::id, User::name, User::age)
from(User::class.java)
where(User::name like "ํ%")
and(User::age ge 20)
orderBy(User::id.desc())
}
val userList = userMapper.selectListByQuery(query)
```
## 11. Spring Boot ํ๊ฒฝ์ค์ (application.yml)
```yaml
mybatis-flex:
# 1. SQL ์คํ ๋ก๊ทธ ์ถ๋ ฅ (๊ฐ๋ฐ ํ๊ฒฝ)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
# 2. ๊ธ๋ก๋ฒ ๋
ผ๋ฆฌ ์ญ์ ์ค์
global-config:
logic-delete-column: is_deleted
logic-deleted-value: 1
logic-normal-value: 0
# 3. ์ฝ์ ์คํ SQL ํฌ๋งทํ
๋ฐ ์คํ ์๊ฐ ์ถ๋ ฅ
logging:
enable: true
```
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์