(101)

8. 질문 상세 - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. 질문 상세 링크 추가하기 question_list.html을 다음과 같이 수정하자 제목 작성일시 타임리프에서 링크의 주소는 th:href속성을 사용한다. URL 주소를 나타낼 때 반드시 @{URL}형식을 지켜야 한다. 문자열과 자바 객체의 값을 더할 때는 반드시 | | 기호로 좌우를 감싸주어야 한다. 타임리프는 문자열을 연결할 때 | 문자를 사용한다 질문 상세 구현하기 Question 데이터를 조회하기 위해 QuestionService를 수정하자 (... 생략 ...) import java.util.Optional; import com.example.board.Data..

7. 서비스 - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. 서비스가 필요한 이유 여태까지 QuestionController에서 QuestionRepository를 직접 사용하여 질문 목록 데이터를 조회했다. 하지만 대부분의 규모있는 스프링부트 프로젝트는 컨트롤러에서 리포지터리를 직접 호출하지 않는다. 중간에 서비스(Service)를 두어 데이터를 처리한다. 서비스는 스프링에서 데이터 처리를 위해 작성하는 클래스이다. 서비스는 왜 필요할까? 모듈화 예를들어 어떤 컨트롤러가 여러개의 리포지터리를 사용하여 데이터를 조회한후 가공하여 리턴한다고 가정해 보자. 이러한 기능을 서비스로 만들어 두면 컨트롤러에서는 해당 서비스를 호출하여 사..

6. 질문 목록과 템플릿 - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. 데이터 조회하여 템플릿에 전달하기 질문 목록을 조회하기 위해서는 Question 리포지터리를 사용해야 한다. Question 리포지터리로 조회한 질문 목록은 Model 클래스를 사용하여 템플릿에 전달할수 있다. package com.example.board.Question; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping..

5. 리포지터리 - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. [ 리포지터리 ] 엔티티만으로는 데이터베이스에 데이터를 저장하거나 조회 할 수 없다. 데이터 처리를 위해서는 실제 데이터베이스와 연동하는 JPA 리포지터리가 필요하다. 리포지터리란? 엔티티에 의해 생성된 데이터베이스 테이블에 접근하는 메서드들을 사용하기 위한 인터페이스이다. 데이터 처리를 위해서는 테이블에 어떤 값을 넣거나 값을 조회하는 등의 CRUD(Create, Read, Update, Delete)가 필요하다. 이 때 이러한 CRUD를 어떻게 처리할지 정의하는 계층이 바로 리포지터리이다. package com.example.board.Question; import..

4. 엔티티 - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. [ 엔티티 ] 엔티티는 데이터베이스 테이블과 매핑되는 자바 클래스를 말한다. 엔티티는 모델 또는 도메인 모델 이라고 부르기도 한다. 질문 엔티티 작성하기 package com.example.board.Question; import java.time.LocalDateTime; import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue;..

3. JPA - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. [ JPA ] 스프링부트는 JPA(Java Persistence API)를 사용하여 데이터베이스를 처리한다. JPA는 자바 진영에서 ORM(Object-Relational Mapping)의 기술 표준으로 사용하는 인터페이스의 모음이다. JPA는 인터페이스이다. 따라서 인터페이스를 구현하는 실제 클래스가 필요하다. JPA를 구현한 대표적인 실제 클래스에는 하이버네이트(Hibernate)가 있다. 우리가 만들 게시판은 질문 답변 게시판이다. 질문이나 답변을 작성하면 데이터가 생성된다. 그러므로 데이터를 저장하거나 조회하거나 수정하는 등의 기능을 구현해야 한다. 웹 서비스는..

2. 컨트롤러(Controller) - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다. 자바 8, 스프링부트 2.7.7버전입니다. [ 컨트롤러 ] URL 매핑을 추가하기 위해 QuestionController.java 파일을 다음과 같이 작성하자. package com.example.board; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class QuestionController { @GetMapping(..

1. 프로젝트의 구조 - 점프 투 스프링부트(게시판 만들기)

해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도입니다 [ 프로젝트의 구조 ] src/main/java 디렉터리 src/main/java 디렉터리의 com.example.board 패키지는 자바 파일을 작성하는 공간이다. 자바 파일로는 스프링부트의 컨트롤러, 폼과 DTO, 데이터 베이스 처리를 위한 엔티티, 서비스 파일등이 있다. PracticeApplication.java 파일 모든 프로그램에는 시작을 담당하는 파일이 있다. 스프링부트 애플리케이션에도 시작을 담당하는 파일이 있는데 그 파일이 바로 + Application.java 파일이다. 프로젝트를 생성할 때 자동 생성된다. package com.example.board; import org.springframework.boot.Spri..

Iterator

[ Iterator ] 컬렉션 프레임워크(Collection Framework)에서 값을 가져오거나 삭제할 때 사용한다 [ 장단점 ] 모든 컬렉션 프레임워크에서 공통으로 사용이 가능하다 3개의 메서드만 알면 되어서 쉽게 값을 가져오고 제거할 수 있다 처음부터 끝까지의 단방향 반복만 가능하다 값을 변경하거나 추가하는 것이 불가능하다 대량의 데이터를 제어할 때 속도가 느리다 [ 메서드 ] Iterator.hasNext() : 다음 값이 들었는지 확인 후 true / false를 반환한다 Iterator.next() : 다음 값을 가져온다 Iterator.remove() : next시 가져왔던 값을 컬렉션에서 삭제시킨다. 반드시 next()후에 사용해야 한다 [ 사용하기 ] import java.util.Ar..

8. 질문 상세 - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 19:55
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

 

질문 상세 링크 추가하기


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>

 

타임리프에서 링크의 주소는 th:href속성을 사용한다. URL 주소를 나타낼 때 반드시 @{URL}형식을 지켜야 한다.

문자열과 자바 객체의 값을 더할 때는 반드시 | | 기호로 좌우를 감싸주어야 한다.

타임리프는 문자열을 연결할 때    |    문자를 사용한다

 

 

질문 상세 구현하기


Question 데이터를 조회하기 위해 QuestionService를 수정하자

(... 생략 ...)
import java.util.Optional;
import com.example.board.DataNotFoundException;
(... 생략 ...)
public class QuestionService {

	(... 생략 ...)
    
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepository.findById(id);
		if(question.isPresent()) {
			return question.get();
		}
		else {
			throw new DataNotFoundException("질문이 없음");
		}
	}	
}

id 값으로 Question 데이터를 조회하는 getQuestion 메서드를 추가했다.

리포지터리로 얻은 Question 객체는 Optional 객체이기 때문에 isPresent 메서드로 해당 데이터가 존재하는지 검사하는 로직이 필요하다. 만약 id값에 해당하는 Question 데이터가 없을 경우 DataNotFoundException을 발생시키도록 했다.

 

 

DataNotFoundException 클래스를 생성하자

package com.example.board;

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

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

DataNotFoundException은 RuntimeException을 상속하여 만들었다.

만약 DataNotFoundException이 발생하면 @ResponseStatus 애너테이션에 의해 404 오류(HttpStatus.NOT_FOUND)가 나타날 것이다.

 

 

질문 상세 페이지에 대한 URL 매핑을 QuestionController에 추가하자

또한 QuestionService의 getQuestion 메서드를 호출하여 Question 객체를 템플릿에 전달할 수 있도록 수정하자

(... 생략 ...)
import org.springframework.web.bind.annotation.PathVariable;
(... 생략 ...)
public class QuestionController {
	
	(... 생략 ...)
    
	@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/question_detail";
	}
	
}

 

 

이제 질문 상세 템플릿을 만들자

경로 : /practice/src/main/resources/templates/question/question_detail.html

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

 

데이터가 존재할 경우 화면에 잘 출력이 될 것이고

존재하지 않는 데이터를 조회 할 경우 404 Not found 오류가 발생할 것이다

 

설정해 놓은 "질문이 없음" 메세지가 떳다

 

 

 

URL 프리픽스(prefix)


QuestionController의 URL 매핑을 살펴보면 모두 /question으로 시작한다는 것을 알 수 있다.

이런 경우 클래스명 위에 @RequestMapping("/question") 애너테이션을 추가하고 메서드 위에서는 /question을 생략할 수 있다

(... 생략 ...)
import org.springframework.web.bind.annotation.RequestMapping;
(... 생략 ...)

@RequestMapping("/question")
@RequiredArgsConstructor
@Controller
public class QuestionController {
	
	private final QuestionService questionService;

	@GetMapping("/list")
	public String list(Model model) {
		(... 생략 ...)
	}
	
	@GetMapping(value = "/detail/{id}")
	public String detail(Model model, @PathVariable("id") Integer id) {
		(... 생략 ...)
	}
	
}

 

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

7. 서비스 - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 19:35
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

 

서비스가 필요한 이유


여태까지 QuestionController에서 QuestionRepository를 직접 사용하여 질문 목록 데이터를 조회했다.

하지만 대부분의 규모있는 스프링부트 프로젝트는 컨트롤러에서 리포지터리를 직접 호출하지 않는다.

중간에 서비스(Service)를 두어 데이터를 처리한다. 서비스는 스프링에서 데이터 처리를 위해 작성하는 클래스이다.

 

 

서비스는 왜 필요할까?

 

모듈화

예를들어 어떤 컨트롤러가 여러개의 리포지터리를 사용하여 데이터를 조회한후 가공하여 리턴한다고 가정해 보자.

이러한 기능을 서비스로 만들어 두면 컨트롤러에서는 해당 서비스를 호출하여 사용하면 된다.

하지만 서비스로 만들지 않고 컨트롤러에서 구현하려 한다면 해당 기능을 필요로 하는 모든 컨트롤러가 동일한 기능을 중복으로 구현해야 한다. 이러한 이유로 서비스는 모듈화를 위해서 필요하다.

 

보안

컨트롤러는 리포지터리 없이 서비스를 통해서만 데이터베이스에 접근하도록 구현하는 것이 보안상 안전하다.

이렇게 하면 어떤 해커가 해킹을 통해 컨트롤러를 제어할 수 있게 되더라도 리포지터리에 직접 접근할 수는 없게 된다.

 

엔티티 객체와 DTO 객체의 변환

우리가 작성한 Question, Answer 클래스는 엔티티(Entity) 클래스이다. 엔티티 클래스는 데이터베이스와 직접 맞닿아 있는 클래스이기 때문에 컨트롤러나 타임리프 같은 템플릿 엔진에 전달하여 사용하는 것은 좋지 않다. 컨트롤러나 타임리프에서 사용하는 데이터 객체는 속성을 변경하여 비즈니스적인 요구를 처리해야 하는 경우가 많은데 엔티티를 직접 사용하여 속성을 변경한다면 테이블 컬럼이 변경되어 엉망이 될수도 있기 때문이다.

 

이러한 이유로 엔티티 클래스는 컨트롤러에서 사용할수 없게끔 설계하는 것이 좋다. 그러기 위해서는 엔티티 대신 사용할 DTO(Data Transfer Object) 클래스가 필요하다. 그리고 엔티티 객체를 DTO 객체로 변환하는 작업도 필요하다.

서비스는 컨트롤러와 리포지터리의 중간자적인 입장에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.

 

 

Question Service


package com.example.board.question;

import java.util.List;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class QuestionService {

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

스프링의 서비스로 만들기 위해서는 위와 같이 클래스명 위에 @Service 애너테이션을 붙이면 된다.

@Controller, @Entity 등과 마찬가지로 스프링부트는 @Service 애너테이션이 붙은 클래스는 서비스로 인식한다.

 

그리고 질문 목록을 조회하여 리턴하는 getList 메서드를 추가했다.

이전 컨트롤러에서 리포지터리를 사용했던 부분을 그대로 옮긴 것이다.

 

 

그리고 QuestionController는 리포지터리 대신 서비스를 사용하도록 수정해야 한다.

(... 생략 ...)
public class QuestionService {

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

앞으로 작성할 컨트롤러들도 리포지터리를 직접 사용하지 않고

Controller -> Service -> Repository 구조로 데이터를 처리할 것이다.

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

6. 질문 목록과 템플릿 - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 19:18
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

 

데이터 조회하여 템플릿에 전달하기


질문 목록을 조회하기 위해서는 Question 리포지터리를 사용해야 한다.

Question 리포지터리로 조회한 질문 목록은 Model 클래스를 사용하여 템플릿에 전달할수 있다.

 

package com.example.board.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.ResponseBody;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController {
	
	private final QuestionRepository questionRepository;

	@GetMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionRepository.findAll();
		model.addAttribute("questionList",questionList);
		return "/question/question_list"; // 리턴값으로 templates/question안의 템플릿을 불러온다
	}
	
}

@RequiredArgsConstructor 애너테이션으로 questionRepository 속성을 포함하는 생성자를 생성하였다. @RequiredArgsConstructor는 롬복이 제공하는 애너테이션으로 final이 붙은 속성을 포함하는 생성자를 자동으로 생성하는 역할을 한다.

롬복의 @Getter, @Setter가 자동으로 Getter, Setter 메서드를 생성하는 것과 마찬가지로 @RequiredArgsConstructor는 자동으로 생성자를 생성한다.

따라서 스프링 의존성 주입 규칙에 의해 questionRepository 객체가 자동으로 주입된다.

스프링의 의존성 주입(Dependency Injection) 방식 3가지
@Autowired 속성 : 애너테이션을 적용하여 객체를 주입하는 방식
생성자 : 생성자를 작성하여 객체를 주입하는 방식 (권장)
Setter : Setter 메서드를 작성하여 객체를 주입하는 방식 (메서드에 @Autowired 애너테이션 적용이 필요하다)

 

 

Question 리포지터의 findAll 메서드를 사용하여 질문 목록 데이터인 questionList를 생성하고

Model 객체에 "questionList" 라는 이름으로 값을 저장했다.

Model 객체는 자바 클래스와 템플릿 간의 연결고리 역할을 한다. Model 객체에 값을 담아두면 템플릿에서 그 값을 사용할 수 있다.

Model 객체는 따로 생성할 필요없이 컨트롤러 메서드의 매개변수로 지정하기만 하면 스프링부트가 자동으로 Model 객체를 생성한다.

 

 

템플릿에서 전달받은 데이터 사용하기


question_list.html 템플릿을 생성한다

경로 : /practice/src/main/resources/templates/question/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>

질문 목록을 HTML의 테이블 구조로 표시되게 하였다.

th: 로 시작하는 속성은 타임리프 템플릿 엔진이 사용하는 속성이다. 바로 이 부분이 자바 코드와 연결된다. question_list.html 파일에 사용한 타임리프 속성들을 잠시 살펴보자.

<tr th:each="question : ${questionList}">

QuestionController의 list 메서드에서 조회한 질문 목록 데이터를 "questionList"라는 이름으로 Model 객체에 저장했다.

타임리프는 Model 객체에 저장된 값을 읽을 수 있으므로 템플릿에서 questionList를 사용할수 있게 되는 것이다.

위의 코드는 <tr> ... </tr> 엘리먼트를 questionList의 갯수만큼 반복하여 출력하는 역할을 한다.

그리고 questionList에 저장된 데이터를 하나씩 꺼내 question 객체에 대입하여 반복구간 내에서 사용할수 있게 한다.

다음 코드는 바로 앞의 for 문에서 얻은 question 객체의 제목을 <td> 엘리먼트의 텍스트로 출력한다.

<td th:text="${question.subject}"></td>

다음 코드도 같은 맥락으로 이해할 수 있다.

<td th:text="${question.createDate}"></td>

 

 

 

자주 사용되는 타임리프의 속성


 

타임리프의 자주 사용하는 속성에는 다음 3가지 유형이 있다.

 

1. 분기문 속성

th:if="${question != null}"

위의 경우 question 객체가 null 이 아닌 경우에 해당 엘리먼트가 표시된다.

 

 

2. 반복문 속성

반복문은 반복횟수만큼 해당 엘리먼트를 반복하여 표시한다. 반복문 속성은 자바의 for each 문과 유사하다.

th:each="question : ${questionList}"

반복문은 다음과 같이 사용할 수도 있다.

th:each="question, loop : ${questionList}"

추가한 loop 객체를 이용하여 루프 내에서 다음과 같은 속성을 사용할수 있다.

  • loop.index - 반복 순서, 0부터 1씩 증가
  • loop.count - 반복 순서, 1부터 1씩 증가
  • loop.size - 반복 객체의 요소 갯수 (예: questionList의 요소 갯수)
  • loop.first - 루프의 첫번째 순서인 경우 true
  • loop.last - 루프의 마지막 순서인 경우 true
  • loop.odd - 루프의 홀수번째 순서인 경우 true
  • loop.even - 루프의 짝수번째 순서인 경우 true
  • loop.current - 현재 대입된 객체 (예: 위의 경우 question과 동일)

 

3. 텍스트 속성

th:text=값 속성은 해당 엘리먼트의 텍스트로 "값"을 출력한다.

th:text="${question.subject}"

텍스트는 th:text 속성 대신에 다음처럼 대괄호를 사용하여 값을 직접 출력할수 있다.

<tr th:each="question : ${questionList}">
    <td>[[${question.subject}]]</td>
    <td>[[${question.createDate}]]</td>
</tr>

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

5. 리포지터리 - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 18:39
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

 

[  리포지터리  ]


엔티티만으로는 데이터베이스에 데이터를 저장하거나 조회 할 수 없다.

데이터 처리를 위해서는 실제 데이터베이스와 연동하는 JPA 리포지터리가 필요하다.

 

리포지터리란?

엔티티에 의해 생성된 데이터베이스 테이블에 접근하는 메서드들을 사용하기 위한 인터페이스이다.
데이터 처리를 위해서는 테이블에 어떤 값을 넣거나 값을 조회하는 등의 CRUD(Create, Read, Update, Delete)가 필요하다. 이 때 이러한 CRUD를 어떻게 처리할지 정의하는 계층이 바로 리포지터리이다.
package com.example.board.Question;

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

public interface QuestionRepository extends JpaRepository<Question, Integer> {

}
package com.example.board.Answer;

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

public interface AnswerRepository extends JpaRepository<Answer, Integer> {

}

리포지터리로 만들기 위해 JpaRepository 인터페이스를 상속했다.

JpaRepository를 상속할 때는 제네릭스 타입으로 <Question, Integer>  <Answer, Integer> 처럼 리포지터리의 대상이 되는 엔티티의 타입(Question)과 해당 엔티티의 PK의 속성 타입(Integer)을 지정해야 한다.

이것은 JpaRepository를 생성하기 위한 규칙이다.

 

이제 QuestionRepository, AnswerRepository를 이용하여 question, answer 테이블에 데이터를 저장하거나 조회할 수 있다.

 

 

테스트를 위해 ApplicationTests.java파일을 사용한다.

파일경로 : /practice/src/test/java/com/mysite/sbb/PracticeApplicationTests.java

package com.example.board;

import java.time.LocalDateTime;

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

import com.example.board.question.Question;
import com.example.board.question.QuestionRepository;

@SpringBootTest
class PracticeApplicationTests {

    @Autowired
    private QuestionRepository questionRepository;

    @Test
    void testJpa() {        
        Question q1 = new Question();
        q1.setSubject("질문1");
        q1.setContent("답변1");
        q1.setCreateDate(LocalDateTime.now());
        this.questionRepository.save(q1);  

        Question q2 = new Question();
        q2.setSubject("질문2");
        q2.setContent("답변2");
        q2.setCreateDate(LocalDateTime.now());
        this.questionRepository.save(q2); 
    }
}

파일을 작성 한 뒤 Run → Run As → JUnit Test를 선택하여 실행한다.

반드시 서버가 꺼진 상태에서 실행하여야 한다.

 

 

정상 실행되었다.

 

 

데이터베이스에 값이 잘 들어갔나 확인해 보기 위해 로컬서버를 시작하고 H2 콘솔에 접속하여 다음 쿼리문을 실행해 보자.

 

 

잘 들어온 것을 확인할 수 있다.

 

 

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

4. 엔티티 - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 18:29
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

[  엔티티  ]


엔티티는 데이터베이스 테이블과 매핑되는 자바 클래스를 말한다.

엔티티는 모델 또는 도메인 모델 이라고 부르기도 한다.

 

 

질문 엔티티 작성하기


package com.example.board.Question;

import java.time.LocalDateTime;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import com.example.board.Answer.Answer;

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> answerList;
}

엔티티로 만들기 위해 Question 클래스에 @Entity 애너테이션을 적용했다.

@Entity 애너테이션을 적용해야 JPA가 엔티티로 인식한다.

Getter, Setter 메서드를 자동으로 생성하기 위해 롬복의 @Getter, @Setter 애너테이션을 적용했다.

 

 

@Id

고유 번호 id 속성에 적용한 @Id 애너테이션은 id 속성을 기본 키로 지정한다.

기본 키로 지정하면 이제 id 속성의 값은 데이터베이스에 저장할 때 동일한 값으로 저장할 수 없다.

고유 번호를 기본 키로 한 이유는 고유 번호는 엔티티에서 각 데이터를 구분하는 유효한 값으로 중복되면 안 되기 때문이다.

데이터베이스에서는 id와 같은 특징을 가진 속성을 기본 키(primary key)라고 한다.

 

 

@GeneratedValue

@GeneratedValue 애너테이션을 적용하면 데이터를 저장할 때 해당 속성에 값을 따로 세팅하지 않아도 1씩 자동으로 증가하여 저장된다. strategy는 고유번호를 생성하는 옵션으로 GenerationType.IDENTITY는 해당 컬럼만의 독립적인 시퀀스를 생성하여 번호를 증가시킬 때 사용한다.

strategy 옵션을 생략할 경우에 @GeneratedValue 애너테이션이 지정된 컬럼들이 모두 동일한 시퀀스로 번호를 생성하기 때문에 일정한 순서의 고유번호를 가질수 없게 된다.
이러한 이유로 보통 GenerationType.IDENTITY를 많이 사용한다.

 

 

@Column

엔티티의 속성은 테이블의 컬럼명과 일치하는데 컬럼의 세부 설정을 위해 @Column 애너테이션을 사용한다. length는 컬럼의 길이를 설정할때 사용하고 columnDefinition은 컬럼의 속성을 정의할 때 사용한다.

columnDefinition = "TEXT"은 "내용"처럼 글자 수를 제한할 수 없는 경우에 사용한다.

엔티티의 속성은 @Column 애너테이션을 사용하지 않더라도 테이블 컬럼으로 인식한다.
테이블 컬럼으로 인식하고 싶지 않은 경우에만 @Transient 애너테이션을 사용한다.

 

테이블의 컬럼명

작성일시에 해당하는 createDate 속성의 실제 테이블의 컬럼명은 create_date가 된다.
createDate처럼 대소문자 형태의 카멜케이스(Camel Case) 이름은 create_date 처럼 모두 소문자로 변경되고 언더바(_)로 단어가 구분되어 실제 테이블 컬럼명이 된다.
엔티티와 Setter

일반적으로 엔티티에는 Setter 메서드를 구현하지 않고 사용하기를 권한다. 엔티티는 데이터베이스와 바로 연결되어 있으므로 데이터를 자유롭게 변경할 수 있는 Setter 메서드를 허용하는 것이 안전하지 않다고 판단하기 때문이다.
그렇다면 Setter 메서드 없이 어떻게 엔티티에 값을 저장할 수 있을까?
엔티티를 생성할 경우에는 롬복의 @Builder 어노테이션을 통한 빌드패턴을 사용하고, 데이터를 변경해야 할 경우에는 그에 해당되는 메서드를 엔티티에 추가하여 데이터를 변경하면 된다.
다만, 이 책은 복잡도를 낮추고 원활한 설명을 위해 엔티티에 Setter 메서드를 추가하여 진행하려 한다.

 

CascadeType.REMOVE

질문 하나에는 여러개의 답변이 작성될 수 있다. 이때 질문을 삭제하면 그에 달린 답변들도 모두 함께 삭제하기 위해서 @OneToMany의 속성으로 cascade = CascadeType.REMOVE를 사용했다.

Question 하나에 Answer는 여러개이므로 Question 엔티티에 추가할 답변의 속성은 List 형태로 구성해야 한다.

 

 

 

답변 엔티티 생성하기


package com.example.board.Answer;

import java.time.LocalDateTime;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import com.example.board.Question.Question;

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;
}

question 속성은 답변 엔티티에서 질문 엔티티를 참조하기 위해 추가했다.

예를 들어 답변 객체를 통해 질문 객체의 제목을 알고 싶다면 answer.getQuestion().getSubject()처럼 접근할 수 있다.

 

하지만 이렇게 속성만 추가하면 안되고 질문 엔티티와 연결된 속성이라는 것을 명시적으로 표시해야 한다.

즉, 다음과 같이 question 속성에 @ManyToOne 애너테이션을 추가해야 한다.

 

답변은 하나의 질문에 여러개가 달릴 수 있는 구조이다. 따라서 답변은 Many가 되고 질문은 One이 된다.

@ManyToOne 애너테이션을 설정하면 Answer 엔티티의 question 속성과 Question 엔티티가 서로 연결된다. (실제 데이터베이스에서는 ForeignKey 관계가 생성된다.)

@ManyToOne은 부모 자식 관계를 갖는 구조에서 사용한다.
여기서 부모는 Question, 자식은 Answer라고 할 수 있다.

 

 

 

테이블이 잘 생성된 것을 확인할 수 있다

 

 

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

3. JPA - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 17:56
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

 

 

[  JPA  ]


스프링부트는 JPA(Java Persistence API)를 사용하여 데이터베이스를 처리한다. JPA는 자바 진영에서 ORM(Object-Relational Mapping)의 기술 표준으로 사용하는 인터페이스의 모음이다.

JPA는 인터페이스이다. 따라서 인터페이스를 구현하는 실제 클래스가 필요하다.
JPA를 구현한 대표적인 실제 클래스에는 하이버네이트(Hibernate)가 있다.

 

 

우리가 만들 게시판은 질문 답변 게시판이다. 질문이나 답변을 작성하면 데이터가 생성된다. 그러므로 데이터를 저장하거나 조회하거나 수정하는 등의 기능을 구현해야 한다. 웹 서비스는 데이터를 처리할 때 대부분 데이터베이스를 사용한다.

 

출처 : 점프 투 스프링부트

 

그런데 데이터베이스를 사용하려면 SQL 쿼리(query)라는 구조화된 질의를 작성하고 실행하는 등의 복잡한 과정이 필요하다. 이때 ORM(object relational mapping)을 이용하면 자바 문법만으로도 데이터베이스를 다룰 수 있다. 즉, ORM을 이용하면 개발자가 쿼리를 직접 작성하지 않아도 데이터베이스의 데이터를 처리할 수 있다.

 

 

ORM


 

Question q1 = new Question();
q1.setSubject("질문1");
q1.setContent("내용1");
this.questionRepository.save(q1);

Question q2 = new Question();
q2.setSubject("질문2");
q2.setContent("내용2");
this.questionRepository.save(q2);

Question은 자바 클래스이며, 이처럼 데이터를 관리하는 데 사용하는 ORM 클래스를 엔티티(Entity)라고 한다.

ORM을 사용하면 내부에서 SQL 쿼리를 자동으로 생성해 주므로 직접 작성하지 않아도 된다.

즉, 자바만 알아도 데이터베이스에 질의할 수 있다.

 

ORM을 이용하면 데이터베이스 종류에 상관 없이 일관된 코드를 유지할 수 있어서 프로그램을 유지·보수하기가 편리하다.

또한 내부에서 안전한 SQL 쿼리를 자동으로 생성해 주므로 개발자가 달라도 통일된 쿼리를 작성할 수 있고 오류 발생률도 줄일 수 있다.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

2. 컨트롤러(Controller) - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 15:51
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도이며 기초 세팅은 생략하였습니다.
자바 8, 스프링부트 2.7.7버전입니다.

 

 

 

[  컨트롤러  ]


URL 매핑을 추가하기 위해 QuestionController.java 파일을 다음과 같이 작성하자.

package com.example.board;

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

@Controller
public class QuestionController {

	@GetMapping("prac")
	@ResponseBody
	public String index() {
		return "반갑습니다";
	}
}

QuestionController 클래스에 @Controller 애너테이션을 적용하면 QuestionController 클래스는 스프링부트의 컨트롤러가 된다. 그리고 메서드의 @GetMapping 애너테이션은 요청된 URL과의 매핑을 담당한다.

서버에 요청이 발생하면 스프링부트는 요청 페이지와 매핑되는 메서드를 컨트롤러를 대상으로 찾는다.

즉, 스프링부트는 http://localhost:8080/prac 요청이 발생하면 /prac URL과 매핑되는 index 메서드를 QuestionController 클래스에서 찾아 실행한다.

@GetMapping에 http://localhost:8080 과 같은 도메인명과 포트는 적지 않는다. 도메인명과 포트는 서버 설정에 따라 변하기 때문이다.

 

 

응답으로 "반갑습니다"라는 문자열을 브라우저에 출력하기 위해 index 함수의 리턴값을 String으로 변경하고 "반갑습니다"라는 문자열을 리턴했다. @ResponseBody 애너테이션은 URL 요청에 대한 응답으로 문자열을 리턴하라는 의미이다.

@ResponseBody 애너테이션을 생략한다면 "반갑습니다"라는 이름의 템플릿 파일을 찾게 된다

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

1. 프로젝트의 구조 - 점프 투 스프링부트(게시판 만들기)

Tech/Java & Spring 2022. 12. 31. 15:38
728x90
728x90
해당 게시글은 점프 투 스프링부트 교재를 통한 개인 학습 용도입니다

 

 

 

 

[  프로젝트의 구조  ]


 

 

 

src/main/java 디렉터리


src/main/java 디렉터리의 com.example.board 패키지는 자바 파일을 작성하는 공간이다. 자바 파일로는 스프링부트의 컨트롤러, 폼과 DTO, 데이터 베이스 처리를 위한 엔티티, 서비스 파일등이 있다.

 

 

PracticeApplication.java 파일


모든 프로그램에는 시작을 담당하는 파일이 있다. 스프링부트 애플리케이션에도 시작을 담당하는 파일이 있는데 그 파일이 바로 <프로젝트명> + Application.java 파일이다. 프로젝트를 생성할 때 자동 생성된다.

 

package com.example.board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PracticeApplication {

	public static void main(String[] args) {
		SpringApplication.run(PracticeApplication.class, args);
	}

}

SbbApplication 클래스에는 위와 같이 반드시 @SpringBootApplication 애너테이션이 적용되어 있어야 한다. @SpringBootApplication 애너테이션을 통해 스프링부트의 모든 설정이 관리된다.

 

 

src/main/resources 디렉터리


자바 파일을 제외한 HTML, CSS, Javascript, 환경파일 등을 작성하는 공간이다.

 

 

templates 디렉터리


resources의 하위 디렉터리인 templates 디렉터리에는 템플릿 파일을 저장한다. 템플릿 파일은 HTML 파일 형태로 자바 객체와 연동되는 파일이다.

 

 

static 디렉터리


프로젝트의 css, js, 이미지파일 등을 저장하는 공간이다.

 

 

application.properties 파일


프로젝트의 환경, 데이터베이스의 설정 등을 저장한다.

 

 

src/test/java 디렉터리


프로젝트에서 작성한 파일을 테스트하기 위한 테스트 코드를 작성하는 공간이다. JUnit과 스프링부트의 테스팅 도구를 사용하여 서버를 실행하지 않은 상태에서 src/main/java 디렉터리에 작성한 코드를 테스트할 수 있다.

 

 

build.gradle 파일


그레이들(Gradle)이 사용하는 환경 파일이다. 그레이들은 그루비(Groovy)를 기반으로 한 빌드 도구로 Ant, Maven과 같은 이전 세대 빌드 도구의 단점을 보완하고 장점을 취합하여 만든 빌드 도구이다. build.gradle 파일에는 프로젝트를 위해 필요한 플러그인과 라이브러리 등을 기술한다.

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

Iterator

Tech/Java & Spring 2022. 12. 25. 12:27
728x90
728x90

[  Iterator  ]


컬렉션 프레임워크(Collection Framework)에서 값을 가져오거나 삭제할 때 사용한다

 

 

[  장단점  ]


모든 컬렉션 프레임워크에서 공통으로 사용이 가능하다

3개의 메서드만 알면 되어서 쉽게 값을 가져오고 제거할 수 있다

 

처음부터 끝까지의 단방향 반복만 가능하다

값을 변경하거나 추가하는 것이 불가능하다

대량의 데이터를 제어할 때 속도가 느리다

 

 

[  메서드  ]


Iterator.hasNext() : 다음 값이 들었는지 확인 후 true / false를 반환한다

 

Iterator.next() : 다음 값을 가져온다

 

Iterator.remove() : next시 가져왔던 값을 컬렉션에서 삭제시킨다. 반드시 next()후에 사용해야 한다

 

 

[  사용하기  ]


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class IteratorExam {
	
	public static void main(String[] args) {
		
		ArrayList<String> list = new ArrayList<>();
		list.add("가렌");
		list.add("갈리오");
		list.add("갱플랭크");
		list.add("그라가스");
		list.add("그레이브즈");
		list.add("그웬");
		list.add("나르");
		list.add("나미");
		list.add("나서스");
		list.add("노틸러스");
		
		Iterator<String> iter1 = list.iterator();
		
		//remove만 달랑 쓰면 IllegalStateException 발생 -> next다음 쓰기
		//iter1.remove();
		
		while(iter1.hasNext()) {
			System.out.println(iter1.next());
		}			
				
		
	}
	
}

 

 

 

728x90
300x250
mag1c

mag1c

2년차 주니어 개발자.

방명록