728x90
728x90
수백 개의 동적 SQL 조각을 효율적으로 관리하려면, 디렉토리 구조를 기능별로 구성하고, 공통 부분은 상위 디렉토리에 두는 방식이 적합합니다. 이를 MyBatis의 XML Mapper와 디렉토리 구조를 활용하여 설계하는 방안을 아래와 같이 제안드립니다.
2024.11.30 - [오라클 데이터베이스 강좌] - Oracle에서 JSON 형태로 데이터 생성하기
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 조각 및 관련 매퍼를 배치.
728x90
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가 동적으로 생성.
728x90
결과 예시
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 필드를 완벽히 유연하게 제어할 수 있습니다. 😊
※ 필자주:
퍼포먼스를 위해 먼저 페이징 처리한 후 조인을 해야 할 경우에는 이 방식은 수정되어야 함!
728x90
728x90
'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 |