Spring

반응형

<trim>

 

속성


prefix : 실행될 쿼리의 <trim> 문 안에 쿼리 가장 앞에 붙여준다.

UPDATE board <trim prefix="SET"> username=#{username},password=#{password}</trim>

 

prefixOverrides : 실행될 쿼리의 <trim> 문 안에 쿼리 가장 앞에 해당하는 문자들이 있으면 자동으로 지워준다.

SELECT * FROM board WHERE id = #{id} 
<trim prefixOverrides="OR">OR TT LIKE '%' || #{searchContent} || '%' </if> 

 

suffix : 실행 될 쿼리의 <trim> 문 안에 쿼리 가장 뒤에 붙여준다.

<trim suffix=")"></trim>

 

suffixOverrides : 실행될 쿼리의 <trim> 문 안에 쿼리 가장 뒤에 해당하는 문자들이 있으면 자동으로 지워준다.

<trim suffixOverrides=","></trim>

 

 

문법


<select id="test" resultType="user">
SELECT * FROM user WHERE id = #{id}  
<trim prefix="AND (" prefixOverrides="OR" suffix=")">
        <if test="para1 != null">
        OR para1 = #{data1}
        </if>
        <if test="para2 != null">
        OR para2 = #{data2}
        </if>
</trim>
</select>

설명 

0.<trim prefix="AND (" prefixOverrides="OR" suffix=")"> 이 부분을 해석하자면

1. 먼저 prefix 속성이 'AND'로 돼있기 때문에 맨 앞에 'AND'가 붙습니다.

2.prefixOverrides 속성이 쿼리 중에 'OR' 텍스트를 찾고, 찾게 되면 'OR' 텍스트를 제거합니다.

3. 그리고 suffix 속성이 <trim> 문 맨 마지막에 ')'를 닫아줍니다.  

 

실행될 쿼리 (para1, para2 값이 들어올 경우)

SELECT * FROM user WHERE id = '119'
AND para1 = 'java119' OR para2 = 'java119' 

 

 

실전 예제

 

prefix, suffixOverrides 사용 예시
1. 맨 앞에 'SET' 붙이고 맨 끝에 있는 콤마(,)를 제거하기

<update id="updateUser">
UPDATE user
	<trim prefix="SET" suffixOverrides=",">
		<if test="username != null">username=#{username},</if>
		<if test="password != null">password=#{password},</if>
		<if test="email != null">email=#{email},</if>
		<if test="bio != null">bio=#{bio},</if>
	</trim>
WHERE id=#{id}
</update>

 

prefix, prefixOverrides 사용

2. 맨 앞에 있는 연산자를(AND 또는 OR) 제거하는 경우

<select id="selectInfo" resultType="user">
	 SELECT * FROM USER
	 <trim prefix="WHERE" prefixOverrides="AND |OR">
		<if test="username != null">AND username=#{username}</if>
		<if test="password != null">OR password=#{password}</if>
		<if test="email != null">AND email=#{email}</if>
	</trim>
</select>

 

prefix, prefixOverrides, suffixOverrides 사용
3. 맨 앞에 'SET' 붙이고 맨 앞, 맨 끝에 있는 콤마(,)를 제거하기

<update id="updateTable">
	UPDATE TABLE
	<trim prefix="SET" prefixOverrides="," suffixOverrides="," >
		<if test="aFlag != null">
			, A_FLAG = #{aFlag}
		</if>

		<if test="bFlag != null">
			, B_FLAG = #{bFlag}
		</if>

		<if test="cFlag != null">
			, C_FLAG = #{cFlag} ,
		</if>
	</trim>
	WHERE KEY = #{key}
</update>

 

prefix, prefixOverrides, suffix 사용

4. 맨 앞에 'AND (' 붙이고 맨 앞 'OR' 제거하고 맨 끝에 ')' 붙이기 

<select id="searchUser">

<trim prefix="AND (" prefixOverrides="OR" suffix=")">
 <if test="searchCategory0 != null"> 
  OR TT LIKE '%' || #{searchContent} || '%' 
 </if>  
 <if test="searchCategory1 != null"> 
  OR DS LIKE '%' || #{searchContent} || '%' 
  </if>
  <if test="searchCategory2 != null"> 
   OR WRT_MPR_NM LIKE '%' || #{searchContent} || '%' 
  </if>
</trim>
</select>

 

반응형
반응형

<sql>

 

개념


다른 구문에서 재사용하기 위한 SQL 조각

출처 : mybatis 공식 사이트

말 그대롭니다. "재사용성을 높이기 위한 SQL 조각" 아주 정확한 표현입니다.

 

 

조건


1.id 속성 값이 필수입니다.
2.사용하려는 태그보다 먼저 선언되어야 합니다.(위에 존재해야 합니다.)

 

문법


<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>

 

<include> 

<property>

 

개념


<sql> 문을 DML(Data Manipulation Language) 태그에 삽입하는 기술

 

 

문법


<select,insert,update,delete>

<include refid="<sql> id"><property name="<sql> property" value=""/></include>

</select,insert,update,delete>

 

<sql> + <include><property> 설명

//no property 
<sql id="example01">
  FROM
</sql>

//property 한개 작성
<sql id="example02">
  FROM ${alias(별칭)}
</sql>

//property 여러 개 작성
<sql id="example03">
  FROM ${alias(별칭)} WHERE id = ${alias02(별칭)}
</sql>
<select id="selectUser" resultType="User">
SELECT id,name
<include refid="example03">
  <property name="alias" value="tablename"/>
  <property name="alias02" value="119"/>
</include>
</select>

실행될 쿼리

SELECT id,name FROM tablename WHERE id = 119

 

설명

0.<sql> 문에는 parameter를 넘길 수 없으므로 property를 사용한다. ex) ${alias},${tablename}..

1. <sql> id 속성 == <include> refid 속성

2. <sql> ${alias(별칭)} == <property> name 속성  

3. <property> value 속성 : ${alias}에 들어갈 값

 

이 네가지만 기억하시면 됩니다.


<sql> + <include> 실전 예제

 

☞ 참고 : <sql> property는 꼭 $로 작성하셔야 합니다.

☞ 참고 02 : <sql> 문에선 property 를 <if> , <bind> 태그에 변수로 인식하지 못합니다.

 

1.Table 문법 재사용

<sql id="returnTable">
  from ${tableproperty}
</sql>
<include refid="returnTable">
  <property name="tableproperty" value="tablename"/>
</include>

 

2.JOIN문 활용 (MyBatis 공식 사이트 예제)

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

 

3.SELECT 칼럼 문법 재사용

<sql id="common_select_table">id, name, age</sql>
<select id="getMemberInfo">
    SELECT
        <include refid="common_select_table" />
    FROM
        TABLE
</select>

 

4.INSERT 문 활용

<sql id="board_columns">
    ${alias}id,
    ${alias}subject,
    ${alias}context,
    ${alias}attachments,
    ${alias}likes,
    ${alias}views,
    ${alias}create_time,
    ${alias}update_time,
    ${alias}writer
</sql>

<insert id="insertBoard">
    INSERT INTO BOARD (
           <include refid="board_columns"><property name="alias" value=""/></include>
         ) VALUES (
          #{id},
          #{subject},
          #{context},
          #{attachments},
          #{likes},
          #{views},
          now(),
          null,
          #{writer} )
</insert>

외부 SQL-Mapper.xml + <include> 사용하기 


board-Mapper.xml 에서 common-Mapper.xml <sql> 사용(접근) 하기

 

common-Mapper.xml

<mapper namespace="mapper.common-Mapper">

<sql id="board_col">
${alias}id,
${alias}subject,
${alias}context
</sql>

</mapper>

board-Mapper.xml

<select id="selectUser">
    SELECT <include refid="mapper.common-Mapper.board_col">
    <property name="alias" value="b."/></include>
      FROM board b
</select>

설명

사용 할 외부 Mapper.xml에 namespace.<sql> id로 가져오면 됩니다.

반응형
반응형

먼저 절대 경로, 상대 경로를 설명하기 전
학창 시절로 가서 절대 평가, 상대 평가를 생각하면서 이 글을 보면 이해가 쉽습니다.

 

절대 경로

 

개념


최초의 시작점으로 경유한 경로를 전부 기입하는 방식

경로의 처음부터 마지막까지 완전히 적힌 경로
어떠한 웹페이지나 파일이 가지고 있는 고유한 경로

최상위 디렉토리가 반드시 포함된 경로

 

 

실전 예시


Browser

http://www.google.com

Windows

C:\chrome\chrome_shutdown_ms.txt

Linux

cd $CATALINA_HOME/bin/

 

윈도(Windows) 절대 경로

 

C:\chrome\


Spring 절대 경로

 

 

servlet-context.xml

<resources mapping="/resources/**" location="/resources/" />

JSP

<img alt="Cat pictures" src="/resources/img/cat01.jpg">

결과

 

 

상대 경로

 

개념


현재 위치한 곳을 기준으로 해서 목표로 하는 (파일이 있는 곳) 위치이다.
상대 경로는 항상 비교할 대상이 있어야 합니다. 결국 내가 어디있냐에 따라 경로가 달라지는 것! 

내 위치와 파일 경로를 비교하는 것! (상대평가처럼)

현재 디렉터리(비교 대상)를 기준으로 작성된 경로

 

 

실전 예시


예시 URL : http://localhost:8080/project 01/abc.jsp

 

 

Spring 상대 경로

 

servlet-context.xml

<resources mapping="/resources/**" location="/resources/" />

 

JSP

${pageContext.request.contextPath} : 내 현재 위치 ( EL(Expression Language) 사용 )

<img src="${pageContext.request.contextPath}/resources/img/cat01.png" />

결과

설명


 ${pageContext.request.contextPath} 사용함으로 써 상대 경로 즉, 내 위치에 따라 값이 변하던 게 고정이 된다.

정확히 말하면 고정이 아니라 알아서 위치를 찾아서 맞춰주는 겁니다. 

예를 들면 부모 폴더에 접근하려면 ../resources/img/cat01.png 이런 식으로 쓰고

소속 폴더에 접근하려면 ./resources/img/cat01.png 써야 하는 걸 알아서 맞춰주는 겁니다.

 

 

그 외 표현식

${pageContext.request.requestURL} : http://localhost:8080/project01/abc.jsp

${pageContext.request.scheme} : http 

${pageContext.request.serverName} : localhost 

${pageContext.request.serverPort} : 8080 

${pageContext.request.requestURI} : /project01/abc.jsp

${pageContext.request.servletPath} : /abc.jsp

 

 

Java

 

HttpServletRequest request

String root = request.getContextPath(); // return 프로젝트 Path

결과 : /project01

String uri = request.getRequestURI(); // return 프로젝트+파일경로

결과 : /project01/abc.jsp

String servlet = request.getServletPath(); // return 파일명

결과 : /abc.jsp

StringBuffer url = request.getRequestURL(); // return 전체 경로

결과 : http://localhost:8080/project01/abc.jsp

 

 

경로 표기법

 

상대 경로

. : 현재 웹페이지가 소속된 폴더
.. : 현재 웹페이지의 부모 폴더

 

절대 경로

 

/ : 루트(root)

./ : 현재 위치

../ : 현재 위치의 상단 폴더

 

반응형
반응형

MyBatis 속성

 

<bind>

 

개념


외부에서 전달된 파라미터를 이용하여 변수 생성하는 엘리먼트

동적 쿼리 변수를 생성할 때 사용한다.

 

 

조건


MyBatis version 3.2.3 이상

 

 

문법


동적 쿼리문 안에 작성

<select | insert | update | delete>

<bind name="지정할 변수이름" value="파라미터 값+부가 옵션"/>

</select | insert | update | delete>

name 속성 : 자기가 지정할 변수 이름

value 속성 : 받아오는 파라미터 값 + 추가 문법 (이때 문법은 OGNL(Object Graph Navigation Language) 표현식을 사용한다.)

 

 

실전 예시 1. Like 문 문법

Parameter 01 : id

Parameter 02 : subject

<select id="getTest" resultType="board">

SELECT * FROM board

<bind name="ids" value="'%'+id+'%'"/>
<bind name="subjects" value="'%'+subject+'%'"/>
<where>
<if test="id != null"> AND id like #{ids}</if>
<if test="subject != null"> AND subject like #{subjects} </if>
</where>

</select>

 

이런식으로 value 속성에 받아온 파라미터를 작성하고 추가 문법을 덧붙이면 됩니다.

 

실제 실행된 쿼리

SELECT * FROM board WHERE id like '%2%' AND subject like '%test%'

실전 예시 2. Map 사용 문법

<bind name="a" value="hMap.get('a'.toString())"/>

<bind name="b" value="hMap.get('b'.toString())"/>

<bind name="c" value="hMap.get('c'.toString())"/>

실전 예시 3. 메서드(Method) 사용 문법

 

bind 사용 전

<select id="selectPerson" parameterType="String" resultType="hashmap">
  SELECT * FROM PERSON WHERE FIRST_NAME like #{name.upperCase() + '%'} 
</select> 

bind 사용 후

<select id="selectPerson" parameterType="String" resultType="hashmap">
  <bind name="nameStartsWith" value="_parameter.getName().upperCase() + '%'"/>
  SELECT * FROM PERSON WHERE FIRST_NAME like #{nameStartsWith} 
</select>
반응형
반응형

MyBatis 속성

 

<where>

 

개념


추가 쿼리 문의 내용을 <where> 문 안에 작성하면 첫 조건이 AND로 시작할지라도 WHERE로 치환해준다.

 

 

조건


MyBatis version 3.2.3 이상

 

 

문법


<select id="id">

SELECT * FROM table

<where>
추가 SQL
</where>

</select>

 

잘못된 문법 예시 1.

<select id="getTest" resultType="board">

SELECT * FROM board

<if test="id != null">WHERE id = #{id} </if>
<if test="subject != null">AND subject = #{subject} </if>

</select>

만약 이런 식으로 작성하면

 

1-1."subject" 칼럼이 먼저 올 경우

SELECT * FROM board AND subject = #{subject}

문법 에러가 나게 된다. 이때 <where> 문을 써주면 된다.


실전 예시 1.

<select id="getTest" resultType="board">

SELECT * FROM board

<where>
<if test="id != null">AND id = #{id} </if>
<if test="subject != null">AND subject = #{subject} </if>
</where>

</select>

이런식으로 작성하게 되면

 

실제 쿼리는 이런 식으로 작성된다.

 

1-1."id" 칼럼 값만 있을 경우

SELECT * FROM board WHERE id = #{id}

1-2."subject" 칼럼 값만 있을 경우

SELECT * FROM board WHERE subject = #{subject}

1-3. 두 컬럼 모두 값이 있을 경우

SELECT * FROM board WHERE id = #{id} AND subject = #{subject}

#WHERE AND가 되지 않고 알아서 문법에 맞게 치환 해줍니다.

 

 

그냥 (AND || OR ) 뭐가 먼저 들어올지 모르는 매개변수 값에 <where> 문을 쓰면 됩니다. 

반응형
반응형

시작하기에 앞서 참고 자료

 

*ibatis iterate문 지원 태그

 

property : 파라미터명

prepend : 쿼리로 쓰일 문자

open : 구문이 시작될때 삽입할 문자열

close : 구문이 종료될때 삽입할 문자열

conjunction : 반복되는 사이에 출력할 문자열

 


*ibatis : MyBatis  버전


MyBatis foreach문 지원 태그

 

collection : 전달받은 인자. List or Array 형태만 가능

item : 전달받은 인자 값을 alias 명으로 대체

open : 구문이 시작될때 삽입할 문자열

close : 구문이 종료될때 삽입할 문자열

separator : 반복 되는 사이에 출력할 문자열

index : 반복되는 구문 번호이다. 0부터 순차적으로 증가

 

즉, ibatis iterate -> MyBatis foreach로 변경됐습니다.

 

 

문법


<foreach collection="List or Array" item="alias" ></foreach>

 

사용 예시

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

-참고 자료 MyBatis 공식 사이트

 

 

1. 배열 예시

 

공통 배열 데이터 

String[] userArray = {"1","2","3"}

 

1-1. 배열 파라미터를 Map을 통해 넘겼을 경우

 

DAO

public List<Members> getAuthUserList(String[] userArray) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("userArray",userArray);
		return sqlSession.selectList("getAuthUserList", map);
}

user-Mapper.xml

<select id="getAuthUserList"  resultType="members">
SELECT m.*,a.name FROM members AS m 
JOIN authority AS a ON m.authority = a.authority
WHERE m.authority IN
<foreach collection="userArray" item="arr" open="(" close=")" separator=",">
 #{arr}
</foreach>
ORDER BY m.authority;
</select>

 

※ 주의 : collection을 꼭! 넘겨주는 배열 변수 값과 동일하게 작성하셔야 합니다.


1-2. 배열 파라미터를 직접 넘겼을 경우

 

DAO

public List<Members> getAuthUserList(String[] userArray) {
		return sqlSession.selectList("getAuthUserList", userArray);
}

user-Mapper.xml

<select id="getAuthUserList"  resultType="members">
SELECT m.*,a.name FROM members AS m 
JOIN authority AS a ON m.authority = a.authority
WHERE m.authority IN
<foreach collection="array" item="arr" open="(" close=")" separator=",">
 #{arr}
</foreach>
ORDER BY m.authority;
</select>

※ 주의 : collection을 꼭! "array"로 작성하셔야 합니다.

 

 

 

2. 리스트 예시

 

공통 리스트 데이터 

List<Members> chkList = userService.getUserList(); //SELECT * FROM members 결과값

 

2-1. 리스트 Map을 통해 넘겼을 경우

 

DAO

public List<Members> getListTest(List<Members> chkList) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("chkList",chkList);
		return sqlSession.selectList("getListTest", map);
}

user-Mapper.xml

<select id="getListTest" resultType="members">
SELECT m.*,a.name FROM members AS m 
JOIN authority AS a ON m.authority = a.authority
WHERE m.authority IN
<foreach collection="chkList" item="item" open="(" close=")" separator=",">
 #{item.authority}
</foreach>
ORDER BY m.authority;
</select>

리스트 안에 뽑고 싶은 결괏값을 {key.value} 형태로 뽑으시면 됩니다.

※ 주의 : collection을 꼭! 넘겨주는 리스트 변수 값과 동일하게 작성하셔야 합니다.

 


2-2. 리스트 파라미터를 직접 넘겼을 경우

 

DAO

public List<Members> getListTest(List<Members> chkList) {
		return sqlSession.selectList("getListTest", chkList);
}

user-Mapper.xml

<select id="getListTest" resultType="members">
SELECT m.*,a.name FROM members AS m 
JOIN authority AS a ON m.authority = a.authority
WHERE m.authority IN
<foreach collection="list" item="item" open="(" close=")" separator=",">
 #{item.authority}
</foreach>
ORDER BY m.authority;
</select>

리스트 안에 뽑고 싶은 결괏값을 {key.value} 형태로 뽑으시면 됩니다.

※ 주의 : collection을 꼭! "list"로 작성하셔야 합니다.

 

 

 

공통 설명

1. 먼저 리스트/배열 변수 값을 collection에 넣어주고, item이라는 설정으로 별칭 설정을 해준다.

2. 리스트/배열의 값이 시작하기 전 open="(" 이 설정돼있으므로'(' (열린 괄호)가 열리게 되고

3. 리스트/배열의 값이 한 번씩 반복문을 거칠 때마다 separator 옵션에 있는 ', '(콤마)가 찍히게 된다.

4. 반복이 끝나면 close=")" 설정이 있으므로 ')' (닫힌 괄호)가 쓰인다.


더욱 자세한 이해를 위해 실전 예시

 

문법에 들어가기 앞서, 공통 테이블 예시

예시 테이블

예시 테이블 02

 

[배열(array)] 예시 1. 멤버 테이블에서 체크박스에 따라 권한별 보여주는 기능 만들기

더보기

JSP

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> 
<meta charset="UTF-8">
<script type="text/javascript">
var chkArray = new Array;
$(document).ready(function(){

	getAuthUserList($("input[name=chk_authority]:checked").val());
	
$("input[name=chk_authority]").change(function(){

	var chk = "";
	chkArray = [];
	
	$("input[name=chk_authority]:checked").each(function() {
	
		chkArray.push($(this).val());

	});
	
		
	if(chkArray.length == 0){
		$("#userList").empty();
	} else {
		getAuthUserList(chkArray);
	}
	
	});

});

function getAuthUserList(chkArray){
	
$.ajax({
		
		url : "/user/getAuthUserList",
		data : { chkArray : chkArray },
		traditional : true,
		async: true,
		success : function(data){
			var html = '';
			
			for(key in data){
			html += '<tr>';
			html += '<td>'+data[key].userId+'</td>';
			html += '<td>'+data[key].nickname+'</td>';
			html += '<td>'+data[key].name+'</td>';
			html += '</tr>';
			}
			
			
			$("#userList").empty().append(html);
			
		}
	})
	
}

</script>
</head>
<body>

<div>
유저 등급별 보기<br>
<input type="checkbox" name="chk_authority" value="0" checked="checked">실버
<input type="checkbox" name="chk_authority" value="1">골드
<input type="checkbox" name="chk_authority" value="2">플래티넘
<input type="checkbox" name="chk_authority" value="3">다이아
<input type="checkbox" name="chk_authority" value="4">마스터
<input type="checkbox" name="chk_authority" value="5">그랜드마스터

<table>
<thead>
<tr>
<th>아이디</th>
<th>닉네임</th>
<th>권한</th>
</tr>
</thead>
<tbody id="userList">
</tbody>
</table>
</div>
</body>
</html>

Controller

@RequestMapping("user/getAuthUserList")
	public @ResponseBody List<Members> getAuthUserList(String[] chkArray) {
		List<Members> result = userService.getAuthUserList(chkArray);
		return result;
}

Service

List<Members> getAuthUserList(String[] chkArray);

Servicelmpl

@Override
public List<Members> getAuthUserList(String[] chkArray) {
		return userDAO.getAuthUserList(chkArray);
}

DAO

public List<Members> getAuthUserList(String[] chkArray) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("chkArray",chkArray);
		return sqlSession.selectList("getAuthUserList", map);
}

user-Mapper.xml

<select id="getAuthUserList"  resultType="members">
SELECT m.*,a.name FROM members AS m 
JOIN authority AS a ON m.authority = a.authority
WHERE m.authority IN
<foreach collection="chkArray" item="item" open="(" close=")" separator=",">
 #{item}
</foreach>
ORDER BY m.authority;
</select>

쿼리 결과

SELECT m.*,a.name FROM members AS m  
JOIN authority AS a ON m.authority = a.authority 
WHERE m.authority IN

(체크박스 수에 따른 값)
#('1','2','3')
#('2','3')
ORDER BY m.authority;

쿼리 결과 설명

1. 먼저 배열 변수 값을 collection에 넣어주고, item이라는 설정으로 별칭 설정을 해준다.

2. 배열의 값이 시작하기 전 open="(" 이 설정돼있으므로'(' (열린 괄호)가 열리게 되고

3. 배열의 값이 한 번씩 반복문을 거칠 때마다 separator 옵션에 있는 ', '(콤마)가 찍히게 된다.

4. 반복이 끝나면 close=")" 설정이 있으므로 ')' (닫힌 괄호)가 쓰인다.

 

View

 

 


[리스트(List)] 예시 2. 멤버 테이블에서 데이터 넘겨받아 리스트로 반복문 돌리기

더보기

Controller

@RequestMapping("user/getUserList")
	public @ResponseBody List<Members> getUserList() {
		List<Members> result = userService.getUserOne();
		List<Members> foreachTest = userService.getListTest(result);
		System.out.println(foreachTest);
		return result;
}

Service

List<Members> getListTest(List<Members> result);

Servicelmpl

@Override
public List<Members> getListTest(List<Members> result) {
		return userDAO.getListTest(result);
}

DAO

public List<Members> getListTest(List<Members> chkList) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		map.put("chkList",chkList);
		return sqlSession.selectList("getListTest", map);
}

user-Mapper.xml

<select id="getListTest" resultType="members">
SELECT m.*,a.name FROM members AS m 
JOIN authority AS a ON m.authority = a.authority
WHERE m.authority IN
<foreach collection="chkList" item="item" open="(" close=")" separator=",">
 #{item.authority}
</foreach>
ORDER BY m.authority;
</select>

쿼리 결과

SELECT m.*,a.name FROM members AS m  
JOIN authority AS a ON m.authority = a.authority 
WHERE m.authority IN

(체크박스 수에 따른 값)
#('1','2','3')
#('2','3')
ORDER BY m.authority;

쿼리 결과 설명

1. 먼저 리스트 변수 값을 collection에 넣어주고, item이라는 설정으로 별칭 설정을 해준다.

2. 리스트의 값이 시작하기 전 open="(" 이 설정돼있으므로'(' (열린 괄호)가 열리게 되고

3. 리스트의 값이 한 번씩 반복문을 거칠 때마다 separator 옵션에 있는 ', '(콤마)가 찍히게 된다.

4. 반복이 끝나면 close=")" 설정이 있으므로 ')' (닫힌 괄호)가 쓰인다.

 

예제 SQL 파일 제공


[MyBatis] foreach문 예제.sql
0.00MB

 

 

반응형
반응형

원인


통계 기능 개발 중 기간 범위를 넓게 잡으면 예외가 발생했다.

 

 

브라우저(Chrome) Console Log

Failed to load resource: the server responded with a status of 400 ()

 

Spring Log

2월 21, 2020 3:32:40 오후 org.apache.coyote.http11.Http11Processor service

정보: Error parsing HTTP request header

 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.

java.lang.IllegalArgumentException: Request header is too large

at org.apache.coyote.http11.Http11InputBuffer.fill(Http11InputBuffer.java:718)

at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:462)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684)

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.lang.Thread.run(Thread.java:748)

 

분석


RFC 2616 (하이퍼 텍스트 전송 프로토콜-HTTP / 1.1 – 섹션 3.2.1)에 따라 HTTP Get Request의 최대 URL 크기에 대한 제한은 없지만 브라우저와 서버단에서 제한을 적용 시킵니다.

 

제가 사용했던 브라우저인 크롬의 URL 제한 범위

 

URL 길이

일반적으로 웹 플랫폼 에는 URL 길이에 대한 제한이 없습니다 (2 ^ 31이 일반적인 제한 임). Chrome 은 실제적인 이유로 프로세스 간 통신에서 서비스 거부 문제가 발생하지 않도록 URL을 최대 2MB로 제한합니다.

VR 플랫폼에서는 1kB 제한이 사용되지만 대부분의 플랫폼에서 Chrome의 검색 주소창은 URL 표시를 32kB ( kMaxURLDisplayChars)로 제한합니다.

URL 길이가 제한을 초과하면 클라이언트가 합리적으로 동작하는지 확인하십시오.

  • 원점 정보는 URL의 시작 부분에 나타나므로 끝을 잘라내는 것은 일반적으로 무해합니다.
  • 가장자리의 경우 URL을 빈 문자열로 렌더링 하는 것은 이상적이지 않지만 잘리지 않는 (또는 예기치 않게 불안정한 경우) 더 나빠질 수 있습니다.
  • 공격자는 긴 URL을 사용하여 시스템의 다른 부분을 악용할 수 있습니다. DNS 구문 은 정규화된 호스트 이름을 253 자로 제한하고 호스트 이름의 각 레이블레이블을 63 자로 제한하지만 Chromium의 GURL 클래스는 이 제한을 적용하지 않습니다.

크로미움 공식 사이트 제공

 

권장사항


URL을 2000 자 미만으로 유지하면 거의 모든 클라이언트 및 서버 소프트웨어 조합에서 작동합니다.

그리고 모든 클라이언트와 서버는 URL 작동하는 여부에 관계없이 2048 미만을 유지해야합니다.

 

 

해결책


1.GET URL parameter 줄이기

 

2.GET -> POST 방식으로 변경

반응형
반응형

정말 문법을 확인하고 또 확인해도 정확히 썼는데 POST 통신이 되지 않는다면?

설정한 XSS 공격 방어 또는 인터셉터를 의심하세요.

네이버에서 만든 lucy-xss-sax.xml 쓸 경우

HttpServletRequest를 가져가 먼저 작업을 하기 때문에 POST로 데이터를 못 받아온다.

POST 방식으로 전달된 "application/json" 타입의 데이터를 Servlet의 Filter나 Spring의 Interceptor에서 모종의 처리를 하기 위해서는 HttpServletRequest의 InputStream을 읽어 들여야 합니다. 
그러나 HttpServletRequest의 InputStream은 한 번 읽으면 다시 읽을 수 없습니다. 

왜냐하면 톰캣이 그걸 막습니다.

만약 Interceptor나 Filter에서 InputStream을 읽게 되면, 이후 Spring이 Converter를 이용해 Json 데이터를 바인딩 처리할 때 아래와 같은 에러를 만날 수 있습니다. Spring이 이미 읽어버린 InputStream을 다시 읽으려고 시도하다가 슬픈 에러를 뱉어내는 거죠.


바인딩 예외


1.JSON으로 던졌을 때

java.lang.IllegalStateException: getReader() has already been called for this request 
org.springframework.http.converter.HttpMessageNotReadableException: 
Could not read JSON: Stream closed; nested exception is java.io.IOException: Stream closed


2.FORM으로 던졌을 때

14:46:39.691 [http-nio-80-exec-11] WARN  j.l.Exception line:23 method:handlerException  - 
org.springframework.validation.BindException: 
org.springframework.validation.BeanPropertyBindingResult: 1 errors 


상황 발생 
어느 날 갑자기 POST로 보내는 통신은 싹 다 null이 뜨기 시작함
데이터를 바인딩하지 못하는 현상

@RequestMapping(value="manage/postTest.do",method=RequestMethod.POST)   
public String signUp(HttpServletRequest request){ 
LOG.warn(request); 
LOG.warn(request.getParameter("id")); 
 return "/java119.do"; 
} 

 

로그 출력 

14:41:21.964 [http-nio-80-exec-1] WARN  c.k.P.o.c.Java119Controller line:39 method:signUp  

-com.pack.project.XSS.RequestWrapper@61d1d9fa 
14:41:21.965 [http-nio-80-exec-1] WARN  c.k.P.o.c.Java119Controller line:40 method:signUp  - null 

 

요청을 Java119Controller에 보냈는데 HttpServletRequest 객체는 뜬금없이 XSS 설정해둔 패키지를 바라보고 있음

XSS 설정 의심 시작

필자가 사용한 XSS는 네이버 선배 형님들이 만든 lucy-xss-servlet-filter입니다.
git 주소 : https://github.com/naver/lucy-xss-servlet-filter

다시 보니

 

Recommendation 

신규로 개발하는 서비스에는 lucy-xss-servlet-filter를 사용하는 것을 추천하지만, 
기존 잘 운영되는 시스템에 lucy-xss-servlet-filter를 사용하는 것은 추천하지 않습니다. 
입력 파라메터가 전부 필터링 되기 때문에 
서비스가 잘 동작하지 않는 의도치 않은 결과가 발생할 수 있기 때문입니다.  

 

라고 써져있었네요.. 죄송합니다 네이버 선배님들

web.xml에 설정해둔 XSS Filter 주석 처리 후 POST 테스트 부분

결과 : 아주 잘 됩니다.

아무리 POST가 안 되는 이유, 갑자기 POST 안됨, POST 통신 먹통, POST 무조건 null 등등 
검색해봐도 찾을 수가 없었는데 
역시 검색에 너무 의존하는 것도 문제인 거 같습니다.

 

꼭 XSSFilter 아니더라도 POST가 되지 않는다면 뭔가가 가로채고 있다는 뜻입니다.

그런 것을 잘 확인해보시고 즐 코 하세요~

 

 

 

XSSFilter를 적용하면서 POST를 하시고 싶으신 분들은 참고 자료

 

Spring Interceptor(혹은 Servlet Filter)에서 POST 방식으로 전달된 JSON 데이터 처리하기 : TOAST Meetup

Spring Interceptor(혹은 Servlet Filter)에서 POST 방식으로 전달된 JSON 데이터 처리하기

meetup.toast.com

 

반응형

+ Recent posts