본문으로 건너뛰기

DBMS Port 정의

Basic Structure

port:
PortServiceName: ## PortServiceName은 프로젝트내에서 중복되지 않는 이름 내에서 자유롭게 지정
meta: dbms @datasource("datasourceName")
methods:
methodName: ## methodName은 service 내에서 중복되지 않게 의미에 맞게 자유롭게 지정
meta: method [-- 설명]
params:
paramName1: ParamType
paramName2: ParamType
return: ReturnType
nativeQuery: true/false
pageable: true/false (선택)
ref: referencedMethodName (선택)
query: |
SQL 또는 JPQL 쿼리

DBMS 설정 옵션

  • @datasource(name): 사용할 데이터소스 이름 (기본값: "main"), 단일 데이터 소스인 경우 설정 불필요
  • nativeQuery: Native SQL(true) 또는 JPQL(false) 선택. 기본값은 true
  • pageable: 페이징 처리 지원 여부
  • ref: 다른 메서드의 쿼리 참조 (페이징 처리 시 유용)
  • query: 실행할 SQL/JPQL (YAML 멀티라인 문법 사용)

State-changing Queries (insert/update/delete)

  • methodName은 insert, update, delete 로 시작되어야 함.
  • return 설정은 int 로 지정하며, 반영갯수 값이 반환된다.

Parameter Binding Methods

1. Named Parameter (권장)

query: |
SELECT * FROM users
WHERE name = :name AND age >= :age

2. 문자열 치환 (실행 시점)

query: |
SELECT * FROM ${tableName}
WHERE status = :status

3. 조건부 쿼리 (동적 SQL)

query: |
SELECT * FROM users
WHERE 1=1
AND name = :name /* if:name */
AND age >= :minAge /* if:minAge */

Primitive Type Return Support

DBMS Port에서 SQL의 COUNT, MAX, MIN, SUM 등의 aggregate 함수나 단일 값 조회 시 primitive 타입을 직접 반환할 수 있습니다.

Supported Primitive Types

  • int, integer: 정수형 (COUNT, SUM 결과)
  • long: 긴 정수형 (큰 COUNT, SUM 결과)
  • double: 실수형 (AVG 결과)
  • float: 부동소수점형
  • string: 문자열 (단일 컬럼 조회, MAX/MIN 결과)
  • boolean: 불린형

Primitive Type Return Examples

port:
UserDbPortService:
meta: dbms @datasource("main")
methods:
# COUNT 쿼리 - int 반환
getUserCount:
meta: method -- 전체 사용자 수 조회
return: int
query: "SELECT COUNT(*) FROM users"

# 조건부 COUNT 쿼리 - long 반환
getActiveUserCount:
meta: method -- 활성 사용자 수 조회
params:
active: boolean
return: long
query: "SELECT COUNT(*) FROM users WHERE active = :active"

# MAX 쿼리 - long 반환
getMaxPrice:
meta: method -- 최고 가격 조회
return: long
query: "SELECT MAX(price) FROM products WHERE active = true"

# AVG 쿼리 - double 반환
getAverageAge:
meta: method -- 평균 나이 조회
return: double
query: "SELECT AVG(age) FROM users"

# 단일 값 조회 - string 반환
getUserNameById:
meta: method -- ID로 사용자명 조회
params:
userId: long
return: string
query: "SELECT name FROM users WHERE id = :userId"

# 존재 여부 확인 - boolean 반환
existsByEmail:
meta: method -- 이메일 존재 여부 확인
params:
email: string
return: boolean
query: "SELECT CASE WHEN COUNT(*) > 0 THEN true ELSE false END FROM users WHERE email = :email"

Generated Java Code Example

@DbmsService(id="UserDbPortService", datasource="main")
public interface UserDbPortService {
Integer getUserCount();
Long getActiveUserCount(Boolean active);
Long getMaxPrice();
Double getAverageAge();
String getUserNameById(Long userId);
Boolean existsByEmail(String email);
}

DBMS Port Examples

1. 기본 조회 쿼리

dto:
UserSearchInput:
meta: dto
fields:
name: string
minAge: int

UserDTO:
meta: dto
fields:
id: long
name: string
email: string
age: int

port:
UserDbPortService:
meta: dbms @datasource("main")
methods:
findUsersByName:
meta: method -- 사용자 이름 검색
params:
input: UserSearchInput
return: List<UserDTO>
nativeQuery: true
query: |
SELECT id, name, email, age
FROM users
WHERE name LIKE CONCAT('%', :name, '%')
AND age >= :minAge

2. 조건부 동적 쿼리

port:
OrderDbPortService:
meta: dbms @datasource("order")
methods:
searchOrders:
meta: method -- 주문 검색 (동적 조건)
params:
input: OrderSearchDTO
return: List<OrderDTO>
nativeQuery: true
query: |
SELECT o.id, o.order_date, o.status, o.amount
FROM orders o
WHERE 1=1
AND o.status = :status /* if:status */
AND o.order_date >= :startDate /* if:startDate */
AND o.order_date <= :endDate /* if:endDate */
AND o.amount >= :minAmount /* if:minAmount */
ORDER BY o.order_date DESC

3. 페이징 지원 쿼리

port:
ProductDbPortService:
meta: dbms @datasource("product")
methods:
getProductList:
meta: method -- 상품 목록 (페이징)
params:
input: ProductSearchDTO
return: Page<ProductDTO>
pageable: true
nativeQuery: true
query: |
SELECT p.id, p.name, p.price, p.category
FROM products p
WHERE p.active = true
AND p.name LIKE CONCAT('%', :keyword, '%') /* if:keyword */

4. 집계 통계 쿼리

port:
StatisticsDbPortService:
meta: dbms @datasource("analytics")
methods:
getSalesStatistics:
meta: method -- 매출 통계
params:
startDate: date
endDate: date
return: List<SalesStatsDTO>
nativeQuery: true
query: |
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE order_date BETWEEN :startDate AND :endDate
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month DESC

5. 복잡한 JOIN 쿼리

port:
ReportDbPortService:
meta: dbms @datasource("report")
methods:
getUserOrderSummary:
meta: method -- 사용자별 주문 요약
params:
userId: long
return: UserOrderSummaryDTO
nativeQuery: true
query: |
SELECT
u.name AS user_name,
u.email,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.amount), 0) AS total_amount,
MAX(o.order_date) AS last_order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = :userId
GROUP BY u.id, u.name, u.email

6. 실제 예시 (Car Info)

dto:
CarInfoInput:
meta: dto
fields:
brand: string -- 브랜드명

CarInfoOutput:
meta: dto
fields:
brand: string -- 브랜드명
description: string -- 설명
modelId: string -- 모델 ID

port:
CarInfoPortService:
meta: dbms @datasource("main")
methods:
findByBrand:
meta: method -- 브랜드별 차량 정보 조회
params:
input: CarInfoInput
return: List<CarInfoOutput>
nativeQuery: true
query: |
SELECT
brand,
description,
model_id
FROM car_info
WHERE brand = :brand

Dynamic SQL Advanced Features

1. 블록 단위 조건 제어

query: |
SELECT * FROM users
WHERE 1=1
/* if:searchCriteria != null */
AND (
name LIKE CONCAT('%', :name, '%')
OR email LIKE CONCAT('%', :email, '%')
)
/* if-end */
/* if:ageRange != null */
AND age BETWEEN :minAge AND :maxAge
/* if-end */

2. 자동 정렬 (sortCode 파라미터)

# SearchDTO에 sortCode 필드가 있으면 자동으로 ORDER BY 절 생성
# 입력 예시: {"sortCode": "name+,age-"}
# 생성되는 SQL: ORDER BY name ASC, age DESC

3. 쿼리 참조 (ref 기능)

port:
ItemDbPortService:
meta: dbms @datasource("item")
methods:
getItemList:
meta: method -- 아이템 목록 조회
params:
input: ItemSearchDTO
return: List<ItemDTO>
nativeQuery: true
query: |
SELECT id, name, price FROM items
WHERE active = true
AND name LIKE CONCAT('%', :keyword, '%') /* if:keyword */

getItemListPaged:
meta: method -- 아이템 목록 조회 (페이징)
params:
input: ItemSearchDTO
return: Page<ItemDTO>
pageable: true
ref: getItemList # getItemList의 쿼리를 참조하여 페이징 처리

Spring Boot DataSource Configuration

Single DataSource

spring:
datasource:
url: jdbc:mysql://localhost:3306/maindb
username: user
password: password
driver-class-name: com.mysql.cj.jdbc.Driver

Multiple DataSources (Java Configuration)

@Configuration
public class DataSourceConfig {

@Primary
@Bean("main")
@ConfigurationProperties("spring.datasource")
public DataSource mainDataSource() {
return DataSourceBuilder.create().build();
}

@Bean("order")
@ConfigurationProperties("spring.datasource.order")
public DataSource orderDataSource() {
return DataSourceBuilder.create().build();
}

@Bean("analytics")
@ConfigurationProperties("spring.datasource.analytics")
public DataSource analyticsDataSource() {
return DataSourceBuilder.create().build();
}
}

Important Notes

SQL Injection Prevention

# 좋은 예: Named Parameter 사용
query: |
SELECT * FROM users
WHERE name = :name -- 안전한 파라미터 바인딩

# 나쁜 예: 문자열 치환 사용 (보안 위험)
query: |
SELECT * FROM users
WHERE name = '${name}' -- SQL Injection 위험

Performance Optimization

  • 대용량 데이터는 반드시 페이징 처리
  • 적절한 인덱스 설정 필요
  • N+1 문제 방지를 위한 JOIN 활용
  • 인덱스를 고려한 WHERE 조건 순서 배치