본문 바로가기
  • writing on sticky note
잡학사전

트스테,테스트 그리고 트스테

by 도담대디 2025. 4. 13.

1. DB 테이블
CREATE TABLE SAVED_QUERY (
    QUERY_ID     NUMBER PRIMARY KEY,
    QUERY_NAME   VARCHAR2(100),
    SQL_TEXT     CLOB,
    DESCRIPTION  VARCHAR2(255)
);

2. 쿼리 저장 화면 - saveQuery.jsp
<form action="saveQuery.do" method="post">
  쿼리명: <input type="text" name="queryName"><br>
  설명: <input type="text" name="description"><br>
  SQL 입력:<br>
  <textarea name="sqlText" rows="10" cols="80"></textarea><br>
  <input type="submit" value="저장">
</form>

<c:if test="${not empty error}">
  <div style="color:red;">${error}</div>
</c:if>
3. 쿼리 저장 결과 - saveSuccess.j

<h3>쿼리 저장 완료</h3>
<p>쿼리 ID: ${queryId}</p>

<h4>예상 컬럼</h4>
<table border="1">
  <tr>
    <c:forEach var="col" items="${columns}">
      <th>${col}</th>
    </c:forEach>
  </tr>
</table>

<a href="runSavedQuery.do?queryId=${queryId}">쿼리 실행</a>

4. 쿼리 실행 화면 - runSavedQuery.jsp
<form action="runSavedQuery.do" method="post">
  <select name="queryId" onchange="this.form.submit();">
    <option value="">쿼리 선택</option>
    <c:forEach var="q" items="${savedQueries}">
      <option value="${q.queryId}" ${q.queryId == selectedId ? 'selected' : ''}>${q.queryName}</option>
    </c:forEach>
  </select>
</form>

<c:if test="${not empty paramList}">
  <form action="runSavedQuery.do" method="post">
    <input type="hidden" name="queryId" value="${selectedId}">
    <c:forEach var="param" items="${paramList}">
      ${param}: <input type="text" name="params[${param}]"><br>
    </c:forEach>
    <input type="submit" value="실행">
  </form>
</c:if>

<c:if test="${not empty resultList}">
  <table border="1">
    <tr>
      <c:forEach var="col" items="${columnNames}">
        <th>${col}</th>
      </c:forEach>
    </tr>
    <c:forEach var="row" items="${resultList}">
      <tr>
        <c:forEach var="col" items="${columnNames}">
          <td>${row[col]}</td>
        </c:forEach>
      </tr>
    </c:forEach>
  </table>
</c:if>

<c:if test="${not empty error}">
  <div style="color:red;">${error}</div>
</c:if>

5. Controller - QueryController.java
@Controller
public class QueryController {

    @Autowired
    private QueryService queryService;

    @RequestMapping("/saveQuery.do")
    public String saveQuery(
        @RequestParam String queryName,
        @RequestParam String sqlText,
        @RequestParam String description,
        HttpSession session,
        Model model
    ) {
        String role = (String) session.getAttribute("userRole");
        Map<String, Object> sessionParams = Map.of(
            "userId", session.getAttribute("userId"),
            "companyId", session.getAttribute("companyId")
        );

        try {
            int queryId = queryService.saveQuery(queryName, sqlText, description, role);
            List<String> columns = queryService.extractColumns(sqlText, sessionParams);
            model.addAttribute("queryId", queryId);
            model.addAttribute("columns", columns);
            return "saveSuccess";
        } catch (IllegalArgumentException e) {
            model.addAttribute("error", e.getMessage());
            return "saveQuery";
        }
    }

    @RequestMapping("/runSavedQuery.do")
    public String runQuery(
        @RequestParam int queryId,
        @RequestParam(required = false) Map<String, String> params,
        HttpSession session,
        Model model
    ) {
        String role = (String) session.getAttribute("userRole");
        String sql = queryService.getSqlById(queryId);
        List<String> hashParams = queryService.extractHashParams(sql);
        List<String> dollarParams = queryService.extractDollarParams(sql);

        if (params == null || params.isEmpty()) {
            model.addAttribute("paramList", hashParams);
            model.addAttribute("savedQueries", queryService.getAllSavedQueries());
            model.addAttribute("selectedId", queryId);
            return "runSavedQuery";
        }

        Map<String, Object> mergedParams = new HashMap<>(params);
        for (String key : dollarParams) {
            mergedParams.put(key, session.getAttribute(key));
        }

        try {
            List<Map<String, Object>> result = queryService.runDynamicQuery(sql, mergedParams, role);
            model.addAttribute("resultList", result);
            model.addAttribute("columnNames", result.isEmpty() ? List.of() : new ArrayList<>(result.get(0).keySet()));
        } catch (IllegalArgumentException e) {
            model.addAttribute("error", e.getMessage());
        }

        model.addAttribute("savedQueries", queryService.getAllSavedQueries());
        model.addAttribute("selectedId", queryId);
        return "runSavedQuery";
    }
}

6. Service - QueryServiceImpl.jav
@Service
public class QueryServiceImpl implements QueryService {

    @Autowired
    private QueryDAO dao;

    private static final List<String> FORBIDDEN_KEYWORDS = List.of(
        "DROP", "DELETE", "TRUNCATE", "ALTER", "UPDATE"
    );

    private void validateSafeSql(String sql, String role) {
        String upperSql = sql.toUpperCase();
        for (String keyword : FORBIDDEN_KEYWORDS) {
            if (upperSql.matches(".*\\b" + keyword + "\\b.*")) {
                if (!"ROLE_ADMIN".equals(role)) {
                    throw new IllegalArgumentException("금지된 SQL 문법이 포함되어 있습니다: " + keyword);
                }
            }
        }
    }

    @Override
    public int saveQuery(String name, String sql, String desc, String role) {
        validateSafeSql(sql, role);
        return dao.insert(name, sql, desc);
    }

    @Override
    public String getSqlById(int id) {
        return dao.findById(id);
    }

    @Override
    public List<Map<String, Object>> runDynamicQuery(String sql, Map<String, Object> params, String role) {
        validateSafeSql(sql, role);

        for (String key : extractDollarParams(sql)) {
            sql = sql.replace("${" + key + "}", "'" + String.valueOf(params.getOrDefault(key, "")) + "'");
        }

        List<Object> paramList = new ArrayList<>();
        Matcher m = Pattern.compile("#\\{(\\w+)}").matcher(sql);
        while (m.find()) paramList.add(params.get(m.group(1)));
        sql = sql.replaceAll("#\\{(\\w+)}", "?");

        return dao.execute(sql, paramList.toArray());
    }

    @Override
    public List<String> extractColumns(String sql, Map<String, Object> sessionParams) {
        for (String key : extractDollarParams(sql)) {
            sql = sql.replace("${" + key + "}", "'" + String.valueOf(sessionParams.getOrDefault(key, "")) + "'");
        }

        List<Object> dummy = new ArrayList<>();
        Matcher m = Pattern.compile("#\\{(\\w+)}").matcher(sql);
        while (m.find()) dummy.add("");
        sql = sql.replaceAll("#\\{(\\w+)}", "?");

        return dao.extractColumns("SELECT * FROM (" + sql + ") WHERE 1=0", dummy.toArray());
    }

    public List<String> extractHashParams(String sql) {
        List<String> list = new ArrayList<>();
        Matcher m = Pattern.compile("#\\{(\\w+)}").matcher(sql);
        while (m.find()) list.add(m.group(1));
        return list;
    }

    public List<String> extractDollarParams(String sql) {
        List<String> list = new ArrayList<>();
        Matcher m = Pattern.compile("\\$\\{(\\w+)}").matcher(sql);
        while (m.find()) list.add(m.group(1));
        return list;
    }

    public List<SavedQuery> getAllSavedQueries() {
        return dao.findAll();
    }
}
7. DAO - QueryDAO.java

@Repository
public class QueryDAO {

    @Autowired
    private JdbcTemplate jdbc;

    public int insert(String name, String sql, String desc) {
        return jdbc.update("INSERT INTO SAVED_QUERY (QUERY_ID, QUERY_NAME, SQL_TEXT, DESCRIPTION) VALUES (SEQ_QUERY.NEXTVAL, ?, ?, ?)",
                name, sql, desc);
    }

    public String findById(int id) {
        return jdbc.queryForObject("SELECT SQL_TEXT FROM SAVED_QUERY WHERE QUERY_ID = ?", String.class, id);
    }

    public List<Map<String, Object>> execute(String sql, Object[] params) {
        return jdbc.queryForList(sql, params);
    }

    public List<String> extractColumns(String sql, Object[] params) {
        return jdbc.query(sql, params, (ResultSet rs) -> {
            ResultSetMetaData meta = rs.getMetaData();
            List<String> cols = new ArrayList<>();
            for (int i = 1; i <= meta.getColumnCount(); i++) {
                cols.add(meta.getColumnName(i));
            }
            return cols;
        });
    }

    public List<SavedQuery> findAll() {
        return jdbc.query("SELECT QUERY_ID, QUERY_NAME FROM SAVED_QUERY",
            (rs, rowNum) -> new SavedQuery(rs.getInt("QUERY_ID"), rs.getString("QUERY_NAME")));
    }
}