
수백 개의 동적 SQL 조각을 효율적으로 관리하려면, 디렉토리 구조를 기능별로 구성하고, 공통 부분은 상위 디렉토리에 두는 방식이 적합합니다. 이를 MyBatis의 XML Mapper와 디렉토리 구조를 활용하여 설계하는 방안을 아래와 같이 제안드립니다.
2024.11.30 - [오라클 데이터베이스 강좌] - Oracle에서 JSON 형태로 데이터 생성하기
Oracle에서 JSON 형태로 데이터 생성하기
Oracle은 JSON 처리를 위한 다양한 내장 함수와 기능을 제공합니다. 비슷한 결과를 얻으려면 JSON_OBJECT, JSON_ARRAYAGG, 그리고 ROWNUM, OVER() 같은 SQL 기능을 활용할 수 있습니다. Oracle에서는 아래와 같은
notion4570.tistory.com
0. 디렉토리 구조 설계
다음은 SQL 조각과 매퍼 파일의 디렉토리 구조 예시입니다:
/resources
├── /mappers
│ ├── /common
│ │ ├── pagination.xml
│ │ ├── sorting.xml
│ │ ├── filters.xml
│ ├── /users
│ │ ├── userQueries.xml
│ │ ├── adminQueries.xml
│ ├── /products
│ │ ├── productQueries.xml
│ ├── /orders
│ │ ├── orderQueries.xml
- /common: 공통적인 페이징, 정렬, 필터 로직이 포함된 SQL 조각.
- 기능별 디렉토리 (/users, /products, /orders): 기능별로 SQL 조각 및 관련 매퍼를 배치.
1. 공통 페이징 템플릿 수정 (/common/pagination.xml)
JSON_OBJECT의 필드 구성도 동적으로 변경할 수 있도록 템플릿화합니다.
<sql id="basePagination">
SELECT JSON_OBJECT(
${jsonFields}
) AS result
FROM (
<include refid="${dynamicQuery}" />
) sub
WHERE ROWNUM BETWEEN (#{currentPage} - 1) * #{pageSize} + 1 AND #{currentPage} * #{pageSize}
</sql>
- ${jsonFields}: JSON_OBJECT 내부의 필드들을 동적으로 정의합니다.
- ${dynamicQuery}: 동적 SQL 조각을 삽입합니다.
2. 기능별 SQL 조각 구성 (/users/userQueries.xml, /products/productQueries.xml)
다양한 데이터 소스를 위한 SQL 조각과 JSON 필드 구성을 별도로 관리합니다.
/users/userQueries.xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 사용자 목록 쿼리 -->
<sql id="userList">
SELECT id, name, age
FROM users
WHERE active = 1
ORDER BY id
</sql>
<!-- 사용자 JSON 필드 구성 -->
<sql id="userJsonFields">
'id' VALUE id,
'name' VALUE name,
'age' VALUE age
</sql>
</mapper>
/products/productQueries.xml
<mapper namespace="com.example.mapper.ProductMapper">
<!-- 제품 목록 쿼리 -->
<sql id="productList">
SELECT id, product_name, stock
FROM products
WHERE available = 1
ORDER BY product_name
</sql>
<!-- 제품 JSON 필드 구성 -->
<sql id="productJsonFields">
'id' VALUE id,
'productName' VALUE product_name,
'stock' VALUE stock
</sql>
</mapper>
3. 동적 쿼리와 JSON 필드를 Mapper에서 주입
Mapper Interface에서 SQL 조각 ID와 JSON 필드 구성 ID를 전달받도록 설계합니다.
UserMapper.java
@Mapper
public interface UserMapper {
List<Map<String, Object>> getPagedData(
@Param("currentPage") int currentPage,
@Param("pageSize") int pageSize,
@Param("dynamicQuery") String dynamicQuery,
@Param("jsonFields") String jsonFields
);
}
4. Mapper XML에서 동적 쿼리와 JSON 필드 조합
Mapper XML에서 공통 템플릿을 호출하면서, JSON 필드와 동적 쿼리를 연결합니다.
<mapper namespace="com.example.mapper.UserMapper">
<select id="getPagedData" resultType="map">
<include refid="common.basePagination" />
</select>
</mapper>
5. Service Layer에서 SQL 조각과 JSON 필드 전달
Service에서 필요한 SQL 조각과 JSON 필드 ID를 선택하여 Mapper에 전달합니다.
UserService.java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<Map<String, Object>> getPagedUsers(int currentPage, int pageSize) {
return userMapper.getPagedData(
currentPage,
pageSize,
"userList", // 동적 쿼리
"userJsonFields" // JSON 필드
);
}
}
ProductService.java
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public List<Map<String, Object>> getPagedProducts(int currentPage, int pageSize) {
return productMapper.getPagedData(
currentPage,
pageSize,
"productList", // 동적 쿼리
"productJsonFields" // JSON 필드
);
}
}
동작 과정 요약
- 공통 페이징 로직: basePagination은 고정된 페이징 로직을 제공하며, 동적으로 SQL과 JSON 필드를 삽입합니다.
- 기능별 SQL 및 JSON 필드: 각 기능별로 쿼리와 JSON 필드 구성을 별도의 XML 파일에서 정의.
- Mapper 동적 주입: 서비스 레이어에서 필요한 SQL 조각과 JSON 필드를 Mapper에 전달.
- 결과: 다양한 레이아웃에 따라 JSON_OBJECT가 동적으로 생성.
결과 예시
getPagedUsers 호출 결과 (Users)
{
"currentPage": 1,
"totalPages": 10,
"totalItems": 100,
"data": [
{ "id": 1, "name": "John", "age": 30 },
{ "id": 2, "name": "Jane", "age": 25 }
]
}
getPagedProducts 호출 결과 (Products)
{
"currentPage": 1,
"totalPages": 5,
"totalItems": 50,
"data": [
{ "id": 1, "productName": "Laptop", "stock": 15 },
{ "id": 2, "productName": "Mouse", "stock": 200 }
]
}
장점 및 추가 고려사항
장점
- 유지보수성:
- JSON 필드와 SQL 쿼리가 분리되어 있어, 서로 독립적으로 관리 가능.
- 공통 페이징 로직 수정 시 다른 부분을 변경할 필요 없음.
- 확장성:
- 새로운 기능(테이블 또는 JSON 필드 추가)이 필요한 경우, 단순히 XML에 SQL 조각과 JSON 필드를 추가하면 됨.
고려사항
- SQL Injection 방지:
- 동적 SQL과 JSON 필드는 사전 정의된 ID만 사용하도록 제한하여 보안 강화.
- 복잡성 증가:
- 작은 규모의 프로젝트에는 오히려 복잡도를 높일 수 있으므로, 확장성이 필요한 경우에 적합.
이 방식이라면 동적 SQL과 JSON 필드를 완벽히 유연하게 제어할 수 있습니다. 😊
※ 필자주:
퍼포먼스를 위해 먼저 페이징 처리한 후 조인을 해야 할 경우에는 이 방식은 수정되어야 함!
'SpringBoot 를 배워보자' 카테고리의 다른 글
오라클 환경에서 MyBatis를 사용하여 SQL을 기능적으로 쪼개어 동적으로 조합하는 예 (0) | 2024.12.02 |
---|---|
Spring Boot 프로젝트에 Swagger UI 적용하여 API 문서 자동 생성하기: 상세 가이드 (0) | 2024.12.02 |
AspectJ Pointcut Expression Language (1) | 2024.11.17 |
스프링 부트 AOP(Aspect Oriented Programming) 심층 분석: 예제와 함께하는 상세 가이드 (0) | 2024.11.17 |
Log4j2: 강력하고 유연한 자바 로깅 프레임워크 심층 분석 (0) | 2024.11.17 |