파일 업로드 방식
1. <form> 태그를 이용하는 방식 : 브라우저의 제한이 없어야 하는 경우에 사용
 - 일반적으로 페이지 이동과 동시에 첨부파일을 업로드하는 방식
 - <iframe>을 이용해서 화면의 이동 없이 첨부파일을 업로드 하는 방식

2. Ajax를 이용하는 방식 : 첨부파일을 별도로 처리하는 방식
 - <input type='file'>을 이용하고 Ajax로 처리하는 방식
 - HTML5의 Drag And Drop 기능이나 jQuery 라이브러리를 이용해서 처리하는 방식

 

서버에서의 주의사항
 - 어떤 종류의 라이브러리나 API등을 활용할 것인지
  o cos.jar : 2002년도 이후에 개발이 종료되었으므로, 더 이상 사용하는 것을 권장하지 않음
  o commons-fileupload : 가장 일반적으로 많이 활용되고, 서블릿 스펙 3.0 이전에도 사용 가능
  o 서블릿 3.0 이상 : 3.0 이상부터는 자체적인 파일 업로드 처리가 API 상에서 지원

 

파일 업로드에서 고려해야 하는 점들
 - 동일한 이름으로 파일이 업로드 되었을 때 기존 파일이 사라지는 문제
 - 이미지 파일의 경우에는 원본 파일의 용량이 큰 경우 섬네일 이미지를 생성해야 하는 문제
 - 이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제
 - 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한

 

첨부파일 삭제
 - 이미지 파일의 경우에는 섬네일까지 같이 삭제되어야 함
 - 파일을 삭제한 후에는 브라우저에서도 섬네일이나 파일 아이콘이 삭제되도록 처리해야 함
 - 비정상적으로 브라우저의 종료 시 업로드된 파일의 처리

 

게시물 조회

- 게시물을 조회 할 때 join처리해서 한꺼번에 게시물과 첨부파일의 정보를 같이 처리하는 방식

(데이터베이스를 한 번만 호출하게 되므로 효율적이지만 MyBatis 쪽에서 처리해야 하는 일이 많아짐)

- JSP에서 첨부파일의 정보를 Ajax를 이용해서 처리하는 방식

(다시 쿼리를 처리해야 하는 불편함이 있집만 난이도가 낮고, 화면에서 처리는 JavaScript 처리가 복잡)

 

ㅁ 전통적인 방식 : 테이블을 join해서 한번의 호출로 처리하는 방식(쿼리를 한번만 호출해 DB의 부하를 줄여줌)

 

 

게시물의 삭제

- 게시물의 내용과 게시물에 포함된 첨부파일을 같이 삭제 해야함

- 단순 DB상이 아닌 실제 저장된 파일의 삭제도 필요

 

 

- event.preventDefault()

주로 별도의 브라우저 행동을 막기 위해서 사용한다.

 

- 페이징 처리에 있어서의 활용

주로 페이징처리를 했을 경우 페이징 버튼이 해당 페이지의 하단부에 위치하게 된다.

그리고 해당 버튼을 클릭시에 preventDefault() 이벤트 처리를 하지 않았을 경우 클릭한 페이지로 넘어가면서 브라우저의 최상단으로 이동하게 된다.

그러나 페이지 이동이 필요없는 경우나 게시판 등 일정한 구역만 바뀌며 원하는 데이터를 찾아서 하단부에서의 활동을 이어나갈 때 브라우저의 최상단으로 이동후에 다시 하단부로 내려와 다음 버튼을 클릭하는 등의 불편함이 존재하게 된다.

그래서 event.preventDefault()를 통해서 <a>태그의 링크로 이동하면서 페이지의 스크롤을 유지할 때 사용한다.

JSON 형태로 Date 형식의 날짜를 받아올 경우 날짜 처리가 안된 순수하게 숫자로 표현되는 시간 값이 나오게 된다.

 

위 사진의 좌측이 날짜 처리후 결과값이고,

우측이 날짜 처리를 하지 않고 date 값을 json 형태로 받아 왔을 때의 값이다.

 

// 해당일 시간, 날짜 처리 함수
function displayTime(timeValue) {
  var today = new Date();
  var gap = today.getTime() - timeValue;
  var dateObj = new Date(timeValue);
  var str = "";

  // 24시간이 지나지 않은 댓글 (시간표시 ex.15:13:21)
  if(gap < (1000 * 60 * 60 * 24)) {
    var hh = dateObj.getHour();
    var mi = dateObj.getMinutes();
    var ss = dateObj.getSeconds();

    return [(hh > 9 ? '' : '0') + hh, ':', (mi > 9 ? '' : '0') + mi, ':', (ss > 9 ? '' : '0') + ss].join('');
    } else { // 24시간이 지난 댓글 (날짜 표시 ex.2020/10/28)
      var yy = dateObj.getFullYear();
      var mm = dateObj.getMonth() + 1;
      var dd = dateObj.getDate();

    return [yy, '/', (mm > 9 ? '' : '0') + mm, '/', (dd > 9 ? '' : '0') + dd].join('');
    }
}

id로 접근시

- $("#id")

 

name으로 접근시

- $(".class")

 

class로 접근시

- $(tag_name[name=name])

ex) $("input[name=value]")

git push시에 에러 발생

프로젝트를 업데이트 하려던 중 문제 발생

 

-원인-

git 사이트 내에서 readme.MD 파일을 수정했기 때문에

 

-해결방법-

git pull로 바뀐 파일을 가져오면서 자동 merge됨

그 후 다시 git push

근 10년간 가장 큰 변화는 '모바일'이다

모바일을 통해서 WEB의 접근 비율이 컴퓨터와 비슷하거나 이미 넘어섰다.

 

과거에는 서버는 브라우저라는 하나의 대상만을 상대로 데이터를 제공했기 때문에 브라우저가 소화 가능한 모든 데이터를 HTML이라는 형태로 전달하고, 브라우저는 이를 화면헤 보여주는 역할을 했다.

 

그러나 스마트폰이 나오면서 앱에서 완성된 HTML이 아닌 그저 자신에게 필요한 순수한 데이터만을 요구하게 되었다.

(초기에는 스마트폰 성능 문제 이후는 브라우저 및 스마트폰 앱에 최적화 시키기 위함)

그래서 서버의 역할은 점점 더 순수하게 데이터에 대한 처리를 목적으로 하는 형태로 진화하고 있다. 또한 브라우저와 앱은 서버에서 전달하는 데이터를 이용해서 앱 혹은 브라우저 내부에서 별도의 방식을 통해서 이를 소비하는 형태로 전환하고 있다.

 

과거에 제작된 웹페이지들의 경우 페이지를 이동하더라도 브라우저의 주소는 변화하지 않는 방식을 선호했다.

그러나 최근의 웹페이지들은 대부분 페이지를 이동하면 브라우저 내의 주소 역시 같이 이동하는 방식을 사용하며 REST 방식으로 전환하게 되었다.

 

Spring의 관점에서 보면 기존에는 Controller에서 Model에 데이터를 담아서 JSP 등과 같은 뷰(View)로 전당하는 방식 이였다가 REST 방식에서는 메서드의 리턴 타입으로 사용자가 정의한 클래스 타입을 사용할 수 있고, 이를 JSON이나 XML로 처리(JSP가 아닌 순수한 데이터를 반환)

org.springframework.web.util.UriComponentsBuilders

는 여러 개의 파라미터들을 연결해서 URL의 형태로 만들어 주는 기능을 가지고 있음

 

URL을 만들어주면 리다이렉트를 하거나, <form> 태그를 이용하는 상황을 많이 줄여줄 수 있음

public String getListLink() {
	UriComponentsBuilders builder = UriComponentsBuilder.fromPath("");
      .queryParam("pageNum", this.pageNum) //페이지번호
      .queryParam("amount", this.getAmount()) //페이지 양
      .queryParam("type", this.getType()) //검색 조건
      .queryParam("keyword", this.getKeyword()); //검색 단어
    
    return builder.toUriString();
}
    

 

UriComponentsBuilder 사용전

@PostMapping("/modify")
public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
	log.info("modify : " +board);
	if(service.modify(board)) {
		rttr.addFlashAttribute("result", "success");
	}

	// 리다이렉트 시에 원래의 페이지로 이동하기 위해서 값을 가지고 이동을 위함
	rttr.addAttribute("pageNum", cri.getPageNum());
	rttr.addAttribute("amount", cri.getAmount());
	rttr.addAttribute("type", cri.getType());
	rttr.addAttribute("keyword", cri.getKeyword());
	
	return "redirect:/board/list";
}

 

UriComponentsBuilder 적용 후

@PostMapping("/modify")
public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
	log.info("modify : " +board);
	if(service.modify(board)) {
		rttr.addFlashAttribute("result", "success");
	}

	/*
	rttr.addAttribute("pageNum", cri.getPageNum());
	rttr.addAttribute("amount", cri.getAmount());
	rttr.addAttribute("type", cri.getType());
	rttr.addAttribute("keyword", cri.getKeyword());
    */
	
    //적용 후
	return "redirect:/board/list" + cri.getListLink();
}

 

- 페이지 이동시에 데이터값을 가지고 넘어갈 때 간편해짐

- 가장 편리한 점은 한글 처리에 신경 쓰지 않아도 된다는 점

- 주로 JavaScript를 사용할 수 없는 상황에서 링크를 처리해야 하는 상황에서 사용

● trim(where, set)

● foreach

 

<tirm>, <where>, <set>

trim, where, set은 단독으로 사용되지 않고 SQL들을 연결해 주고ㅡ, 앞 뒤에 필요한 구문들(AND, OR, WHERE등)을 추가하거나 생략하는 역활을 함

SQL을 작성하다 보면 상황에 따라서 WHERE나 AND, OR 등이 문제가 되는 상황이 발생할 수도 있음

ex) WHERE ROWNUM <= 20 의 경우 문제가 없지만 검색 조건이 들어가면 문제가 될 수 있음

 

<where>의 경우 태그 안쪽에서 SQL이 생성될 때는 WHERE 구문이 붙고, 그렇지 않는 경우에는 생성되지 않음

select * from table
	<where>
    	<if test="no != null">
        	no = #{no}
        </if>
    </where>

위와 같은 경우 bno 값이 null인 경우에는 WHERE구문이 없어지고, bno 값이 존재하는 경우에만

'WHERE no = xx'와 같이 생성

no가 존재하는 경우 select * from table WHERE no = 33
no가 null인 경우 select * from table

 

<trim>은 태그의 내용을 앞의 내용과 관련되어 원하는 접두/접미를 처리 할 수 있음

select * from table
 <where>
  <if test="no != null">
   no = #{no}
  </if>
  <trim prefixOverrides = "and">
   rownum = 1
  </trim>
 </where>

trim은 prefix, suffix, prefixOverrides, suffixOverrides 속성을 지정할 수 있음

no가 존재하는 경우 select * from table WHERE no = 33 and rownum = 1
no가 null인 경우 select * from table WHERE rownum = 1

 

<foreach>

foreach는 List, 배열, 맵 등을 이용해서 루프를 처리

주로 IN 조건에서 많이 사용하지만, 경우에 따라서는 복잡한 WHERE 조건을 만들때에도 사용할 수 있음

ex) 제목('T')은 'TTTT'로 내용('C')은 'CCCC"라는 값을 이용한다면 Map의 형태로 작성이 가능

Map<String, String> map = new HashMap<>();
map.put("T", "TTTT");
map.put("C", "CCCC");

작성된 Map을 파라미터로 전달하고, foreach를 이용하면 다음과 같은 형식이 가능

select * from table
 <tirm prefix="where (" suffix=")" prefixOverrides="OR" >
  <foreach item="val" index="key" collection="map">
   <trim prefix="OR">
    <if test="key == 'T'.toString()">
     title = #{val}
    </if>
    <if test="key == 'C'.toString()">
     content = #{val}
    </if>
    <if test="key == 'W'.toString()">
     writer = #{val}
    </if>
   </trim>
  </foreach>
 </trim>

foreach를 배열이나 List를 이용하는 경우에는 item 속성만을 이용하면 되고, Map의 형태로 key와 value를 이용해야 할 때는 index와 item 속성을 둘 다 이용하고, 전달 된 값에 따라서 처리

select * from table
 where ( content = ?
  OR title = ? )

 

● if

● choose(when, otherwise)

 

<if>

if는 test라는 속성과 함께 특정한 조건이 true가 되었을 때 포함된 SQL을 사용하고자 할 때 사용

ex) 단일 항목으로 제목, 내용, 작성자(title, content, writer)에 대해 검색해야 하는 상활 일 때

- 검색 조건이 'T'면 제목(title)이 키워드(keyword)인 항목을 검색

- 검색 조건이 'C'면 내용(content)이 키워드(keyword)인 항목을 검색

- 검색 조건이 'W'면 작성자(writer)이 키워드(keyword)인 항목을 검색

// 검색 조건이 T일 경우
<if test="type == 'T'.toString()">
	(title like '%' || #{keyword} || '%')
</if>
// 검색 조건이 C일 경우
<if test="type == 'C'.toString()">
	(content like '%' || #{keyword} || '%')
</if>
// 검색 조건이 W일 경우
<if test="type == 'W'.toString()">
	(writer like '%' || #{keyword} || '%')
</if>

 

 

<choose>

if와는 달리 choose는 여러 상황들 중 하나의 상황에서만 동작

Java 언어의 'if~else'나 JSTL의 <choose>와 유사

<choose>
  <when test="type == 'T'.toString()">
      (title like '%' || #{keyword} || '%')
  </when>
  <when test="type == 'C'.toString()">
      (content like '%' || #{keyword} || '%')
  </when>
  <when test="type == 'W'.toString()">
      (writer like '%' || #{keyword} || '%')
  </when>
  
  <otherwise>
  	(title liek '%' || #{keyword} || '%' OR content like '%' || #{keyword} || '%')
  </otherwise>
</choose>

 

페이징 처리하기 전 생각해봐야하는 부분

한 페이지에 몇개의 글을 보여 줄 것인지?,  페이징 부분에 몇개의 페이지를 표시 할 것이지?

 

페이지가 10개씩 보인다고 가정하고 한페이지에 10개의 데이터가 출력 될 때

 

1. 페이징의 끝 번호 계산

this.endPage = (int)(Math.ceil(페이지번호) / 10.0)) * 10;

1페이지의 경우 : Math.ceil(0.1) * 10 = 10

10페이지의 경우 : Math.ceil(1) * 10 = 10

11페이지의 경우 : Math.ceil(1.1) * 10 = 20

 

2. 페이징의 시작 번호 계산

this.startPage = this.endPage - 9;

 

3. total을 통한 endPage의 재계산

realEnd = (int) (Math.ceil((total * 1.0) / amount) );

if (realEnd < this.endPage) {
	this.endPage = realEnd;
    }

총 데이터가 80개가 될 경우 총 endPage는 10이 아닌 8이 되어야 하기에 재계산이 필요

만약 realEnd가 endPage보다 작다면 끝 페이지는 작은 값이 되어야 함

 

4. 이전 계산

this.prev = this.startPage > 1;

이전 페이지는 시작 번호가 1보다 클 경우 존재

 

5. 다음 계산

this.next = this.endPage < realEnd;

realEnd가 끝 번호보다 큰 경우에만 존재

 

 

 

DTO 클래스 설계

public PageDTO(Criteria cri, int total) {
	this.cri = cri;
	this.total = total;
	
	this.endPage = (int) (Math.ceil(cri.getPageNum() / 10.0)) * 10;
	this.startPage = this.endPage - 9;
	
	int realEnd = (int) (Math.ceil((total * 1.0) / cri.getAmount()));
	
	if (realEnd < this.endPage) {
		this.endPage = realEnd;
	}
		
	this.prev = this.startPage > 1;
	this.next = this.endPage < realEnd;
}

 

페이징 설계시 생각해 보아야 할 부분2


예를 들어 1 2 3 4 5 6 7 8 9 로 페이징 처리 된 게시글이 있다고 칠 경우

6번 페이지에서 어느 게시글을 본 후 뒤로가기로 돌아갔을 경우 해당 페이지로 돌아가지 않고 1페이지의 부분으로 돌아가게 됨

 

해결 방안

페이징의 정보를 파라미터로 넘긴다

내가 구현한 방식은 UriComponentsBuilder(여러 개의 파라미터들을 연결해서 URL 형태로 만들어 줌)를 사용해서 파라미터를 url형태로 넘겨서 유지

페이지 번호, 총 게시글의 수, 검색 조건 등을 파라미터로 넘김

여기서 get으로 파라미터를 받아 왔을 때 파라미터가 없으면 첫번째 페이지를 출력

 

+ Recent posts