본문 바로가기
스프링부트

[스프링부트] 홈페이지 만들기[2] - map 개념, 게시판

by CodeMango 2023. 3. 27.

1. bbslist : 데이터 가져오기

Bbs.xml

<!--  스프링과 다른점 -> namespace -> dao가 mapper를 흡수하게함. -->
<mapper namespace="mul.cam.a.dao.BbsDao">

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

	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'">
		 		and title like concat('%', #{search}, '%') and del=0
		 	</if>
		 	<if test="choice == 'content'">
		 		and content like concat('%', #{search}, '%') and del=0
		 	</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>

 

BbsDao.java

@Mapper
@Repository
public interface BbsDao {

	List<BbsDto> bbslist(BbsParam param); //BbsDto 타입의 객체들을 담을 수 있는 List를 반환

BbsService.java

@Service
@Transactional
public class BbsService {

	@Autowired
	BbsDao dao;
	
	public List<BbsDto> bbslist(BbsParam param) { //BbsDto 타입의 객체들을 담을 수 있는 List를 반환
		return dao.bbslist(param);
	}

BbsController.java

	//스프링에서는 bbslist에서  다 싸서 보내줬지만. 부트에서는 에이젝스로 따로따로 받아야 해서 따로따로 보내야 합니다.
	//데이터를 두개 보내기 위해서는 list 말고, Map으로 보낼 수 있다.
	@GetMapping(value = "bbslist")
	public Map<String, Object> bbslist(BbsParam param){	//맨처음엔 param으로 아무것도 안들어옴
		System.out.println("BbsController bbslist " + new Date());
		
		// 글의 시작과 끝
		int pn = param.getPageNumber();  // 0 1 2 3 4
		int start = 1 + (pn * 10);	// 1  11
		int end = (pn + 1) * 10;	// 10 20 
		
		param.setStart(start);
		param.setEnd(end);
		//1
		List<BbsDto> list = service.bbslist(param);	//param을 집어넣을 때 주의할점 = 페이징 같이 넣어야함.
		//param으로 게시글 목록 가져오기 위한 조건(페이지번호, 검색어 등) 걸어서 조건에 맞는 BbsDto 객체를 담는 리스트 반환
		int len = service.getAllBbs(param);	//글의 총수
		
		//2
		int pageBbs = len / 10;		// 글의 총수 나누기 한페이지에 보여줄 페이지수 -> 33개 /10개라면 3페이지
		if((len % 10) > 0) {
			pageBbs = pageBbs + 1;
		}
//Map 객체에 BbsDto 객체의 List와 페이지 정보(pageBbs)를 각각 "list"와 "pageBbs"라는 키(Key)로 저장
		Map<String, Object> map = new HashMap<>();
		map.put("list", list);		//1
		map.put("pageBbs", pageBbs);//2
		
		return map;		
	}

 

1)전체 코드 해석

위 코드는 Spring Framework의 @GetMapping 어노테이션을 사용하여, HTTP GET 요청에 대한 응답으로

게시판(Bbs)의 글 목록과 페이지 정보를 반환하는 메서드입니다. 해당 메서드의 구현 과정을 설명하면 다음과 같습니다.

  1. BbsParam 객체로부터 페이지 번호(pn)와 한 페이지당 출력할 글의 개수(start, end)를 계산합니다.
  2. BbsParam 객체와 계산된 start, end 값을 이용하여 BbsDto 객체의 List를 조회합니다.
  3. 전체 게시글의 수(len)를 조회합니다.
  4. 전체 게시글 수(len)와 한 페이지당 출력할 글의 개수(10)를 이용하여 전체 페이지 수(pageBbs)를 계산합니다.
  5. Map<String, Object> 객체를 생성합니다.
  6. Map 객체에 BbsDto 객체의 List와 페이지 정보(pageBbs)를 각각 "list"와 "pageBbs"라는 키(Key)로 저장합니다.
  7. 생성된 Map 객체를 반환합니다.

Map은 키-값(Key-Value) 쌍으로 데이터를 저장하는 자료구조로, Key를 이용하여 Value를 검색할 수 있습니다.

HashMap은 Map 인터페이스를 구현한 클래스 중 하나로, Key와 Value가 모두 null일 수 있고,

동기화가 되지 않은(non-synchronized) 자료구조입니다.

따라서, 위 코드에서는 Map 객체를 생성하여 BbsDto 객체의 List와 페이지 정보(pageBbs)를 각각 "list"와 "pageBbs"라는 Key로 저장한 후, 생성된 Map 객체를 반환하는 것입니다.

이 때, HashMap을 사용하여 Map 객체를 생성하였으며, HashMap 클래스는 Key와 Value가 모두 null일 수 있으므로,

Key와 Value가 null이 아닌 경우에만 사용하는 것이 좋습니다.

 

2) Map

public Map<String, Object> bbslist(BbsParam param){

스프링에서는 bbslist에서 다 싸서 보내줬지만. 부트에서는 에이젝스로 따로따로 받아야 해서 따로따로 보내야 합니다.

데이터를 두개 한꺼번에 보내기 위해서는 list 말고, Map으로 보낼 수 있습니다.

Map은 Java에서 제공하는 인터페이스(interface) 중 하나로, 키-값(key-value) 쌍으로 데이터를 저장하는 자료구조입니다.

Map 인터페이스는 Key와 Value로 이루어진 Entry 객체를 저장하며, Key를 이용하여 Value를 검색합니다.

Key는 중복될 수 없지만 Value는 중복될 수 있습니다.

 

Map 인터페이스를 구현한 클래스로는 대표적으로 HashMap, TreeMap, LinkedHashMap 등이 있습니다.

위 코드에서, 반환값이 Map<String, Object>인 경우는 BbsDto 타입의 객체들을 List에 담아서 Map의 Value에 저장하고,

각 BbsDto의 고유한 식별자를 Key로 사용하여 Map에 저장하는 경우일 가능성이 높습니다.

이렇게 구현하면, 클라이언트에서는 Key를 이용하여 BbsDto 객체를 검색할 수 있습니다.

 

🎈list에 담긴 것은 BbsDto다?

위 코드에서 list 변수는 BbsDto 객체를 담는 List입니다.

따라서 map.put("list", list); 코드에서 "list"라는 키(Key)에는 BbsDto 객체를 담고 있는 List가 값(Value)으로 저장됩니다.

map 객체에서 "list" 키로 값을 조회하면, List<BbsDto> 타입의 객체가 반환됩니다. 이 리스트 객체는 각 요소로 BbsDto 객체를 가지고 있으며, 각 BbsDto 객체는 글의 제목, 내용, 작성자, 작성일 등의 정보를 가지고 있습니다.

 

3) HashMap

HashMap은 Java에서 제공하는 Map 인터페이스를 구현한 클래스 중 하나로, Key-Value 쌍으로 데이터를 저장하는 자료구조입니다.

HashMap은 Key를 이용하여 Value를 검색하며, 중복된 Key를 허용하지 않습니다. Key와 Value는 모두 null일 수 있고,

동기화가 되지 않은(non-synchronized) 자료구조입니다.

HashMap의 장점은 데이터의 삽입과 삭제가 빠르며, 검색 속도도 빠릅니다.

또한, 데이터의 양이 많아지더라도 검색 속도가 느려지는 경우가 적습니다.

하지만, HashMap은 순서를 보장하지 않으며, Key와 Value가 null일 수 있기 때문에 검색할 때 주의가 필요합니다.

또한, *동기화가 되지 않은 자료구조이므로 멀티스레드 환경에서 안전하지 않습니다.

멀티스레드 환경에서는 ConcurrentHashMap을 사용하는 것이 좋습니다.

 

*동기화 : 동기화(synchronization)란, 둘 이상의 스레드가 공유 자원에 접근할 때, 한 스레드의 작업이 끝날 때까지 다른 스레드의 접근을 제한하여 데이터의 일관성과 안전성을 보장하는 것을 말합니다.

BbsParam.java

package mul.cam.a.dto;

import java.io.Serializable;

public class BbsParam implements Serializable{

	private String choice;	// 제목/내용/작성자
	private String search;	// 검색어
	private int pageNumber; // [1][2][3]
	
	private int start;
	private int end;
	
	public BbsParam() {
	}

	public BbsParam(String choice, String search, int pageNumber, int start, int end) {
		super();
		this.choice = choice;
		this.search = search;
		this.pageNumber = pageNumber;
		this.start = start;
		this.end = end;
	}

//getter/setter 생략

bbslist.html (프론트앤드)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>bbslist</title>

<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- 에이젝스 추가 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>

<script type="text/javascript" src="./jquery/jquery.twbsPagination.min.js"></script>

<style type="text/css">
.table th, .table td { 	text-align: center; 	vertical-align: middle!important; }
</style>

</head>
<body>

<h1>게시판</h1>

<hr>

<div align="center">

<table style="margin-left: auto; margin-right: auto; margin-top: 3px; margin-bottom: 3px">
	<tr>
		<td style="padding-left: 3px">
			<select class="custom-select" id="choice" name="choice">
				<option selected>검색</option>
				<option value="title">제목</option>
				<option value="content">내용</option>
				<option value="writer">작성자</option>
			</select>
		</td>
		<td style="padding-left: 5px" class="align-middle">
			<input type="text" class="form-control" id="search" name="search" onkeyup="enterKeyEvent()" placeholder="검색어" value="">
		<td style="padding-left: 5px">
			<span>
				<button type="button" class="btn btn-primary" onclick="searchBtn()">검색</button>
			</span>
		</td>
	</tr>
</table>

<br>

<table class="table table-hover table-sm" style="width: 1000px">
	<col width="70"><col width="600"><col width="100"><col width="150">
	<thead>
		<tr class="bg-primary" style="color: white;">
			<th>번호</th><th>제목</th><th>조회수</th><th>작성자</th>
		</tr>
	</thead>
	<tbody id="bbsBody">
		<!--  글을 여기에 올리면됨. -->
		
	</tbody>
</table>

<br>
	
<div class="container">
    <nav aria-label="Page navigation">
        <ul class="pagination" id="pagination" style="justify-content:center"></ul>
    </nav>
</div>

<a href="bbswrite.html">글쓰기</a>

</div>

<script type="text/javascript">
$(document).ready(function() {	
	/*
	$.ajax({
		url:"http://localhost:3000/bbslist",
		type:"get",
		data:{ choice:"", search:"", pageNumber:0 },	//세가지 다 보내기
		
		
		success:function(map){	//넘어오는 데이타 : map
			// alert(map);
			// alert(JSON.stringify(map));
			
			let list = map.list;
			// alert(list);
			let pageBbs = map.pageBbs;
			// alert(pageBbs);
			
			$.each(list, function (i, bbs) {
				let str = "<tr>"
							+	"<th>" + (i + 1) + "</th>"
							+	"<td style='text-align: left;'>" + bbs.title + "</td>"
							+	"<td>" + bbs.readcount + "</td>"
							+	"<td>" + bbs.id + "</td>"
						+ "</tr>"; 	
				$("#bbsBody").append(str);
			});			
		}, 
		error:function(){
			alert('error');
		}
	});
	*/	
});

getBbslist(0);


function getBbslist( pn ) {
	let choice = $("#choice").val();
	let search = $("#search").val();
	
	$.ajax({
		url:"http://localhost:3000/bbslist",
		type:"get",
		data:{ "choice":choice, "search":search, "pageNumber":pn },
		success:function(map){
			let list = map.list;
			let pageBbs = map.pageBbs;
			
			$("#bbsBody").html("");
			
			$.each(list, function (i, bbs) {
				let str = "<tr>"
							+	"<th>" + (i + 1) + "</th>";
					
					if(bbs.del == 0){		
						str +=	"<td style='text-align: left;'>" + getArrow(bbs.depth) 
							          + "<a href='bbsdetail.html?seq=" + bbs.seq + "'>" + bbs.title + "</a></td>";
					}else{
						str +=	"<td><font color='#ff0000'>*** 이 글은 작성자에 의해서 삭제되었습니다 ***</font></td>	";
					}   
					
					str +=		"<td>" + bbs.readcount + "</td>"
							+	"<td>" + bbs.id + "</td>"
						+ "</tr>"; 	
				$("#bbsBody").append(str);
			});	
			
			loadPaging(pageBbs, pn);
		}, 
		error:function(){
			alert('error');
		}
	});
}

function searchBtn() {
	getBbslist( 0 );
}


function loadPaging(pageBbs, pn) {
	
	$('#pagination').twbsPagination('destroy');
	
	$('#pagination').twbsPagination({
		startPage: pn + 1,
	    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) {
	        // 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);
	    	
	    	getBbslist( page-1 );
	    }
	});
}

function getArrow( depth ) {
	let rs = "<img src='./images/arrow.png' width='20px' height='20px'/>&nbsp;";
	let nbsp = "&nbsp;&nbsp;&nbsp;&nbsp;";
	
	let ts = "";
	for(i = 0;i < depth; i++){
		ts += nbsp;
	}
	return depth==0?"":ts + rs;
}


</script>

</body>
</html>

1) 코드해석

$(document).ready(function() {	
	/*
	$.ajax({
		url:"http://localhost:3000/bbslist",
		type:"get",
		data:{ choice:"", search:"", pageNumber:0 },	//세가지 다 보내기
		
		
		success:function(map){	//넘어오는 데이타 : map
			// alert(map);
			// alert(JSON.stringify(map));
			
			let list = map.list;
			// alert(list);
			let pageBbs = map.pageBbs;
			// alert(pageBbs);
			
			$.each(list, function (i, bbs) {
				let str = "<tr>"
							+	"<th>" + (i + 1) + "</th>"
							+	"<td style='text-align: left;'>" + bbs.title + "</td>"
							+	"<td>" + bbs.readcount + "</td>"
							+	"<td>" + bbs.id + "</td>"
						+ "</tr>"; 	
				$("#bbsBody").append(str);
			});			
		}, 
		error:function(){
			alert('error');
		}
	});
	*/	
});

위 코드는 jQuery를 사용하여, HTTP GET 요청을 보내어 게시판(Bbs)의 글 목록과 페이지 정보를 가져오는 코드입니다. 이 코드에서 사용된 기능들을 간단하게 설명해드리겠습니다.

  1. $(document).ready() : HTML 문서를 완전히 로드한 후, 코드를 실행하기 위한 jQuery 함수입니다.
  2. $.ajax() : AJAX 요청을 보내는 jQuery 함수입니다. 이 함수를 통해 서버에 HTTP GET 요청을 보내어 게시판의 글 목록과 페이지 정보를 가져옵니다.
  3. url : 요청을 보낼 URL을 지정합니다.
  4. type : 요청 방식을 지정합니다. 이 코드에서는 HTTP GET 방식을 사용합니다.
  5. data : 요청 파라미터를 지정합니다. 이 코드에서는 choice, search, pageNumber의 값을 지정하여 요청합니다.
  6. success : 요청에 대한 성공 콜백 함수입니다. 서버에서 데이터를 반환할 경우, 해당 함수가 실행됩니다.
  7. error : 요청에 대한 실패 콜백 함수입니다. 요청에 실패한 경우, 해당 함수가 실행됩니다.
  8. map : 서버에서 반환된 JSON 데이터를 담는 변수입니다.
  9. $.each() : 배열의 모든 요소에 대해 반복문을 실행하는 jQuery 함수입니다. 이 함수를 통해 글 목록 데이터를 HTML 테이블에 추가합니다.
  10. i, bbs : 반복문에서 사용하는 인덱스와 요소 변수입니다.
  11. str : HTML 테이블에 추가할 tr 태그 문자열입니다.
  12. $("#bbsBody").append(str) : HTML 테이블의 tbody에 str 문자열을 추가합니다.

 

2) $.each() 함수

$.each(list, function (i, bbs)

$.each() 함수는 배열을 반복문으로 순회하며, 각 요소에 대해 함수를 실행합니다.

예를 들어, 다음과 같은 배열이 있다고 가정해보겠습니다.

let arr = [10, 20, 30, 40, 50];

이 배열을 $.each() 함수를 사용하여 순회하면서 각 요소를 출력해보겠습니다.

$.each(arr, function(index, value) {
  console.log(index + ": " + value);
});

 

위 코드에서 $.each() 함수는 첫 번째 매개변수로 배열 arr을 받으며, 두 번째 매개변수로는 배열의 각 요소를 대입할 변수를 받습니다. 이 때, 변수 이름은 개발자가 원하는 이름으로 지정할 수 있습니다. 위 코드에서는 첫 번째 매개변수인 index는 배열의 인덱스 값, 두 번째 매개변수인 value는 배열의 요소 값입니다.

$.each() 함수가 실행될 때, 배열 arr의 모든 요소를 하나씩 순회하면서, 지정된 함수를 실행합니다. 실행 결과는 다음과 같습니다.

0: 10
1: 20
2: 30
3: 40
4: 50

이렇게  $.each() 함수를 사용하면 배열의 요소를 반복문으로 순회하며, 각 요소에 대해 함수를 실행할 수 있습니다.

 

위 코드에서 $.each() 함수는 첫 번째 매개변수로 배열 list를 받으며, 두 번째 매개변수로는 배열의 각 요소를 대입할 변수를 받습니다. 이 때, 변수 이름은 개발자가 원하는 이름으로 지정할 수 있습니다.

 

$.each(list, function(i, bbs) {...}); 코드에서 i는 배열의 인덱스 값을,

bbs는 배열 list의 요소 중 하나인 BbsDto 객체를 가리키는 변수를 의미합니다.

controlelr에서 list에 BbsDto객체를 담아서 보냈기 때문입니다. 

이때, BbsDto 객체는 seq, id, ref, step, depth, title, content, wdate, del, readcount 정보를 가지고 있습니다.

 

i는 각 요소의 인덱스 값을 가지며, bbs는 각 요소인 BbsDto 객체를 가리키는 변수입니다.  

위 코드에서는 list 배열을 반복문으로 순회하면서, 각 요소를 bbs 변수로 받아서 함수를 실행합니다.

즉, bbs는 각 반복에서 다음 요소를 가리키며, bbs.title, bbs.readcount, bbs.id와 같이 BbsDto 객체의 속성을 참조할 수 있습니다.

따라서, 위 코드에서 $.each(list, function(i, bbs) {...});는 list 배열을 반복문으로 순회하면서,

각 요소를 bbs 변수로 받아서 함수를 실행하는 것을 의미합니다.

 

컬럼이 seq, id, ref, step, depth, title, content, wdate, del, readcount 순서로 정의되어 있다면,

BbsDto 객체가 List에 담겨서 전달되는 경우 $.each(list, function (i, bbs) {...}); 코드에서

각 인덱스에서 bbs 객체의 값은 다음과 같이 매핑될 것입니다.

  • i=0일 때, bbs = {seq: 값1, id: 값2, ref: 값3, step: 값4, depth: 값5, title: 값6, content: 값7, wdate: 값8, del: 값9, readcount: 값10}
  • i=1일 때, bbs = {seq: 값1, id: 값2, ref: 값3, step: 값4, depth: 값5, title: 값6, content: 값7, wdate: 값8, del: 값9, readcount: 값10}
  • i=2일 때, bbs = {seq: 값1, id: 값2, ref: 값3, step: 값4, depth: 값5, title: 값6, content: 값7, wdate: 값8, del: 값9, readcount: 값10}
  • ...

2) 개별 코드해석

function loadPaging(pageBbs, pn) {
	
	$('#pagination').twbsPagination('destroy');
	
	$('#pagination').twbsPagination({
		startPage: pn + 1,
	    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) {
	        // 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);
	    	
	    	getBbslist( page-1 );
	    }
	});

이 함수는 Bootstrap의 페이징 기능을 사용하여 페이지 번호를 생성하고, 페이지 번호를 클릭했을 때 해당 페이지에 해당하는 게시물 목록을 불러오기 위해 getBbslist() 함수를 호출합니다.

  • pageBbs: 전체 페이지 수
  • pn: 현재 페이지 번호
  • $('#pagination').twbsPagination('destroy');: 페이징 객체를 초기화합니다.
  • $('#pagination').twbsPagination({ ... });: 페이징 객체를 생성합니다.
    • startPage: 시작 페이지 번호를 설정합니다. 현재 페이지 번호 + 1로 설정됩니다.
    • totalPages: 전체 페이지 수를 설정합니다.
    • visiblePages: 한 화면에서 보여줄 페이지 수를 설정합니다.
    • first: 맨 처음 버튼의 모양을 설정합니다.
    • prev: 이전 버튼의 텍스트를 설정합니다.
    • next: 다음 버튼의 텍스트를 설정합니다.
    • last: 맨 마지막 버튼의 모양을 설정합니다.
    • initiateStartPageClick: 페이지 번호를 클릭했을 때 onPageClick 함수를 자동으로 실행하지 않도록 설정합니다.
    • onPageClick: 페이지 번호를 클릭했을 때 실행될 함수를 설정합니다. getBbslist() 함수를 호출하여 해당 페이지에 해당하는 게시물 목록을 불러옵니다.

 

 

Bbs.xml

BbsDao.java

BbsService.java

BbsController.java

BbsParam.java

Bbs.xml

BbsDao.java

BbsService.java

BbsController.java

BbsParam.java

Bbs.xml

BbsDao.java

BbsService.java

BbsController.java

BbsParam.java

Bbs.xml

BbsDao.java

BbsService.java

BbsController.java

BbsParam.java

 

댓글