본문 바로가기
스프링/mvc패턴

[spring] 02.23 게시판 만들기 -AOP구현 , pagination..?이엇나

by CodeMango 2023. 2. 23.

1. AOP구현

 

🎈AOP란?

AOP(Aspect Oriented Programming) 관점지향

목적 : 감시자

 

🎈AOP를 사용하는 이유

view->controller->view
세션확인
로그인한 정보가 로그인 됐는지 확인하는것
로그인 안 했으니까 글 읽을 수 없어. 로그인 하고와!
이런 처리를 하기 위해
view에서 view로 이동할 때 세션확인한다.
이런 공통된 작업을 할 수 있는게
AOP이다.

 

🎈방식)

xml방식, annotation방식이 있는데 annotation 방식을 더 많이 쓴다.

 

🎈@Aspect 어노테이션 사용

블로그 참조 : https://ktko.tistory.com/entry/Spring-AOP%EA%B5%AC%ED%98%84Aspect-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%82%AC%EC%9A%A9

 

[스프링/Spring] AOP구현(@Aspect 어노테이션 사용)

AOP 구현 : @Aspect 어노테이션 사용 @Aspect 어노테이션을 이용해서 공통으로 적용할 기능을 구현한 경우, XML 설정에서 이를 인식할 수 있도록 태그를 추가해주어야 한다. @Aspect 어노테이션을 이용해

ktko.tistory.com

 

기능)

컨트롤러에서 뷰로 갔다가 "뷰에서 다시 컨트롤러로 갔을때" 감시자가 신호 받아서 동작을 한다.

 

MyBatis의 동적 쿼리

 

동적 쿼리(Dynamic SQL)란, 실행 시점에 조건에 따라 SQL 쿼리를 동적으로 생성하는 기술입니다. 즉, 실행 시점에 필요한 SQL 문장을 조합하여 완성된 SQL을 생성하고 실행합니다. 이를 사용하면 하나의 SQL 쿼리로 여러 조건에 따라 다른 결과를 얻을 수 있습니다.

예를 들어, 위 코드에서 <if> 태그를 사용하여 WHERE 절의 검색 조건을 동적으로 생성하고 있습니다. choice와 search라는 매개변수를 통해 검색 조건을 전달받아, 이를 바탕으로 검색 조건이 필요한 경우에만 WHERE 절을 생성하고, 그렇지 않은 경우에는 WHERE 절을 생성하지 않습니다. 이렇게 하면 검색 조건에 따라 다양한 SQL 쿼리를 실행할 수 있습니다.

동적 쿼리는 복잡한 검색 기능을 제공하는 경우나, 조건이 다양하게 변할 수 있는 경우 유용하게 사용됩니다. MyBatis에서는 XML Mapper 파일에서 동적 쿼리를 작성할 수 있는 여러 가지 태그를 제공하고 있습니다. <if> 태그 외에도 <choose>, <when>, <otherwise>, <foreach> 등 다양한 태그를 활용하여 동적 쿼리를 작성할 수 있습니다.

 

2. 게시판 흐름 잡기

게시판을 작성하기 전에, 게시판 파일의 흐름을 설명하겠습니다.

Controller는 HTTP 요청에 대한 핸들러 역할을 하고, 요청을 처리한 후 결과를 뷰에 전달합니다.

controller에서 "bbslist.do" URL에 대한 GET 요청을 처리하고 , 요청 파라미터를 BbsParam 객체로 자동 매핑하여 Service 계층으로 전달합니다. Service 계층에서는 DAO 계층을 호출하여 데이터베이스에서 데이터를 조회하고, 조회한 결과를 service를 거쳐 Controller로 반환합니다.

마지막으로 Controller에서는 뷰에 필요한 모델 데이터를 생성하여 뷰로 전달합니다.

 

DAO는 데이터베이스 연동을 담당하고, 데이터베이스에서 데이터를 조회, 삽입, 수정, 삭제하는 메서드를 제공합니다.

MyBatis를 이용하여 데이터베이스에 접근하고, bbslist 쿼리를 실행하여 BbsDto 리스트를 반환하거나, getAllBbs 쿼리를 실행하여 글의 총 개수를 반환합니다.

Service는 비즈니스 로직을 담당합니다. DAO 계층으로부터 데이터를 받아와서 가공하거나, 다른 서비스와의 조합 작업 등을 수행합니다. 위 코드에서는 DAO 계층의 bbslist, getAllBbs 메서드를 호출하여 데이터를 반환합니다.

Mapper는 DAO 계층에서 SQL 문장을 실행하는 역할을 합니다. 위 코드에서는 MyBatis 프레임워크를 이용하여 bbslist, getAllBbs 쿼리를 작성하고 실행합니다. bbslist 쿼리는 게시글의 목록과 페이징 처리를 위한 쿼리입니다. getAllBbs 쿼리는 검색 조건에 맞는 총 글의 개수를 반환하는 쿼리입니다.

 

3. 게시판 만들기 어려운 부분 정리

1. MyBatis의 동적 쿼리 

<select id="bbslist" parameterType="mul.cam.a.dto.BbsParam"
	resultType="mul.cam.a.dto.BbsDto">

	<!-- choice가 select박스, search가 검색값 -->
	select seq, id, ref, step, depth, title, content, wdate, del, readcount
	from
		(select row_number()over(order by ref desc, step asc) as rnum,
			seq, id, ref, step, depth, title, content, wdate, del, readcount
		 from bbs
		 where 1=1 
		 <if test="choice != null and choice != '' and search != null and search != '' ">
		 	<if test="choice == 'title'">	<!-- if(choice.equals("title")) -->
		 		and title like concat('%', #{search}, '%') and del=0	<!-- (title like '%검색어%')  -->
		 	</if>
		 	<if test="choice == 'content'">
		 		and content like concat('%', #{search}, '%') and del=0	<!-- (content like '%검색어%')  -->
		 	</if>
		 	<if test="choice == 'writer'">	<!-- 작성자 -->
		 		and id=#{search} and del=0
		 	</if>
		 </if>		 
		 order by ref desc, step asc) a
	where rnum between ${start} and ${end}	<!-- 숫자니까 $로 표시 -->
</select>

위 코드는 MyBatis의 Mapper 파일에서 사용되는 동적 쿼리(Dynamic SQL)입니다. 이 쿼리는 게시판 게시글 목록을 조회하는 기능을 제공합니다.

쿼리 해석:

  • <select> 태그로 시작하여 </select> 태그로 끝나는 쿼리문입니다.
  • Mapper 파일에서 사용할 때, 들어오는값 = 파라미터 타입(parameterType)은 BbsParam, 결과 타입(resultType)은 BbsDto로 지정되어 있습니다.
  • 실제 쿼리문은 <select> 태그와 </select> 태그 사이에 위치하며, 다음과 같은 내용으로 구성되어 있습니다.
    • <select> 태그의 내용 중간에 있는 SQL 쿼리문은 서브쿼리입니다. 서브쿼리에서는 게시판 게시글 테이블(bbs)에서 row_number() 함수를 사용하여 순서대로 번호를 부여하고, 이를 rnum이라는 이름으로 지정합니다.
    • SELECT 구문에서는 게시글 테이블에서 필요한 컬럼들(seq, id, ref, step, depth, title, content, wdate, del, readcount)을 선택합니다.
    • FROM 구문에서는 서브쿼리(a)를 사용합니다.
    • WHERE 구문에서는 1=1이라는 조건문을 기본적으로 포함하고 있습니다. 이 조건문은 나중에 추가될 검색 조건과 함께 사용됩니다.
    • <if> 태그를 사용하여 검색 조건을 동적으로 생성합니다. choice와 search 매개변수를 사용하여, 검색 조건이 존재하는 경우에만 해당 검색 조건을 WHERE 절에 추가합니다. 검색 조건은 title, content, writer(작성자) 중 하나로 선택될 수 있습니다. choice가 title인 경우, title 컬럼에서 검색어(search)를 포함한 게시글을 찾습니다. choice가 content인 경우, content 컬럼에서 검색어를 포함한 게시글을 찾습니다. choice가 writer인 경우, id 컬럼에서 작성자(search)가 일치하는 게시글을 찾습니다. del 컬럼이 0인 게시글만 조회합니다.
    • ORDER BY 구문에서는 ref를 기준으로 내림차순으로 정렬하고, ref 값이 같은 경우에는 step을 오름차순으로 정렬합니다.
    • 서브쿼리(a)에서는 rnum이라는 컬럼이 생성되며, 이 컬럼의 값이 start와 end 사이에 존재하는 게시글만 선택합니다. start와 end는 Mapper에서 파라미터로 전달됩니다.

2. where 1=1

where 1=1은 조건절에서 항상 참(true)을 반환하는 것으로, 실제로는 아무런 의미가 없는 코드입니다.

하지만 이 코드는 BbsParam 객체를 이용한 동적 쿼리를 작성할 때 매우 유용합니다. BbsParam 객체에서 검색어(search)나 검색 조건(choice)가 입력되지 않은 경우, where절에서 해당 검색어를 무시하고 기본적인 쿼리를 실행하기 위해서 1=1을 추가한 것입니다.

이렇게 하면 BbsParam 객체에 검색어나 검색 조건이 입력되지 않은 경우에도 쿼리가 실행되어 문제가 발생하지 않으며, 검색어나 검색 조건이 입력된 경우에는 추가적인 where절이 추가되어 검색 조건에 맞는 쿼리를 실행할 수 있습니다.

 

3. row_number()over

row_number()over는 SQL에서 윈도우 함수(window function) 중 하나입니다.

윈도우 함수는 행 집계 함수(aggregation function)와 달리, 집계 함수의 결과를 각 행에 매핑하는 함수입니다.

따라서 결과 집합에서 각 행에 대해 계산된 결과를 반환합니다.

 

row_number()over 함수는 윈도우를 지정하고 해당 윈도우에서 각 행의 순서를 반환합니다.

예를 들어, row_number()over(order by col)는 col 열을 기준으로 오름차순으로 정렬한 결과에서 각 행의 순서를 반환합니다. 이때 반환되는 값은 1부터 시작하며, 순서대로 1씩 증가합니다.

위의 코드에서는 row_number()over(order by ref desc, step asc)를 사용하여, ref 열을 기준으로 내림차순으로 정렬한 뒤, step 열을 기준으로 오름차순으로 정렬한 결과에서 각 행의 순서를 지정하고 있습니다. 이렇게 지정된 순서를 rnum이라는 별칭(alias)으로 지정하고, 이 값을 이용하여 각 게시글의 순서를 나타냅니다.

 

4. 리스트에 담는 이유

 아래와 같은 코드가 있는데, 리턴값을 List라고 한 이유를 알아보았습니다.

	List<BbsDto> bbslist(BbsParam bbs);

 

 

위 코드는 조회된 결과를 List<BbsDto> 타입으로 반환하고 있습니다. 이는 게시글이 여러 개 조회될 수 있기 때문입니다.

BbsDto는 하나의 게시글 정보를 담을 수 있는 객체입니다. 반면에 List<BbsDto>는 여러 개의 BbsDto 객체를 담을 수 있는 컬렉션입니다.

따라서 List<BbsDto>를 사용하지 않고 BbsDto 하나를 반환한다면, 한 개의 게시글만을 조회할 수 있습니다.

그러나 게시글은 여러 개가 존재할 수 있으므로 List<BbsDto>를 사용하여 여러 개의 게시글을 한 번에 조회할 수 있도록 구현하는 것이 바람직합니다.

따라서, List<BbsDto>를 사용하는 것이 게시글 목록 조회 기능을 보다 효율적으로 구현할 수 있도록 하기 위한 선택이었습니다.

 

5. ifnull(max(ref), 0)+1

<insert id="writeBbs" parameterType="mul.cam.a.dto.BbsDto">
	insert into bbs(id, ref, step, depth, title, content, wdate, del, readcount)
	values(#{id}, (select ifnull(max(ref), 0)+1 from bbs b), 0, 0, 
	<!-- (select ifnull(max(ref), 0)+1
		-> 현재 들어왔을땐 null일 수 있으니까 그땐 제일 높은 ref값을 집어넣고, null이 아니면 0을 집어넣어라 
		+1 : 새로운 글이 작성될 때마다 ref 값이 1씩 증가
	-->
			#{title}, #{content}, now(), 0, 0)	
</insert>

1)ifnull 함수 : 첫 번째 인자가 NULL일 경우 두 번째 인자를 반환하는 함수

ifnull(max(ref), 0) : max(ref)가 null일 경우 0을 반환합니다.

만약 bbs 테이블에 아직 글이 한 개도 없다면, max 함수는 null 값을 반환합니다. 따라서 ifnull 함수를 사용하여 null 값 대신에 0을 반환하도록 합니다.

 

2) +1 : subquery에서 구한 가장 큰 ref 값에 +1을 합니다. 새로운 글이 작성될 때마다 ref 값은 1씩 증가합니다.

새로운 글이 작성될 때마다 1씩 증가하는 원리는 다음과 같습니다.

  1. 글 작성 시, 새로운 ref 값을 할당합니다.
  2. ref 값은 기존에 작성된 글들 중 가장 큰 ref 값보다 1 큰 값을 가집니다.
  3. 따라서, 새로운 글이 작성될 때마다 ref 값은 1씩 증가합니다.

위 코드에서 (select ifnull(max(ref), 0)+1 from bbs b) 부분은 새로운 글을 작성할 때 해당 글의 ref 값을 지정하는 부분입니다. 따라서, 글이 아직 작성되지 않았을 경우 max(ref) 값은 null이 되고, 이때 ifnull 함수는 0을 반환합니다. 그리고 +1을 하면, 새로운 글이 작성될 때마다 이전 글들의 ref 값보다 1씩 큰 값을 가지게 됩니다.

 

3) bbs b : bbs는 bbs테이블 이름이고 b는 alias를 뜻한다.

 

6. twbsPagination의 문법

$('#pagination').twbsPagination({
	startPage: <%=pageNumber+1 %>, 	//페이지넘버 0부터 시작되므로
    totalPages: <%=pageBbs %>,		// 넘겨받은 현재 페이지 개수
    visiblePages: 10,
    first:'<span srid-hidden="true">«</span>',
    prev:"이전",
    next:"다음",
    last:'<span srid-hidden="true">»</span>',
    initiateStartPageClick:false,   // onPageClick 페이징버튼이 자동실행되지 않도록
    onPageClick: function (event, page) { // event는 이벤트 객체이고, page는 클릭된 페이지 번호
        // alert(page);
        let choice = document.getElementById('choice').value;
		let search = document.getElementById('search').value;
    	location.href = "bbslist.do?choice=" + choice + "&search=" + search + "&pageNumber=" + (page-1);
    /* twbsPagination의 페이지 번호는 1부터 시작
    	bbslist의 PageNumber은 0부터 시작
    	따라서 twbsPagination에서 사용하는 startPage에는 +1
    	bbslist에서 사용하는 onPageClick에는 -1 
    */

twbsPagination의 페이지 번호는 1부터 시작
bbslist의 PageNumber은 0부터 시작
따라서 twbsPagination에서 사용하는 startPage에는 +1
bbslist에서 사용하는 onPageClick에는 -1

 

 

스프링에서는 자식은 하나여야됨.

10:35분 강의 듣기

댓글