프로젝트 개요 : Jump to SpringBoot 클론코딩 - 회원제 QnA 게시판 웹
프로젝트 환경 : IntelliJ, SpringBoot, JPA, H2
프로젝트 코드 : https://github.com/smkim9202/sbb
컨트롤러(controller)
URL매핑
1. 로컬서버 실행
2. 'http://localhost:8080/sbb' 요청
브라우저에 Controller에 등록되지 않은 페이지 요청시 로컬서버를 실행하고 있어도 404 오류가 발생한다.
Not Found(404) 오류는 HTTP 오류코드 중 하나다.
페이지 요청 발생시 제일 먼저 컨트롤러에 요청된 페이지의 URL 매핑이 있는지 조사한다.
즉 URL매핑하기 위해 컨트롤러를 작성하고 /sbb URL에 대한 매핑을 추가해야 한다.
컨트롤러
파일명 : 프로젝트명/src/main/java/프로젝트패키지명/MainController.java
package com.mysite.sbb;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MainController {
@RequestMapping("/sbb")
@ResponseBody
public String index() {
return "index";
}
}
- @Controller : 적용한 클래스는 스프링부트의 컨트롤러가 된다.
- @RequestMapping : 요청된 URL과의 매핑을 담당한다. 도메인명과 포트를 적지 않는 이유는 서버 설정에 따라 변하기 때문이다.
- @ResponseBody : 뷰리졸버를 사용하지 않고 HTTP의 BODY에 문자 내용 직접 반환한다. 문자 반환이 아닌 객체반환도 할 수 있다. 객체를 반환하면 객체가 JSON으로 변환된다.
controller에 /sbb 매핑 후에는 브라우저에 URL매핑이 정상적으로 이루어진다.
DB - JPA
ORM(Object relational mapping)
데이터베이스에 데이터를 저장하는 테이블을 자바 클래스로 만들어 관리하는 기술.
SQL 쿼리 문법 직접적으로 몰라도 자바 문법만으로 DB를 다룰 수 있다는 장점이 있다.
자바코드에서 테이블은 클래스이며, 데이터를 관리하는 데 사용하는 ORM 클래스를 엔티티(Entity)라고 한다.
내부에서 SQL쿼리를 자동으로 생성해 주므로 직접 작성하지 않아도 된다.
=> DB 종류에 상관 없이 일관된 코드를 유지 할 수 있기 때문에 프로그램 유지보수하기가 편리하다.
=> 내부에서 안전한 SQL 쿼리를 자동으로 생성해 주므로 개발자가 달라도 통일된 쿼리를 작성해 오류 발생률을 줄인다.
[question 테이블 구성 예시]
id : 각 데이터를 구분하는 고윳값으로 DB 설정을 통해 값이 자동으로 증가되도록 할수 있다.
id | subject | content |
1 | 안녕하세요 | 가입 했어요! |
2 | 질문 있습니다 | ORM이 궁금해요 |
- SQL 쿼리 사용
//question 테이블에 새로운 데이터를 삽입하는 쿼리
insert into question (subject, content) values ('안녕하세요', '가입 인사드립니다 ^^');
insert into question (subject, content) values ('질문 있습니다', 'ORM이 궁금합니다');
- ORM 사용
//ORM을 사용하여 자바 코드로 쿼리를 대신 했을 경우
Question q1 = new Question();
q1.setSubject("안녕하세요");
q1.setContent("가입 인사드립니다 ^^");
this.questionRepository.save(q1);
Question q2 = new Question();
q2.setSubject("질문 있습니다");
q2.setContent("ORM이 궁금합니다");
this.questionRepository.save(q2);
JPA(Java Persistence API)
자바 진영에서 ORM의 기술 표준으로 사용하는 인터페이스의 모음으로 스프링부트는 JPA를 사용하여 데이터베이스를 처리한다. 인터페이스이기 때문에 구현하는 실제 클래스가 필요하다. JPA를 구현한 대표적인 실제 클래스에는 하이버네이트(Hibernate)가 있고, JPA + 하이버네이트 조합을 많이 사용한다.
H2 데이터베이스
개발용이나 소규모 프로젝트에서 사용되는 파일 기반의 경량 데이터베이소다. 개발시에 사용하다 실제 운영시스템으로 올릴 때는 규모있는 Oracle, MySQL 등의 DB를 사용하는 것이 일반적인 개발 패턴이다.
H2 설치
파일명 : 프로젝트명/bulid.gradle
dependencies {
...
runtimeOnly 'com.h2database:h2'
// runtimeOnly : 해당 라이브러리가 런타임시에만 필요한 경우 사용.
// 컴파일시에만 필요한 경우에는 compileOnly를 사용한다.
}
H2 설정
파일명 : 프로젝트명/src/main/resources/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 콘솔의 접속을 허용할지의 여부 true로 설정
- spring.h2.console.path : 콘솔 접속을 위한 URL 경로
- spring.datasource.url : 데이터베이스 접속을 위한 경로로 먼저 사용자 홈디렉터리(C:\user\사용자명) 설정된이름.mv.db(여기에선 local.mv.db) 파일을 생성 후에 설정 해줘야한다.
- spring.datasource.driverClassName : 데이터베이스 접속시 사용되는 드라이버
- spring.datasource.username : 데이터베이스의 사용자명(사용자명은 기본값인 sa로 설정)
- spring.datasource.password : 데이터베이스의 패스워드(로컬 개발 용도로 사용하기 때문에 패스워드 설정 X)
H2 콘솔 접속
http://localhost:8080/h2-console
한국어변경 / JDBC URL : spring.datasource.url로 설정한 주소 / 연결
JPA 환경설정
JPA 라이브러리 설치
파일명 : /프로젝트명/build.gradle
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//implemetation : 해당 라이브러리 설치를 위해 일반적으로 사용하는 설정
//해당 라이브러리가 변경되더라도 이 라이브러리와 연관된 모든 모듈들을 컴파일하지 않고
//직접 관련 있는 모듈들만 컴파일 하기 때문에 rebuild 속도가 빠르다
}
JPA 라이브러리 설정
파일명 : /프로젝트명/src/main/resources/application.properties
#JPA
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
- spring.jpa.properties.hibernate.dialect : 데이터베이스 엔진 종류를 설정
- spring.jpa.hibernate.ddl-auto : 엔티티를 기준으로 테이블을 생성하는 규칙을 정의
보통 개발환경에서는 update 모드를 사용하고 운영환경에서는 none 또는 validate 모드를 사용한다.
- none : 엔티티가 변경되더라도 데이터베이스를 변경하지 않는다.
- update : 엔티티의 변경된 부분만 적용한다.
- validate : 변경사항이 있는지 검사만 한다.
- create : 스프링부트 서버가 시작될때 모두 drop하고 다시 생성한다.
- create-drop : create와 동일하다. 하지만 종료시에도 모두 drop 한다.
엔티티(Entity)
데이터베이스 테이블과 매핑되는 자바 클래스
모델 또는 도메인 모델이라고 부르기도 한다.
엔티티 속성 구상
보통 만들고자 하는 서비스의 엔티티에 어떤 어떤 속성들이 필요한지 먼저 생각해야 한다.
- 질문(Question) 엔티티 구상
질문 엔티티 속성명 | 설명 |
id | 질문의 고유 번호 |
subject | 질문의 제목 |
content | 질문의 내용 |
create_date | 질문을 작성한 일시 |
- 답변(Answer) 엔티티 구상
답변 엔티티 속성명 | 설명 |
id | 답변의 고유 번호 |
question | 질문(어떤 질문의 답변인지 알아야함) |
content | 답변의 내용 |
create_date | 답변을 작성한 일시 |
엔티티 작성
- 질문(Question) 엔티티 작성
package com.mysite.sbb;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.List;
@Getter //롬복 애너테이션으로 Getter 메서드 자동으로 생성
@Setter //롬복 애너테이션으로 Setter 메서드 자동으로 생성
@Entity //JPA가 엔티티로 인식하게 해주는 애너테이션
//컬럼의 세부 설정을 하고 싶을 경우 @Column(속성명 = 속성값) 애너테이션 사용
//@Column 애너테이션 생략시에도 컬럼으로 인식
//컬럼으로 인식하고 싶지 않을 경우 @Transient 애너테이션 사용
public class Question {
@Id //기본 키로 지정하는 애너테이션
//데이터를 구분하는 유효한 값인 고유번호 속성에 적용해서 중복되지 않게 함
//DB에서 기본키(primary key)를 엔티티에서는 @id
@GeneratedValue(strategy = GenerationType.IDENTITY)
//1씩 자동으로 증가하여 저장하는 애너테이션
//strategy : 고유번호 생성하는 옵션. 옵션 생략 할 경우 @GeneratedValue으로 지정된 컬럼들이 모두 동일한 시퀀스로 번호를 생성 됨
//strategy = GenerationType.IDENTITY : 해당 컬럼만의 독립적인 시퀀스 생성하여 번호를 증가시킬 때 사용
private Integer id;
@Column(length = 200)
//length : 컬럼의 길이 설정
private String subject;
@Column(columnDefinition = "TEXT")
//columnDefinition : 컬럼의 속성을 정의
//columnDefinition = "TEXT" : 글자수를 제한 할 수 없는 경우 사용
private String content;
private LocalDateTime createDate;
//엔티티에 작성한 컬럼명이 createDate 라면 실제 DB 테이블의 컬럼명은 create_date가 된다.
//카멜케이스이름은 모두 소문자로 변경 후 언더바(_)가 단어를 구분한다.
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
//1:N 관계에 적용하는 애너테이션
//실제 DB에서 ForeignKey 관계가 생성
//mappedBy : 참조 엔티티 속성명으로 여기서는 답변엔티티에서 질문엔티티를 참조한 속성명을 전달해야한다.
//cascade = CascadeType.REMOVE : 질문을 삭제하면 그에 달린 답변들도 모두 삭제하기 위한 속성=속성값
private List<Answer> answerList;
//질문 하나에 답변은 여러개이므로 답변 속성은 List 형태로 구성
//질문 객체에서 답변 참조 : question.getAnswerList() 호출
}
- 답변(Answer) 엔티티 작성
package com.mysite.sbb.answer;
import com.mysite.sbb.Question.Question;
import com.mysite.sbb.user.SiteUser;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import javax.persistence.*;
import java.time.LocalDateTime;
@Getter
@Setter
@Entity
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(columnDefinition = "TEXT")
private String content;
@CreatedDate
private LocalDateTime createDate;
@ManyToOne
//N:1 관계에 적용하는 애너테이션으로 어떤엔티티와 연결된 속성이라는 것을 명시적으로 표시
//실제 DB에서 foreignKey 관계가 생성
//많은것(답변)은 Many, 하나(질문)는 One
//부모는 Question 자식은 Answer로 부모 자식관계를 갖는 구조
private Question question;
//질문 엔티티 참조하기 위해 추가 : 어떤 질문의 답변인지 알아야 함
//답변 객체를 통해 질문 객체의 제목 알고 싶은 경우 : answer.getQuestion().getSubject()처럼 접근
}
작성 한 엔티티 테이블에서 확인
h2 콘솔 접속해서 테이블 및 컬럼 생성 확인하기
리포지토리(Repository)
엔티티로는 DB를 생성 할 수 있지만 데이터 처리(CRUD-Create, Read, Update, Delete)를 할 수 없다.
데이터처리를 위해 실제 DB와 연동하는 JPA 리포지터리가 필요하다.
엔티티에 의해 생성된 DB 테이블에 접근하는 메서드들을 사용하기 위한 인터페이스.
CRUD를 어떻게 처리할지 정의하는 계층.
리포지토리 생성 규칙
- JpaRepository 인터페이스 상속 받기
- JpaRepository 상속 시 제네릭스 타입으로 <엔티티타입, 해당엔티티의 PK 속성의 타입> 지정
- 질문(Question) 엔티티의 리포지토리
파일명 : 프로젝트명/src/main/java/패키지명/QusetionRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
}
- 답변(Answer) 엔티티의 리포지토리
파일명 : 프로젝트명/src/main/java/패키지명/AnswerRepository.java
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AnswerRepository extends JpaRepository<Answer, Integer> {
}
도메인 별로 분류하기
도메인 : "질문", "답변", "사용자"처럼 굵직한 요구사항 또는 문제 영역을 대표하는 말로 도메인별로 패키지를 나누어 자바파일을 관리해야 한다.
'프로젝트 > clone coding' 카테고리의 다른 글
[Jump to SpringBoot : SBB] 스프링부트 windows cmd창으로 빌드하고 실행 (0) | 2022.04.21 |
---|---|
[Jump to SpringBoot : SBB] 서비스와 DTO (0) | 2022.04.20 |
[Jump to SpringBoot : SBB] thymeleaf 템플릿 엔진(+ROOT URL) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] JpaRepository 상속 받은 Repository 테스트해보기(+JUnit 프레임워크) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] IntelliJ에서 SpringBoot 사용하기 (0) | 2022.04.18 |