Загружаем каталог…
Загружаем каталог…
출근 첫 주가 끝나고 주말 일을 하다가 복기하면서 문득 궁금해진 것들을 정리했다. 그런데 세 개를 다 확인해보니 내가 알던 게 전부 틀렸거나 반쯤 틀렸다. 그리고 공통점이 있었다. 1. JSP는 HTML에 JS를 끼워넣는 것인가 내가 알던 것 "JSP는 HTML 파일에 <script> 태그 사이로 자바스크립트 기능을 동적으로 끼워넣는 것" 뭔가 기능은 맞는 것 같은데 핵심을 놓친 느낌이 계속 들었다. 그럴 만했다. 실제 — 자바스크립트가 아니라 자바다 <%@ page contentType="text/html; charset=UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <body> <c:forEach var="dto" items="${freeBoardList}"> <tr><td>${dto.title}</td></tr> </c:forEach> </body> </html> 이 코드는 서버에서 실행된다. 브라우저는 이걸 본 적이 없다. [서버] JSP 를 읽는다 → ${freeBoardList} 를 실제 데이터로 치환 → <c:forEach> 를 돌려서 <tr> 을 여러 개 만든다 → 완성된 HTML 을 만든다 ↓ [브라우저] <tr><td>안녕하세요</td></tr> <tr><td>반갑습니다</td></tr> ← 이것만 받는다 브라우저가 받는 HTML 에는 <c:forEach> 도 ${...} 도 없다. 서버가 다 처리하고 결과만 보낸다. 반대로 브라우저의 소스 보기에 <c:forEach> 나 ${...} 가 그대로 보이면 서버가 처리하지 않은 것이다. 맨 위 taglib 선언이 빠지면 <c:...> 태그가 글자 그대로 나가고, web.xml 이 2.4 미만 버전이면 EL 이 꺼져서 ${...} 가 그대로 나간다. 한 줄 정의 HTML 안에 자바 코드를 섞어서, 서버가 HTML을 만들어내는 기술. "동적"의 주체가 서버다. 요청할 때마다 다른 HTML이 만들어진다. 값이 HTML 의 일부가 된다는 뜻이기도 하다. ${dto.title} 은 값을 escape 없이 그대로 넣는다. 게시판 제목처럼 사용자가 쓴 값은 <c:out value="${dto.title}"/> 로 찍어야 < , > 가 태그로 해석되지 않는다. JS 로 목록을 그릴 때도 같다. 문자열로 이어 붙여 .html() 에 넣는다면 escape 를 거치거나 .text() 로 넣는다. 그럼 내가 말한 건 뭐였나 <script> $("#tb").html(html); </script> 이건 브라우저에서 실행되는 자바스크립트다. JSP와 아무 상관이 없다. JSP 파일 안에 <script> 를 쓸 수 있어서 헷갈린 것이다. <%-- JSP 파일 --%> <c:forEach var="dto" items="${list}"> ← 서버가 실행 <tr><td>${dto.title}</td></tr> </c:forEach> <script> function searchFn() { ... } ← 브라우저가 실행 </script> 한 파일 안에 두 세계가 공존한다. 그게 JSP 를 처음 볼 때 혼란스러운 이유였다. 실행 시점이 다르다 JSP/EL/JSTL 서버가 HTML 을 만들 때 한 번 JavaScript 브라우저가 HTML 을 받은 뒤, 계속 이 차이 때문에 실제로 막힌 적이 있다. 검색 조건 <select> 를 바꿀 때마다 다른 입력창을 띄워야 했는데, <c:choose> 로 하려다 실패했다. <!-- [오개념] 이건 "페이지가 처음 뜰 때" 만 판단한다 --> <c:choose> <c:when test="${searchType eq '6'}"> <input type="text" name="startDate"> </c:when> </c:choose> 사용자가 select 를 바꾸는 건 브라우저에서 일어나는 일이라, 이미 끝난 JSTL 은 반응할 수 없다. // [정답] 미리 다 만들고 JS 로 숨긴다 function changeFn() { $("#case1, #case2, #case3").hide(); var v = $("#selection").val(); if (v == "6") $("#case3").show(); } 판단 기준 서버가 아는 값을 그린다 → JSTL / EL 사용자 조작에 반응한다 → JavaScript "누가 그 값을 알고 있느냐." 이 기준 하나로 정리된다. 검색 후 "아까 고른 옵션을 유지"하는 건 서버가 아는 값이니 EL 이 맞다. ${paramMap.searchType eq '4' ? 'selected' : ''} 같은 식으로. paramMap 은 컨트롤러가 model 에 담아 줘야 보이고, 안 담았다면 EL 기본 객체로 ${param.searchType ...} 이라 쓴다. 페이지를 서버가 다시 그릴 때 얘기고, AJAX 로 목록만 바꾸면 select 는 고른 그대로 남는다. 결정적 사실 — JSP는 서블릿이다 톰캣이 .jsp 파일을 자바 소스로 변환해서 컴파일한다. // 톰캣이 자동 생성한 freeBoardMain_jsp.java (패키지·implements·throws 는 줄였다) public final class freeBoardMain_jsp extends HttpJspBase { public void _jspService(HttpServletRequest request, HttpServletResponse response) { out.write("<html>\n<body>\n"); // ... } } 톰캣의 work/ 폴더에 이 파일이 실제로 생긴다. 열어보면 out.write("<html>") 같은 코드가 줄줄이 나온다. 이클립스에서 띄웠다면 워크스페이스의 .metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/프로젝트명/org/apache/jsp/ 아래다. WEB-INF 폴더는 WEB_002dINF 로 바뀌어 있다. 서블릿 자바 코드 안에 HTML 을 문자열로 섞는다 JSP HTML 안에 자바 코드를 섞는다 방향이 반대일 뿐 같은 것이다. 화면을 만들 때는 후자가 편해서 JSP 가 나왔다. 정리해서 말하면 JSP는 HTML 안에 자바 코드를 넣어 서버에서 동적으로 HTML을 생성하는 기술입니다. 톰캣이 JSP를 서블릿 자바 코드로 변환하고 컴파일해서 실행하기 때문에, 실제로는 서블릿과 같은 것입니다. 브라우저는 JSP 코드를 받지 않고 완성된 HTML만 받습니다. 2. SqlSessionTemplate 과 @Mapper 인터페이스 두 방식을 다 겪었다 // 개인 프로젝트에서 쓰던 방식 mapper.selectBoardList(boardCode, rowBounds); // 회사에서 보는 방식 sqlSessionTemplate.selectList("freeBoardGetList", param); 둘 다 XML 의 쿼리를 불러와 SQL 을 실행하는 건 같은데, 왜 이름과 형태가 다른지 궁금했다. 답 — 같은 것의 두 가지 표현이다 @Mapper 인터페이스 ↓ MyBatis 가 실행 시점에 구현체를 자동 생성 (동적 프록시) ↓ 메서드명 → XML 의 id 로 매칭 ↓ 내부적으로 SqlSession.selectList("...selectBoardList", param) 호출 인터페이스 방식이 결국 SqlSessionTemplate 방식을 대신 해주는 것이다. @Mapper public interface NeighborhoodMapper { List<Neighborhood> selectBoardList(int boardCode, RowBounds rowBounds); } 구현 클래스가 없는데 동작하는 게 신기했는데 , MyBatis 가 실행 시점에 만들어주는 것이었다. <mapper namespace="com.zipinfo.project.neighborhood.model.mapper.NeighborhoodMapper"> ↑ 인터페이스의 전체 경로와 일치해야 한다 <select id="selectBoardList"> ← 메서드명과 일치 namespace + id 를 합치면 SqlSessionTemplate 에 넘기는 문자열이 된다. 회사 코드처럼 "freeBoardGetList" 만 넘겨도 되는 건 MyBatis 가 짧은 id 도 같이 등록하기 때문이다. 다른 매퍼에 같은 id 가 생기면 짧은 id 호출은 is ambiguous in Mapped Statements collection 으로 터진다. 이것도 실행해봐야 안다. 차이점 SqlSessionTemplate Mapper 인터페이스 쿼리 지정 문자열 메서드 호출 오타 발견 런타임 메서드는 컴파일, XML id 는 런타임 파라미터 타입 Object 타입 지정 반환 타입 캐스팅 필요할 때 있음 선언된 타입 IDE 자동완성 안 됨 됨 여러 파라미터 Map 으로 묶어야 @Param 사용 가능 실제로 이 차이를 겪었다 Mapper 에서 쿼리 두 개를 하나로 합치면서 freeBoardSearchList 를 지웠는데, Service 가 아직 그 id 를 부르고 있었다. Mapped Statements collection does not contain value for freeBoardSearchList sqlSessionTemplate.selectList("freeBoardSearchList", paramMap); // ↑ 오타든 삭제된 id든 실행해봐야 안다 인터페이스 방식이었으면 메서드를 지우는 순간 컴파일 단계에서 "그런 메서드 없다"고 잡혔을 것이다. 단 XML 의 <select> 만 지우고 메서드를 남기면 컴파일은 통과하고, 처음 호출할 때 Invalid bound statement (not found) 로 터진다. 그리고 문제가 하나 더 있었다. return 문은 고쳤는데 위쪽 로그 줄이 옛 id 를 그대로 쓰고 있었다. System.out.println(sqlSessionTemplate.selectList("freeBoardSearchList", paramMap)); // ← 여기서 터짐 return sqlSessionTemplate.selectList("freeBoardGetList", paramMap); // ← 도달 못 함 문자열이라 Ctrl+F 로 찾지 않으면 놓친다. 메서드였으면 지우는 순간 IDE 가 부르는 곳을 전부 빨갛게 표시했을 것이다. 이 코드에는 다른 문제도 있다. 로그를 찍으려고 쿼리를 한 번 더 실행하고 있다. 결과를 변수에 담아 쓰는 게 맞다. 왜 회사는 옛날 방식인가 iBatis 2.x (~2010) 문자열 id 방식뿐. Spring 에서는 SqlMapClientTemplate MyBatis 3.0 (2010) SqlSession 문자열 방식과 Mapper 인터페이스가 처음부터 같이 있었다 mybatis-spring 1.0 (2010) SqlSessionTemplate 과 MapperFactoryBean 이 같이 나왔다 MyBatis 3.4 (2016) @Mapper 어노테이션 추가 현재 인터페이스 방식이 권장 SqlSessionTemplate 이 옛날 방식인 게 아니라, 문자열 id 로 부르는 습관이 iBatis 시절에서 온 것이다. MyBatis 3 와 mybatis-spring 은 처음부터 두 방식을 같이 줬다. 레거시 프로젝트는 만들 당시의 방식이 그대로 남아 있다. 그리고 한번 정한 방식을 중간에 바꾸지 않는다. 같은 맥락 — Service / ServiceImpl 개인 프로젝트 NeighborhoodService (인터페이스) + NeighborhoodServiceImpl (구현) 회사 FreeBoardService (클래스 하나) 인터페이스 분리는 "구현을 갈아끼울 수 있게" 하려는 것인데, 실제로 갈아끼우는 일이 거의 없어서 요즘은 생략하기도 한다. 옛 Spring 에서는 인터페이스가 있느냐가 AOP 프록시 방식을 갈랐다. Spring 3.2 전에는 인터페이스가 없는 클래스에 트랜잭션 같은 AOP 를 걸려면 CGLIB 라이브러리를 따로 넣어야 했고, 인터페이스가 있으면 JDK 동적 프록시로 끝났다. 3.2부터는 CGLIB 가 Spring 안에 들어 있어서 라이브러리를 따로 넣을 일이 없다. 둘 다 흔한 방식이다. 프로젝트 관례를 따르면 된다. 정리 원리 같다. 인터페이스 방식이 내부적으로 SqlSession 을 호출한다 차이 타입 안전성과 IDE 지원 현대적 인터페이스 방식 현장 둘 다 본다. 레거시일수록 SqlSessionTemplate 두 방식을 다 해본 게 오히려 유리하다. 어느 프로젝트에 가도 읽을 수 있다. 3. serialize() 없으면 여러 개를 못 보내나 내가 알던 것 "데이터를 둘 이상 백엔드로 넘긴다면 serialize() 를 한다" 검색 기능을 만들 때 기간 검색만 입력칸이 두 개였다. 통일성을 위해 전부 serialize() 로 보내야 한다고 생각했다. 그런데 최종 코드에는 serialize() 가 없었다. 왜 그랬는지 정리해봤다. 먼저 — 전제가 틀렸다 data : { searchType : "6", startDate : "20260101", endDate : "20261231" } $.ajax 의 data 에 객체를 주면 jQuery 가 알아서 직렬화한다. searchType=6&startDate=20260101&endDate=20261231 serialize() 를 쓴 것과 결과가 똑같다. 둘 다 key=value&key=value 형태로 나간다. serialize() 폼 안의 name 붙은 것을 자동 수집 객체 직접 작성 내가 고른 것만 담는다 serialize() 는 "폼 요소를 자동으로 수집"하는 편의 기능일 뿐이다. 여러 개를 보내는 것과는 별개 문제다. 그리고 검색 화면은 조건이 안 맞았다 serialize() 가 무엇을 담는지는 세 가지로 정해진다. 대상 <form> 에 부르면 안의 입력 요소를 모은다 <div> 에 부르면 에러 없이 빈 문자열이다. 안의 입력 요소를 직접 골라야 한다 name name 속성이 있는 요소만 담긴다 제외 disabled, 버튼류, type="file", 체크 안 된 체크박스·라디오는 빠진다. 숨긴 요소는 안 빠진다 문제 1 — <form> 이 없었다 <div> <select id="selection" onchange="changeFn()">...</select> <span id="case1" style="display:none">...</span> <span id="case2" style="display:none">...</span> <span id="case3" style="display:none">...</span> <button onclick="searchFn()">검색</button> </div> <div> 로만 감싸져 있었다. 여기에 바로 serialize() 를 부르면 빈 문자열이 나온다. 폼으로 감싸거나, div 에 id 를 붙여 $("#searchArea :input").serialize() 처럼 입력 요소를 직접 골라야 했다. select 에는 name 도 없어서 그대로는 searchType 부터 빠진다. 문제 2 — 숨긴 요소도 전송된다 이게 더 큰 문제다. display:none 이어도 disabled 가 아니면 값이 나간다 name 을 칸마다 따로 붙였다면( searchType , typeQuery , textQuery , startDate , endDate ), "제목" 검색을 골라도 서버가 받는 건 이렇게 된다. {searchType=4, typeQuery=01, textQuery=카페, startDate=, endDate=} ↑ 안 보이는데 나간다 어떤 값을 써야 하는지 서버가 판단해야 한다. 막으려면 hide() 할 때마다 disabled 를 걸고, show() 할 때 다시 풀어야 한다. $("#case1, #case2, #case3").hide() .find("input, select").prop("disabled", true); $("#case3").show() // 6번일 때 .find("input, select").prop("disabled", false); // 보여줄 칸은 다시 푼다 코드가 늘어난다. $("#searchArea :input:visible").serialize() 처럼 보이는 것만 고르는 방법도 있다. 대신 type="hidden" 도 안 보이는 요소로 쳐서 같이 빠진다. 문제 3 — name 충돌 이게 결정적이었다. 서버가 기대하는 것 searchType=4 & query=카페 ← 2~5번 searchType=6 & startDate=... & endDate=... ← 6번 query 라는 이름으로 보내야 하는데 입력칸이 두 개다. <select name="query" id="typeQuery"> <!-- 1번용 --> <input name="query" id="textQuery"> <!-- 2~5번용 --> name 이 같으면 serialize() 가 둘 다 담는다. query=01&query=카페 서버에서 Map<String,String> 으로 받으면 첫 번째 값 하나만 들어온다. 마크업에서 select 가 먼저라, "카페"를 입력해도 서버는 01 을 받는다. String 하나로 받으면 01,카페 로 붙어서 온다. 그래서 택한 방식 var value = $("#selection").val(); var data = { searchType : value }; if (value == "1") { data.query = $("#typeQuery").val(); } else if (value == "2" || value == "3" || value == "4" || value == "5") { data.query = $("#textQuery").val(); } else if (value == "6") { data.startDate = $("#startDate").val(); data.endDate = $("#endDate").val(); } 필요한 것만 골라 담는다. 1~5번 { searchType, query } 6번 { searchType, startDate, endDate } 보내는 방식은 하나고 담기는 키만 다르다. 원래 원했던 "통일성"이 이 부분이다. 나중에 페이지네이션을 붙일 때 data.page = page 한 줄만 추가하면 됐다. 조건부로 담는 구조라 확장이 쉬웠다. 그럼 serialize() 는 언제 쓰나 상세/수정 화면에서는 썼다. data : $("#detailForm").serialize() <form id="detailForm"> <input type="hidden" name="num" value="${freeBoardDto.num}"/> <select name="codeType">...</select> <input type="text" name="name" value="..."/> <input type="text" name="title" value="..."/> <textarea name="content">...</textarea> </form> 여기는 조건이 다 맞았다. <form> 으로 감싸져 있다 모든 요소에 name 이 있다 숨기거나 조건부로 보이는 게 없다 필드가 다섯 개라 손으로 담으면 길다 판단 기준 serialize() 가 좋을 때 폼 형태가 고정되어 있다 필드가 많다 전부 보내면 된다 직접 담는 게 좋을 때 조건에 따라 보낼 항목이 달라진다 숨겨진 요소가 있다 보내기 전에 가공이 필요하다 검색은 후자였다. "전체" 보기를 빼면 조건이 6개고 각각 다른 입력칸을 쓰니까. 정리 틀린 표현 "여러 개를 보내려면 serialize" 맞는 표현 "폼 전체를 통째로 보낼 때 serialize" 세 가지의 공통점 정리하고 보니 셋이 같은 종류의 오해였다. JSP "브라우저에서 JS 를 실행하는 것" → 서버에서 HTML 을 만드는 것 Mapper "다른 방식" → 같은 것을 대신 해주는 것 serialize "여러 개를 보내는 방법" → 폼을 수집해주는 것 전부 "어디서 실행되는가" 혹은 "무엇을 대신해주는가" 의 문제였다. 도구가 대신해주는 걸 모르면 Mapper 인터페이스 SqlSession 호출을 대신한다 serialize() 폼 요소 수집을 대신한다 jQuery DOM API 호출을 대신한다 Spring MVC URL 마다 서블릿을 만드는 일을 대신한다
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
게시판 구축의 오해와 해답 (JSP / MAPPER / SERIALIZE). 출근 첫 주가 끝나고 주말 일을 하다가 복기하면서 문득 궁금해진 것들을 정리했다. 그런데 세 개를 다 확인해보니 내가 알던 게 전부 틀렸거나 반쯤 틀렸다. 그리고 공통점이 있었다. 1. JSP는 HTML에 JS를 끼워넣는 것인가 내가 알던 것 "JSP는 HTML 파일에 태그 사이로 자바스크립트 기능을 동적으로 끼워넣는 것" 뭔가 기능은 맞는 것 같은데 핵심을 놓친 느낌이 계속 들었다. 그럴 만했다. 실제 — 자바스크립트가 아니라 자바다 ${dto.title} 이 코드는 서버에서 실행된다. 브라우저는 이걸 본 적이 없다. [서버] JSP 를 읽는다 → ${freeBoardList} 를 실제 데이터로 치환 → 를 돌려서 을 여러 개…
Открыть источник