본문 바로가기
백엔드/웹 개발

블로그 화면 구성하기 - 타임리프5 (블로그 글 수정/생성 기능 추가하기)

by study_yeon 2023. 8. 11.

2023.08.08 ~ 08.10 수업

교재 : 스프링부트3 백엔드 개발자 되기

◎ 블로그 화면 구성하기  

- 템플릿 엔진(타임리프) 이용
- 글 목록, 글 내용, 수정 화면으로 구성

2023.08.10 - [백엔드/웹 개발] - 블로그 화면 구성하기 - 타임리프4 (블로그 글 삭제 기능)

 

블로그 화면 구성하기 - 타임리프4 (블로그 글 삭제 기능)

◎ 블로그 화면 구성하기 - 템플릿 엔진(타임리프) 이용 - 글 목록, 글 내용, 수정 화면으로 구성 2023.08.09 - [백엔드/웹 개발] - 블로그 화면 구성하기 - 타임리프3 (블로그 글 뷰 구현하기) 블로그

dustj0824.tistory.com

이어서

 

▶ 수정/생성 기능 구현하기

 

1. 컨트롤러

- BlogViewController에 newArticle()메서드 추가

- id 인자는 Long 타입

 

1) 수정 기능 : /new-article?id=수정글번호

- 요청 시 id값이 있으면 수정

- findById()를 활용하여 기존 값을 가져온다
2) 생성 기능 : /new-article

- 요청 시 id값이 없으면 생성 

- 빈 ArticleViewResponse 객체를 만든다


* 웹 클라이언트로부터 요청을 받으면 요청 정보가 넘어온다
ㄱ. GET 메소드 요청 : 요청경로 사용(/서비스명/요청값/) @PathVariable({id})
ㄴ. 쿼리문자열(쿼리스트링) : ?키1=값1&키2=값2
요청변수 @RequestParam에 위의 '키 : 값' 들이 넘어옴
ㄷ. ?id= : 수정 할 id번호 쿼리문자열이 없으면 id=null이 되어 값 없음 에러 발생
이를 해결하기 위해 @RequestParam(required = false)를 작성

	@GetMapping("/new-article")
	public String newArticle(
		@RequestParam(required = false) Long id, Model model) {
		// 쿼리문자열이 없는 경우(/new-article)
		if(id == null) { // id가 없으면 글 생성
			model.addAttribute("article", new ArticleViewResponse());
		} else { // id가 있으면 글 수정(쿼리스트링이 있는 경우)
			// 1. 쿼리스트링의 id를 Article테이블에서 검색
			// 2. 검색한 테이블의 내용을 Article객체 VO객체에 저장
			// 3. VO객체를 데이터를 수정할 수 있는 DTO(ArticleViewResponse)에 저장
			// 4. DTO를 웹브라우저에게 응답
			Article article = blogService.findById(id);
			model.addAttribute("article", new ArticleViewResponse(article));
			
		}
		
		return "newArticle"; // template의 html 파일을 반환
	}

@PathVariable 과 @RequestParam 의 차이

 

@PathVariable
http://localhost/index/1 와 같이 rest api에서 값을 호출할 때 사용
URL처리시 사용

@RequestParam 
JSP의 HttpServletRequest와 유사 - getParameter를 이용
쿼리스트링 정보를 쉽게 가져오는 기능
http://localhost?index=1&page=2 와 같이 파리미터의 값과 이름을 함께 전달할 때 사용
게시판 등에서 페이지 및 검색 정보를 함께 전달하는 방식에서 사용

메소드의 파라미터값으로 @RequestParam를 넣어준다
@RequestParam("가져올 데이터의 이름")[데이터타입][가져온데이터를 담을 변수]
그리고 Model객체를 이용하여 뷰로 값을 넘겨준다

* 파라미터에 required 속성을 추가하면 해당 필드가 쿼리스트링에 존재하지 않아도 예외가 발생하지 않는다
@RequestParam(required=false)를 적용해 특정 파라미터 값만 받을 수 있다
해당 키값이 존재하지 않다고 해서 예외가 발생하지 않는다
1) 필수가 아닌 파라미터인 경우, required 속성 값을 주어 false로 지정해주면 된다.
필수가 아닌 파라미터의 값이 존재하지 않을 경우는 null 값을 할당

이름            타입               설명
value          String        파라미터 이름
required      boolean    해당 파라미터가 반드시 필수인지의 여부. 기본값은 true.


2. 뷰 만들기 

2-1) HTML : newArticle 만들기

경로 : HiThymeleaf/src/main/resources/templates

- 타임리프 설정
- 부트스트랩 설정(css 라이브러리)

- 헤더 영역, 컨테이너(컨텐츠) 영역, 푸터(꼬리말) 영역으로 구분하기

- 컨트롤러 메서드에서 반환하는 대상(뷰) 

- <article>, <section>은 자바스크립트의 시멘틱 태그 

- 수정 시에는 id(primary key)가 필요하므로 input의 타입을 hidden으로 설정하여 엘리먼트를 숨기고 th:value로 글의 id를 저장한다

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>블로그 글 작성</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- 헤더영역 -->
<div class="p-5 mb-5 text-center</> bg-light">
	<h1 class="mb-3">My Blog</h1>
	<h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>

<!-- 컨테이너영역 -->
<div class="container mt-5">
	<div class="row">
		<div class="col-lg-8">
			<article>
				<!-- 수정 글을 서버로 요청시 hidden값으로 글 번호를 요청값으로 추가 -->
				<input type="hidden" id="article-id" th:value="${ article.id }">
				
				<header class="mb-4"> <!-- 제목 입력 -->
					<input 
						type="text" placeholder="제목"
						class="form-control" id="title"
						th:value="${ article.title }">
				
				</header>
				<section class="mb-5"> <!-- 내용 입력 -->
					<textarea 
						rows="10" placeholder="내용"
						class="form-control h-25" 
						id="content" th:text="${ article.content }">
					</textarea>
				</section>
				<!-- id가 있으면 '수정'버튼을 없으면 '등록'버튼을 보이게 설정 -->
				<button th:if="${ article.id } != null" 
					type="button" id="modify-btn" 
					class="btn btn-primary btn-sm">수정</button>
				<button th:if="${ article.id } == null" 
					type="button" id="create-btn" 
					class="btn btn-primary btn-sm">등록</button>
			</article>
		</div>
	</div>
</div>
<script src="/js/articles.js"></script>
<!-- 푸터영역 -->

</body>
</html>

 

2-2) 자바스크립트

- 수정 / 생성 기능을 위한 API 구현

- crud중에 create update delete 실행 시 에러검사나 실행 기능 검사를 목적

- 수정이 완료되면 location.replace를 사용하여 `/articles/${id}` 글 하나 보여주는 주소로 이동(주소 덮어쓰기)  

- 생성이 완료되면 location.replace를 사용하여 "/articles" 전체 글 목록 보여주는 주소로 이동(주소 덮어쓰기)  

- " " (쌍따옴표)는 변하지 않는 문자열(정적)을 담을 때 사용하고  ` ` (백틱)문자열을 담는 템플릿 리터럴로 가변 값을 담을 때 활용한다 

 

※ URL API ( URLSearchParams )

- 브라우저에서 입력받은 값을 사용해 URLSearchParams 객체를 생성

let params = new URLSearchParams(location.search);

○ get()메서드 : 매개변수로 전달 받은 값으로 첫 번째로 검색되는 값을 가져온다. 없으면 null을 반환

 

※ method : 

○ GET : 존재하는 자원을 요청(디폴트 값)
- 단순히 API에 있는 데이터를 가져올 때 쓰임
PUT : 존재하는 자원 수정 요청
- headers 옵션으로 JSON 사용한다고 알려줘야 한다(요청 형식 지정)
- body옵션에는 HTML에 입력한 데이터는 JSON 형식의 문자열로 취급을 해야하기 때문에 문자열화(stringify)한다
○ POST : 새로운 자원 생성(추가) 요청
- put과 유사
- 폼을 사용해서 데이터를 만들 때, 보내는 데이터의 양이 많거나, 비밀번호 등 개인정보를 보낼 때 POST 메서드 사용
○ DELETE : 존재하는 자원 삭제 요청
- 보낼 데이터가 없기 때문에 삭제하고 원하는 화면으로 이동

// 수정버튼이 눌렸으면 그 수정버튼의 이벤트 핸들러 등록
// 수정버튼의 html태그 얻어오기
const modifyButton = document.getElementById('modify-btn');
if(modifyButton) {
	modifyButton.addEventListener('click', event => {
		let params = new URLSearchParams(location.search);
 		let id = params.get('id');
        
		fetch(`/api/articles/${id}`, {
			method: 'PUT',
			headers: {
				"Content-Type" : "application/json", 
			},
			body: JSON.stringify({
				title: document.getElementById("title").value,
				content: document.getElementById("content").value
			})
		})
		.then(() => {
			alert('수정이 완료되었습니다.');
			location.replace(`/articles/${id}`);	
		});
	});
}

// 등록버튼이 눌렸으면 그 등록버튼의 이벤트 핸들러 등록
// 등록버튼의 html태그 얻어오기
const createButton = document.getElementById('create-btn');

if(createButton) {
	createButton.addEventListener('click', event => {
		fetch("/api/articles", {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
			},
			body: JSON.stringify({
				title: document.getElementById("title").value,
				content: document.getElementById("content").value,
			}),
		}).then(()=> {
			alert("등록 완료되었습니다.");
			location.replace("/articles");
		})
	})
}

2-3) HTML :

ㄱ. article.html 수정 (블로그 글 수정)

- 자바스크립트에서 값을 읽기 위해 [수정] 버튼에 id값 부여

- 클릭 이벤트 구현 

<button type="button" id="modify-btn" 
		th:onclick="|location.href='@{/new-article?id={articleId}(articleId=${article.id})'}|" 
		class="btn btn-primary btn-sm">수정</button>

ㄴ articleList.html 수정 (블로그 글 생성)

- 컨테이너 상단에 위치 시키기
- 자바스크립트에서 값을 읽기 위해 [등록] 버튼에 id값 부여
- 클릭 이벤트 구현

<button type="button" id="create-btn" 
	th:onclick="|location.href='@{/new-article}'|"
	class="btn btn-secondary btn-sm mb-3">글 등록</button>

 


☆ 글 수정 버튼을 누르면 예외 발생 

- 400에러

java.lang.IllegalArgumentException: Invalid character found in the request target [/new-article?id={articleId}(articleId=${article.id}) ].

 

IllegalArgumentException : 

부정한 인수, 또는 올바르지 않은 인수를 메소드에 건네준 것

Invalid character found in the request target  :

특정 버전(7.0.73, 8.0.39, 8.5.7) 이상의 톰캣에서는 보안상의 이유로 쿼리스트링에 특수문자가 포함되어 있을 경우 차단하여 발생

-> 해석하면 

/new-article?id={articleId}(articleId=${article.id})에 특수문자가 포함되어서 예외가 발생 한 것으로 추정된다

 

근데 책에 있는 코드를 다운 받으면 실행이 된다 ,,?

같은 톰캣을 사용하는데 차이는  데이터베이스를 책에서는 h2를 사용하고 나는 오라클을 사용한다는 점인데 오라클 설정에는 문제가 없었다

 

고로 특수문자 때문에 에러가 발생한 것은 아니다. id 인수에서 문제가 있다는 것

id값을 읽어오는지 확인하기 위해 뷰컨트롤러에 로그를 추가하여 이클립스 콘솔에 출력이 되는지 확인했다

log.info("글 번호 : " + id);

콘솔에서 확인되지 않으므로 BlogService.java의 findById에서 id를 읽어오는데 문제가 발생한 것이다.

 

Service의 글 수정 메소드 인자의 id 타입이 Long(참조타입)으로 작성되었다 long(기본타입)으로 수정하니 400에러는 해결되었다.

결론은 인수 타입에서 차이가 발생하여 오류가 났다 

-> IllegalArgumentException 올바르지 않은 인수로 인한 오류

수정본
@Transactional // 트랜잭션 메소드
public Article update(long id, UpdateArticleRequest request) {
	// 업데이트 할 데이터가 있는지 검색
	Article article = blogRepository.findById(id)
		.orElseThrow(()-> new IllegalArgumentException("not found: " + id));
	// 데이터가 있다면 아티클 내용 업데이트
	article.update(request.getTitle(), request.getContent());
	return article;
}

(참고)

long은 기본 타입(Primitive Type), Long은 참조 타입(Reference Type)이다

 

- 기본 타입은 정수, 실수, 문자, 논리 리터럴 등 실제 메모리에 데이터 값을 직접 저장하는 타입으로서
boolean, char, byte, short, int, long, float, double이 있다

기본 타입은 null 할당이 불가능하다

- 참조 타입은 객체의 주소를 저장하는 타입으로 메모리 주소 값을 통해 객체를 참조하는 타입으로서
기본 타입을 제외한 문자열, 배열, enum, 클래스, 인터페이스가 있다


 등록 버튼이 작동하지 않는다

등록 / 수정 버튼이 작동하는지 확인하기 위해 console.log를 활용해보자

- 각각의 자바스크립트 메소드에 콘솔을 작성하여 확인해보았다

console.log('삭제 버튼 작동')
console.log('수정 버튼 작동')
console.log('등록 버튼 작동')

* 개발자 도구(콘솔)에서 에러 확인 

- 글 등록 버튼을 눌러 내용을 작성하고 등록을 누르면 브라우저 콘솔에 에러가 확인된다

- 상단에 작성한 콘솔 로그가 확인이 안되니 버튼이 작동하지 않음을 확인 

GET http://localhost/js/articles.js net::ERR_ABORTED 404

-> 경로 설정이 잘못된 경우 나타나는 에러로 보통은 경로를 중복으로 작성해서 발생한다

하지만 난 오탈자로 오류가 발생했다

js폴더에 자바스크립트는 articles.js가 아닌 article.js라는 이름이다!

그러니 경로를 찾지 못해서 404에러가 발생하였따 ... 

** newArticle.html에서 article.js를 임포트 한 부분의 오타 

<script src="/js/articles.js"></script>

+ 수정 버튼이 작동하지 않는다

* 개발자 도구(콘솔)에서 에러 확인 

Failed to load resource: the server responded with a status of 404 () 발생 

-> 대부분 작업하다 경로를 지정해주지 않았거나 수정 안했을 때 나타남 

** 등록 버튼과 동일한 부분에서 발생  newArticle.html에서 js파일을 찾지 못하여 경로 오류!

 


● 게시판을 만들며 느낀점 

- 지금까지 로그의 중요성을 전혀 느끼지 못했는데 로그를 통해 오류를 찾고 해결할 수 있다는 것을 알게되었다.

로그를 어떻게 활용해야하는지 조금은 알게되었다.

- 지금은 crud가 작동하는 단순한 흐름정도를 이해했는데 코드를 혼자 활용할 수 없을 것 같다.

많은 연습이 필요하겠구나 생각했다.

- 게시판 crud를 구성하는 것에도 많은 파일이 엮여있는데 프로젝트는 얼마나 복잡하고 클까 형상관리가 정말 중요하겠구나 싶다.

 

 

'백엔드 > 웹 개발' 카테고리의 다른 글

Git 설치  (0) 2023.08.16
Maven 프로젝트 Gradle로 변환하기  (0) 2023.08.16
블로그 화면 구성하기 - 타임리프4 (블로그 글 삭제 기능)  (0) 2023.08.10
필터, 정규표현식  (0) 2023.08.03
리스너(Listener)  (0) 2023.07.04