728x90
반응형
SMALL

템플릿 상속하기

각 템플릿의 반복되는 내용을 담아 기본 틀이 되는 템플릿을 만든다.
모든 템플릿은 이 기본 템플릿을 상속받아야 한다.

layout.html

<!DOCTYPE html>
<html lang="ko">
	<head>
		<!-- Required meta tags -->
		<meta charset="utf-8">
		<meta name ="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		
		<!-- Bootstrap CSS -->
		<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">

		<!-- sbb CSS -->
		<link rel="stylesheet" type="text/css" th:href="@{/style.css}">
		<title>Hello, sbb!</title>
	</head>
	<body>
		<!-- 기본 템플릿 안에 삽입될 내용 Start -->
		<th:block layout:fragment="content"></th:block>
		<!-- 기본 템플릿 안에 삽입될 내용 End -->
	</body>
</html>

 

question_list.html

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<table class="table">
	<thead class="table-dark">
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성일시</th>
		</tr>
	</thead>
	<tbody>
		<tr th:each="question, loop : ${questionList}">
			<td th:text="${loop.count}"></td>
			<td>
				<a th:href="@{|/question/detail/${question.id}|}"
				th:text="${question.subject}"></a>
			</td>
			<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
		</tr>
	</tbody>
</table>
</div>
</html>

 

question_detail.html

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
	<!-- 질문 -->
	<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
	<div class="card my-3">
		<div class="card-body">
			<div class="card-text" style="white-space:pre-line;" th:text="${question.content}"></div>
			<div class="d-flex justify-content-end">
				<div class="badge bg-light text-dark p-2 text-start">
					<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
		</div>
	</div>
	<!-- 답변의 갯수 표시 -->
	<h5 class="border-bottom my-3 py-2" 
		th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
	<!-- 답변 반복 시작 -->
	<div class="card my-3" th:each="answer : ${question.answerList}">
		<div class="card-body">
			<div class="card-text" style="white-space:pre-line;" th:text="${answer.content}"></div>
			<div class="d-flex justify-content-end">
				<div class="badge bg-light text-dark p-2 text-start">
					<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
		</div>
	</div>
	<!-- 답변 반복 끝  -->
	<!-- 답변 작성 -->
	<form th:action="@{|/answer/create/${question.id}|}" method="post" class="my-3">
		<textarea name="content" id="content" rows="10" class="form-control"></textarea>
		<input type="submit" value="답변등록" class="btn btn-primary my-2">
	</form>
</div>
</html>
728x90
반응형
LIST
728x90
반응형
SMALL

부트스트랩 설치

https://getbootstrap.com/docs/5.3/getting-started/download/

bootstrap-5.3.3-dist.zip파일을 다운받았다.

 

압축을 풀면 css폴더에 bootstrap.min.css파일이 있는데 이 파일을 static폴더 아래에 복사하자.

 

질문 목록에 부트스트랩 적용하기

question_list.html

<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<div class="container my-3">
<table class="table">
	<thead class="table-dark">
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성일시</th>
		</tr>
	</thead>
	<tbody>
		<tr th:each="question, loop : ${questionList}">
			<td th:text="${loop.count}"></td>
			<td>
				<a th:href="@{|/question/detail/${question.id}|}"
				th:text="${question.subject}"></a>
			</td>
			<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
		</tr>
	</tbody>
</table>
</div>

결과

질문 상세에 부트스트랩 적용하기

question_detail.html

<link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
<div class="container my-3">
	<!-- 질문 -->
	<h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
	<div class="card my-3">
		<div class="card-body">
			<div class="card-text" style="white-space:pre-line;" th:text="${question.content}"></div>
			<div class="d-flex justify-content-end">
				<div class="badge bg-light text-dark p-2 text-start">
					<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
		</div>
	</div>
	<!-- 답변의 갯수 표시 -->
	<h5 class="border-bottom my-3 py-2" 
		th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
	<!-- 답변 반복 시작 -->
	<div class="card my-3" th:each="answer : ${question.answerList}">
		<div class="card-body">
			<div class="card-text" style="white-space:pre-line;" th:text="${answer.content}"></div>
			<div class="d-flex justify-content-end">
				<div class="badge bg-light text-dark p-2 text-start">
					<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
				</div>
			</div>
		</div>
	</div>
	<!-- 답변 반복 끝  -->
	<!-- 답변 작성 -->
	<form th:action="@{|/answer/create/${question.id}|}" method="post" class="my-3">
		<textarea name="content" id="content" rows="10" class="form-control"></textarea>
		<input type="submit" value="답변등록" class="btn btn-primary my-2">
	</form>
</div>

728x90
반응형
LIST
728x90
반응형
SMALL

스태틱 디렉터리와 스타일시트

스타일시트, 즉 css파일은 static 디렉터리에 저장해야 한다.

\

CSS 파일 만들기

static 폴더 우클릭 후 [New -> File] 클릭

style.css

textarea {
	width:100%;
}

input[type=submit]{
	margin-top:10px;
}

 

템플릿에 스타일 적용

이전에 만든 html에 위의 style.css를 적용해보자.

question_detail.html

<link rel="stylesheet" type="text/css" th:href="@{/style.css}">

<h1 th:text="${question.subject}">제목</h1>
<div th:text="${question.content}">내용</div>

<h5 th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<div>
	<ul>
		<li th:each="answer : ${question.answerList}" th:text="${answer.content}"></li>
	</ul>
</div>


<form th:action="@{|/answer/create/${question.id}|}" method="post">
	<textarea name="content" id="content" rows="15"></textarea>
	<input type="submit" value="답변 등록">
</form>

 

적용된 결과

728x90
반응형
LIST
728x90
반응형
SMALL

답변 기능 만들기

답변 텍스트 창과 등록 버튼 만들기

question_detail.html

<h1 th:text="${question.subject}">제목</h1>
<div th:text="${question.content}">내용</div>

<form th:action="@{|/answer/create/${question.id}|}" method="post">
	<textarea name="content" id="content" rows="15"></textarea>
	<input type="submit" value="답변 등록">
</form>

 

답변 등록 컨트롤러 만들기

AnswerController.java

package com.mysite.sbb.answer;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.mysite.sbb.question.Question;
import com.mysite.sbb.question.QuestionService;

import lombok.RequiredArgsConstructor;

@RequestMapping("/answer")
@RequiredArgsConstructor
@Controller
public class AnswerController {
	private final QuestionService questionService;
	
	@PostMapping("/create/{id}")
	public String createAnswer(Model model, @PathVariable("id") Integer id,
			@RequestParam(value = "content") String content) {
		Question question = this.questionService.getQuestion(id);
		// TODO : 답변을 저장한다.
		return String.format("redirect:/question/detail/%s", id);
	}
}

 

답변 서비스 만들기

AnswerService.java

package com.mysite.sbb.answer;

import java.time.LocalDateTime;
import org.springframework.stereotype.Service;
import com.mysite.sbb.question.Question;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class AnswerService {
	private final AnswerRepository answerRepository;
	
	public void create(Question question, String content) {
		Answer answer = new Answer();
		answer.setContent(content);
		answer.setCreateDate(LocalDateTime.now());
		answer.setQuestion(question);
		this.answerRepository.save(answer);
	}
}
이제 만든 AnswerService를 AnswerController에서 사용해보자.

AnswerService.java

package com.mysite.sbb.answer;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.mysite.sbb.question.Question;
import com.mysite.sbb.question.QuestionService;
import lombok.RequiredArgsConstructor;

@RequestMapping("/answer")
@RequiredArgsConstructor
@Controller
public class AnswerController {
	private final QuestionService questionService;
	private final AnswerService answerService;
	
	@PostMapping("/create/{id}")
	public String createAnswer(Model model, @PathVariable("id") Integer id,
			@RequestParam(value = "content") String content) {
		Question question = this.questionService.getQuestion(id);
		
		// TODO : 답변을 저장한다.
		this.answerService.create(question, content);
		
		return String.format("redirect:/question/detail/%s", id);
	}
}

 

답변 등록하기 결과

 

질문 상세페이지에 답변 표시하기

원래 있던 답변들을 질문 아래에 표시하기 위해 question_detail.html 을 수정해보자.

question_detail.html

<h1 th:text="${question.subject}">제목</h1>
<div th:text="${question.content}">내용</div>

<h5 th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<div>
	<ul>
		<li th:each="answer : @{question.answerList}" th:text="${answer.content}"></li>
	</ul>
</div>


<form th:action="@{|/answer/create/${question.id}|}" method="post">
	<textarea name="content" id="content" rows="15"></textarea>
	<input type="submit" value="답변 등록">
</form>

 

결과

728x90
반응형
LIST
728x90
반응형
SMALL

상세 페이지 만들기

질문 목록에서 질문 제목을 클릭하면 상세페이지로 이동하게끔 하려고 한다.

 

질문 목록에 링크 추가

question_list.html

<table>
	<thead>
		<tr>
			<th>제목</th>
			<th>작성일시</th>
		</tr>
	</thead>
	<tbody>
		<tr th:each="question : ${questionList}">
			<td>
				<a th:href="@{|/question/detail/${question.id}|}"
				th:text="${question.subject}"></a>
			</td>
			<td th:text="${question.createDate}"></td>
		</tr>
	</tbody>
</table>

 

 

상세 페이지 컨트롤러 만들기

위의 링크를 클릭하면 url이 http://localhost:8080/question/detail/2 로 잘 이동한다. 하지만 이에 매핑된 url이 없어 404 오류가 뜬다.

 

QuestionController.java

package com.mysite.sbb.question;

import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController {
	
	private final QuestionService questionService;
	
	@GetMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionService.getList();
		model.addAttribute("questionList",questionList);
		return "question_list";
	}
	
	@GetMapping(value = "/question/detail/{id}")
	public String detail(Model model, @PathVariable("id") Integer id) {
		return "question_detail";
	}
}

 

question_detail.html

<h1>제목</h1>
<div>내용</div>

이제 링크 매핑은 됐지만 해당 질문에 맞는 데이터를 가져와야 한다.

 

상세 페이지에 서비스 사용하기

 

QuestionService.java

package com.mysite.sbb.question;

import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class QuestionService {

	private final QuestionRepostiory questionRepostiory;
	
	public List<Question> getList(){
		return this.questionRepostiory.findAll();
	}
	
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepostiory.findById(id);
		if (question.isPresent()) {
			return question.get();
		} else {
			throw new DataNotFoundException("question not found");
		}
	}
}

 

DataNotFoundException.java

package com.mysite.sbb;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "entity not found")
public class DataNotFoundException extends RuntimeException{
	private static final long serialVersionUID = 1L;
	public DataNotFoundException(String message) {
		super(message);
	}
}

 

QuestionController.java

package com.mysite.sbb.question;

import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController {
	
	private final QuestionService questionService;
	
	@GetMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionService.getList();
		model.addAttribute("questionList",questionList);
		return "question_list";
	}
	
	@GetMapping(value = "/question/detail/{id}")
	public String detail(Model model, @PathVariable("id") Integer id) {
		Question question = this.questionService.getQuestion(id);
		model.addAttribute("question", question);
		return "question_detail";
	}
}

 

상세 페이지 출력하기

question_detail.html

<h1 th:text="${question.subject}">제목</h1>
<div th:text="${question.content}">내용</div>

해당 id값에 맞는 질문 내용이 정상적으로 보이게 됨.

 

http://localhost:8080/question/detail/20 와 같은 없는 id값을 요청할 경우 404에러 발생.

 

 

https://exuzii.tistory.com/75

 

스프링 부트 URL 프리픽스 / 매핑 url 중복 제거

@GetMapping("/question//list") @GetMapping(value="/question/detail/{id}") 위의 url의 프리픽스가 /question으로 시작한다. 이렇게 중복될 때 해당 컨트롤러에 매핑되는 url은 항상 /question 으로 시작할 수 있도록 설정

exuzii.tistory.com

 

728x90
반응형
LIST
728x90
반응형
SMALL

서비스 활용

서비스가 필요한 이유?

복잡한 코드를 모듈화 할 수 있으며, 엔티티 객체를 DTO 객체로 변환할 수 있다.

 

서비스 만들기

원래 컨트롤러에서 리포지토리를 사용했다.
이제 컨트롤러에서 리포지토리 대신 사용할 서비스를 만들어보자.

 

QuestionService.java

package com.mysite.sbb.question;

import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class QuestionService {

	private final QuestionRepostiory questionRepostiory;
	
	public List<Question> getList(){
		return this.questionRepostiory.findAll();
	}
}

 

컨트롤러에서 서비스 사용하기

이전에 리포지토리를 사용했다면 그 중간을 이어주는 서비스를 사용하도록 변경해주자.

 

QuestionController.java

package com.mysite.sbb.question;

import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController {
	
	private final QuestionService questionService;
	
	@GetMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionService.getList();
		model.addAttribute("questionList",questionList);
		return "question_list";
	}
}

똑같이 동일한 결과를 볼 수 있다.

 

728x90
반응형
LIST
728x90
반응형
SMALL

질문 목록 만들기

http://localhost:8080/question/list 요청

 

=> 404에러 발생. 저 url과 매핑되는 것이 없기 때문.

 

질문 목록 URL 매핑하기

com.mysite.sbb.question 패키지 안에
QuestionController.java 클래스 생성.

 

QuestionController.java

package com.mysite.sbb.question;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class QuestionController {

	@GetMapping("/question/list")
	@ResponseBody
	public String list() {
		return "question list";
	}
}

재실행 하면 문자열이 출력된 것을 확인 가능.

URL 매핑에 성공한 것이다.

 

템플릿 설정하기

앞서 'question list'를 직접 자바 코드로 작성하여 문자열로 리턴하였다.

하지만 보통 자바 코드에서 직접 만들지 않고 템플릿을 사용한다.

템플릿이란? 자바 코드를 삽입할 수 있는 HTML 형식의 파일을 말한다.

 

템플릿으로는 타임리프(Thymeleaf)를 사용할 것이다.

 

타임리프 설치

//thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

build.gradle 파일에 추가하고 마우스 우클릭 후 [Gradle] -> [Refresh...] 클릭.

 

템플릿 사용하기

html파일 생성

src/main/resources/template 을 선택 후 우클릭을 눌러 [New -> File]을 클릭한다..
파일이름은 "question_list.html"로 입력한다.

<h2>Hello Template</h2>

Controller 수정

package com.mysite.sbb.question;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class QuestionController {

	@GetMapping("/question/list")
	public String list() {
		return "question_list";
	}
}
@ResponseBody 어노테이션을 삭제하고, return값은 html의 파일명으로 수정한다.

실행 후 화면.

 

데이터를 템플릿에 전달하기

이제 질문 목록의 데이터를 조회하여 템플릿을 통해 화면에 출력하려고 한다..
질문 관련 데이터를 조회하기 위해서는 QuestionRepository를 사용해야 한다.

 

Controller 수정

QuestionController.java

package com.mysite.sbb.question;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController {
	private final QuestionRepostiory questionRepostiory;

	@GetMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionRepostiory.findAll();
		model.addAttribute("questionList",questionList);
		return "question_list";
	}
}
@RequiredArgscontructor 어노테이션은 생성자 방식으로 questionRepository 객체를 주입한다. final이 붙은 속성을 포함하는 생성자를 자동으로 만들어주는 역할을 한다.

또 finalAll() 메서드를 이용하여 질문 목록을 가져와 questionList에 저장해준다.
list메서드의 매개변수에 있는 model을 통해서 템플릿으로 데이터를 전달해준다.

 

 

데이터 화면에 출력하기

html 템플릿 수정

question_list.html

<table>
	<thead>
		<tr>
			<th>제목</th>
			<th>작성일시</th>
		</tr>
	</thead>
	<tbody>
		<tr th:each="question : ${questionList}">
			<td th:text="${question.subject}"></td>
			<td th:text="${question.createDate}"></td>
		</tr>
	</tbody>
</table>
tbody 안의 내용이 가장 중요하다.
th:는 타임리프에서 사용하는 속성인데 이 곳에서 자바 코드와 연결된다. 자바 코드에서 model에 담아 전달했던 questionList를 ${questionList}를 통해 받는다.
th:each문을 통해 questionList에서 값을 하나씩 꺼내 출력한다.

실행결과.

 

루트 URL 사용하기

http://localhost:8080 으로 접속해보자.

 

현재 루트 URL을 매핑하지 않아서 404 오류가 뜬다.

 

MainController.java

package com.mysite.sbb;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {
	@GetMapping("/sbb")
	@ResponseBody
	public String index() {
		return "안녕하세요. sbb에 오신 것을 환영합니다.";
	}
	
	@GetMapping("/")
	public String root() {
		return "redirect:/question/list";
	}
}
"/"url과 매핑을 시킨다.
여기서 "redirect:/question/list" 이 부분은 루트로 들어오면 /question/list 페이지로 리다이렉트 하라는 명령어이다.

따라서 localhost:8080을 입력해도 localhost:8080/question/list로 자동으로 이동한다.

728x90
반응형
LIST
728x90
반응형
SMALL

답변 데이터 저장하기

@Autowired AnswerRepository answerRepository;

	@Test
	void testJpa() {
		//답변 데이터 저장하기
		Optional<Question> oq = this.questionRepostiory.findById(2);
		assertTrue(oq.isPresent());
		Question q = oq.get();
				
		Answer a = new Answer();
		a.setContent("네 자동으로 생성됩니다.");
		a.setQuestion(q);
		a.setCreateDate(LocalDateTime.now());
		this.answerRepository.save(a);
	}

 

 

답변 데이터 조회하기

@Test
	void testJpa() {
		//답변 데이터 조회하기
		Optional<Answer> oa = this.answerRepository.findById(1);
		assertTrue(oa.isPresent());
		Answer answer = oa.get();
		assertEquals(2, answer.getQuestion().getId());
	}

 

답변 데이터를 통해 질문 데이터 찾기 VS 질문 데이터를 통해 답변 데이터 찾기

@Test
@Transactional
	void testJpa() {
		//질문 데이터를 통해 답변 데이터 찾기
		Optional<Question> oq = this.questionRepostiory.findById(2);
		assertTrue(oq.isPresent());
		Question question = oq.get();
		
		List<Answer> answerList = question.getAnswereList();
		
		assertEquals(1, answerList.size());
		assertEquals("네 자동으로 생성됩니다.", answerList.get(0).getContent());
	}

 

현재의 패키지 상태. 패키지들을 도메인대로 나눔.

728x90
반응형
LIST
728x90
반응형
SMALL
@Test
	void testJpa() {
		//findBySubjectLike
		List<Question> qList = this.questionRepostiory.findBySubjectLike("sbb%");
		Question q = qList.get(0);
		assertEquals("sbb가 무엇인가요?", q.getSubject());
		
	}

리포지터리 생성

앞서 만든 엔티티에 대해 데이터들을 CRUD할 수 있도록 도와주는 인터페이스를 작성한다.

package com.mysite.sbb;

import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepostiory extends JpaRepository<Question, Integer> {

}
package com.mysite.sbb;

import org.springframework.data.jpa.repository.JpaRepository;

public interface AnswerRepository extends JpaRepository<Answer, Integer> {

}

JpaRepository를 상속받아야 함.

 

JUnit 설치

//JUnit
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org..junit.platform:junit-platform-launcher'

build.gradle 수정.

 

질문 데이터 저장하기 (Test 이용)

package com.mysite.sbb;

import java.time.LocalDateTime;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SbbApplicationTests {
	
	@Autowired
	private QuestionRepostiory questionRepostiory;

	@Test
	void testJpa() {
		Question q1 = new Question();
		q1.setSubject("sbb가 무엇인가요?");
		q1.setContent("sbb에 대해서 알고 싶습니다.");
		q1.setCreateDate(LocalDateTime.now());
		this.questionRepostiory.save(q1);
		
		Question q2 = new Question();
		q2.setSubject("스프링 부트 모델 질문입니다.");
		q2.setContent("id는 자동으로 생성되나요?");
		q2.setCreateDate(LocalDateTime.now());
		this.questionRepostiory.save(q2);
	}

}

테스트 실행은 [Run As] -> [Junit Test]
* 테스트를 실행할 때는 server를 끄고 실행해야함!!

@Autowired 어노테이션은 repository를 new Repository();로 생성하지 않고 spring이 만들어주는 것을 사용하겠다는 의미.

데이터가 잘 들어간 것을 확인할 수 있음.

 

질문 데이터 조회하기

findAll()

//질문 조회하기
List<Question> all = this.questionRepostiory.findAll();
assertEquals(2, all.size());
		
Question q = all.get(0);
assertEquals("sbb가 무엇인가요?", q.getSubject());

test 코드에 추가.

 

* asserEquals 메서드는 jupiter에서 제공함. 두 인자값을 비교해주는 역할.
* findAll() 메서드는 jpa에서 제공하는 기본 메서드.

테스트 성공.

 

findById()

id값으로 찾기

@Test
	void testJpa() {
		//findById
		Optional<Question> oq = this.questionRepostiory.findById(1);
		if (oq.isPresent()) {
			Question question = oq.get();
			assertEquals("sbb가 무엇인가요?", question.getSubject());
		}
	}

 

findBySubject()

findBySubject는 JPA에서 제공해주는 것이 아니기 때문에 직접 선언을 해주어야 한다.
그럼 select * from question where subject=? 형식으로 쿼리를 자동으로 날린다.
package com.mysite.sbb;

import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepostiory extends JpaRepository<Question, Integer> {
	Question findBySubject(String subject);
}
@Test
	void testJpa() {
		//findBySubject
		Question question = this.questionRepostiory.findBySubject("sbb가 무엇인가요?");
		assertEquals(1, question.getId());
	}

 

 

findBySubjectAndContent()

Question findbySubjectAndContent(String subject, String content);

QuestionRepository에 메서드 추가.

@Test
	void testJpa() {
		//findBySubjectAndContent
		Question q = this.questionRepostiory.findbySubjectAndContent("sbb가 무엇인가요?",
				"sbb에 대해서 알고 싶습니다.");
		assertEquals(1, q.getId());
	}

 

findBySubjectList()

List<Question> findBySubjectLike(String subject);

@Test
	void testJpa() {
		//findBySubjectLike
		List<Question> qList = this.questionRepostiory.findBySubjectLike("sbb%");
		Question q = qList.get(0);
		assertEquals("sbb가 무엇인가요?", q.getSubject());
		
	}

 

질문 데이터 수정하기

@Test
	void testJpa() {
		//질문 데이터 수정하기
		Optional<Question> oq= this.questionRepostiory.findById(1);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		q.setSubject("수정된 제목");
		this.questionRepostiory.save(q);
	}

제목이 수정된 것을 확인할 수 있음.

 

질문 데이터 삭제하기

@Test
	void testJpa() {
		//질문 데이터 삭제하기
		assertEquals(2, this.questionRepostiory.count());
		Optional<Question> oq= this.questionRepostiory.findById(1);
		assertTrue(oq.isPresent());
		Question q = oq.get();
		this.questionRepostiory.delete(q);
		assertEquals(1, this.questionRepostiory.count());
	}

ID가 1인 question이 삭제됨.

728x90
반응형
LIST
728x90
반응형
SMALL

H2 데이터베이스

데이터 베이스 설치하기

1. build.gradle 라이브러리 설치.

//h2 database
runtimeOnly 'com.h2database:h2'

build.gradle 파일 마우스 오른쪽 클릭 -> gradle -> Refresh Gradle Project 클릭.

 

2. application.properties 설정

#DATABASE
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:~/local
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

데이터베이스 설정 코드 추가.

- spring.h2.console.enabled : H2 콘솔 접속 여부
- spring.h2.console.path : H2 콘솔로 접속하기 위한 URL경로.
- spring.datasource.url : 데이터베이스에 접속하기 위한 경로.
- spring.datasource.driverClassName : 데이터베이스에 접속할 때 사용하는 드라이버 클래스명
- spring.datasource.username : 데이터베이스 사용자명
- spring.datasource.password : 데이터베이스 비밀번호

 

3. 데이터베이스 파일 생성

jdbc:h2:~/local 이렇게 설정했을 때 '~'는 사용자의 홈 디렉토리.
아래에 H2데이터베이스파일로 local.mv.db라는 파일을 생성한다.
copy con local.mv.db + (Ctrl+Z)

만들어진 것 확인.

 

4. H2 데이터베이스 접속

http://localhost:8080/h2-console

설정한대로 입력한 후 Connect클릭.

 

연결완료.

 

JPA

JPA 환경 설정하기

1. build.gradle에 라이브러리 추가

//JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

build.gradle 파일 마우스 오른쪽 클릭 -> gradle -> Refresh Gradle Project 클릭.

 

2. applicaation.properties 수정

#JPA
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

 

게시판 웹의 엔티티 작성

질문 엔티티

package com.mysite.sbb;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Question {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Column(length = 200)
	private String subject;
	
	@Column(columnDefinition = "TEXT")
	private String content;
	
	private LocalDateTime createDate;
    
    //관계주입
    @OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
	private List<Answer> answereList;
}

 

답변 엔티티

package com.mysite.sbb;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Answer {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Column(columnDefinition = "TEXT")
	private String content;
	
	private LocalDateTime createDate;
	
	//관계주입
	@ManyToOne
	private Question question;
}

 

 

728x90
반응형
LIST

+ Recent posts