JAVA SPRING/SpringBoot

개인프로젝트 _ SpringBootBoard (2)

오동순이 2023. 5. 1. 23:28

스프링 프로젝트 구조

https://wikidocs.net/160947

 

2-01 스프링부트 프로젝트의 구조

현재 SBB 프로젝트는 HelloController.java와 HelloLombok.java 파일만 생성한 상태다. 그런데 이보다 규모를 갖춘 프로젝트를 만들고자 한다면 프로젝트…

wikidocs.net

 

SbbApplication.java 파일

모든 프로그램에는 시작을 담당하는 파일이 있다. 스프링부트 애플리케이션에도 시작을 담당하는 파일이 있는데 그 파일이 바로 <프로젝트명> + Application.java 파일이다. 스프링부트 프로젝트를 생성할때 "Sbb"라는 이름을 사용하면 다음과 같은 SbbApplication.java 파일이 자동으로 생성된다.

[파일명:/sbb/src/main/java/com/mysite/sbb/SbbApplication.java]

package com.mysite.sbb;

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

@SpringBootApplication
public class SbbApplication {

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

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

src/main/resources 디렉터리

src/main/resources 디렉터리는 자바 파일을 제외한 HTML, CSS, Javascript, 환경파일 등을 작성하는 공간이다.

templates 디렉터리

src/main/resources 디렉터리의 하위 디렉터리인 templates 디렉터리에는 템플릿 파일을 저장한다. 템플릿 파일은 HTML 파일 형태로 자바 객체와 연동되는 파일이다. templates 디렉터리에는 SBB의 질문 목록, 질문 상세 등의 HTML 파일을 저장한다.

static 디렉터리

static 디렉터리는 SBB 프로젝트의 스타일시트(.css), 자바스크립트(.js) 그리고 이미지 파일(.jpg, .png) 등을 저장하는 공간이다.

application.properties 파일

application.properties 파일은 SBB 프로젝트의 환경을 설정한다. SBB 프로젝트의 환경, 데이터베이스 등의 설정을 이 파일에 저장한다.

src/test/java 디렉터리

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

build.gradle 파일

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

 

URL 매핑

properies에 server.port=9988

로 설정해놨기 때문에 아래와 같이 URL을 실행시켜보았음

 

http://localhost:9988/sbb

404 not found HTTP 오류 코드가 나타남

제대로 클라이언트가 요청하지 않았기 때문에 나타나는데  컨트롤러Controller에 요청된 URL 매핑이 있는지 확인해야함

 

컨트롤러

package com.mysite.sbb.controller;

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

@Controller // @controller 에너테이션
public class MainController {
    @GetMapping("/sbb") // URL 매핑 http://localhost:9988/sbb 로 요청
    @ResponseBody       // @ResponseBody URL 요청에 대한 응답 
    public String index() {
        return "안녕하세요 SBB에 오신것을 환영합니다.";
    }
}

요청이 제대로 들어가서 내용이 나온느 것을 확인 할 수 있음.

 

JPA

데이터베이스를 사용하려면 SQL쿼리를 작성하여 실행하는 과정이 필요하지만 ORM(object relational mapping)을 이용하면 자바 문법만으로 데이터베이스를 다룰 수 있다. 개발자가 SQL 쿼리를 작성하지 않더라도 데이터 처리 가능!

 

ORM

SQL 쿼리와 ORM을 비교해 보자. 다음과 같은 형태로 구성된 질문 테이블에 데이터를 입력한다고 가정해 보자.

[question 테이블 구성 예]

idsubjectcontent

1 안녕하세요 가입 인사드립니다 ^^
2 질문 있습니다 ORM이 궁금합니다
... ... ...

표에서 id는 각 데이터를 구분하는 고윳값이다. 데이터베이스의 설정을 통해 값이 자동으로 증가되어 저장되도록 할수 있다.

이렇게 구성된 question 테이블에 새로운 데이터를 삽입하는 쿼리는 보통 다음처럼 작성한다.

insert into question (subject, content) values ('안녕하세요', '가입 인사드립니다 ^^');
insert into question (subject, content) values ('질문 있습니다', '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);

위와 같이 ORM을 이용한 데이터의 삽입 예제는 코드 자체만 놓고 보면 양이 많아 보이지만 별도의 SQL 문법을 배우지 않아도 된다는 장점이 있다.

코드에서 Question은 자바 클래스이며, 이처럼 데이터를 관리하는 데 사용하는 ORM 클래스를 엔티티(Entity)라고 한다. ORM을 사용하면 내부에서 SQL 쿼리를 자동으로 생성해 주므로 직접 작성하지 않아도 된다. 즉, 자바만 알아도 데이터베이스에 질의할 수 있다.

점프 투 스프링부트ORM의 장점을 더 알아보자

ORM을 이용하면 데이터베이스 종류에 상관 없이 일관된 코드를 유지할 수 있어서 프로그램을 유지·보수하기가 편리하다. 또한 내부에서 안전한 SQL 쿼리를 자동으로 생성해 주므로 개발자가 달라도 통일된 쿼리를 작성할 수 있고 오류 발생률도 줄일 수 있다.

 

Entity 생성

이제 SBB가 사용할 엔티티(Entity)을 만들어 보자. 엔티티는 데이터베이스 테이블과 매핑되는 자바 클래스를 말한다. SBB는 질문과 답변을 할 수 있는 게시판 서비스이다. 따라서 SBB에는 질문과 답변에 해당하는 엔티티가 있어야 한다.

 

Entity는 데이터베이스 테이블과 연결하기 위해서 테이블명세서를 참고하여 DB와 SpringProj에 생성하였음

 

member_info, board_info, board_image, comment_info, board_likes

테이블 명 member_info 설명 회원
순번 칼럼명 타입 null 기본값 설명
1 mi_seq bigint not null auto_increment primary key 회원 번호
2 mi_id varchar(255) not null     아이디
3 mi_pwd varchar(255) not null     비밀번호
4 mi_name varchar(255) not null     이름
5 mi_email varchar(255) not null     이메일
6 mi_grade integer not null     회원등급(일반0, 관리자99)
7 mi_status integer not null default 0   회원상태(정상0, 탈퇴1)
package com.mysite.sbb.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Entity
@Table(name = "member_info")
public class MemberInfoEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "mi_seq") 
    private Long miSeq;
    
    @Column(name = "mi_id")
    private String miId;
    
    @Column(name = "mi_pwd")
    private String miPwd;
    
    @Column(name = "mi_name")
    private String miName;
    
    @Column(name = "mi_email")
    private String miEmail;
    
    @Column(name = "mi_grade")
    private Integer miGrade;
    
    @Column(name = "mi_status")
    private Integer miStatus;
}

 

테이블 명 board_info 설명 게시판
순번 칼럼명 타입 null 기본값 설명
1 bi_seq bigint not null auto_increment primary key 번호
2 bi_mi_seq bigint not null   fk mi_seq 회원번호
3 bi_title varchar(255) not null     제목
4 bi_detail text not null     내용
5 bi_reg_dt datetime not null default current_timestamp   작성날짜
6 bi_edit_dt datetime null     수정날짜
7 bi_views int not null     조회수
8 bi_status int not null default 0   0정상 1삭제
package com.mysite.sbb.entity;

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

import com.fasterxml.jackson.annotation.JsonIgnore;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Entity
@Table(name = "board_info")
public class BoardInfoEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "bi_seq")
    private Long biSeq;

    @Column(name = "bi_title")
    private String biTitle;
    
    @Column(name = "bi_detail")
    private String biDetail;
    
    @Column(name = "bi_reg_dt")
    private LocalDateTime biRegDt;
    
    @Column(name = "bi_edit_dt")
    private LocalDateTime biEditDt;
    
    @Column(name = "bi_views")
    private Integer biViews;
    
    @Column(name = "bi_status")
    private Integer biStatus;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    @JoinColumn(name = "bi_mi_seq")
    private MemberInfoEntity memberInfo;

    @OneToMany(mappedBy = "bimgBiSeq") // 이미지를 참조하여 이미지 리스트 형식으로 보여줌, 양방향 참조
    private List<BoardImageEntity> imgs = new ArrayList<>(); // 참조되는 대상

    @OneToMany(mappedBy = "boardInfo") // 게시판을 참조하여 댓글 리스트 형식으로 보여줌, 양방향 참조
    private List<CommentInfoEntity> comment = new ArrayList<>(); // 참조되는 대상

    // 게시판 조회 수
    public void upView() {
        this.biViews = this.biViews+1;  // 누르면 조회수 1증가
    }
}

 

테이블 명 board_image 설명 게시판이미지
순번 칼럼명 타입 null 기본값 설명
1 bimg_seq bigint not null auto_increment primary key 번호
2 bimg_name varchar(255) not null     파일이름
3 bimg_uri text not null     이미지 uri
4 bimg_bi_seq bigint not null   fk bi_seq 게시글 번호
5 bimg_status int not null default 0   0정상 1삭제
package com.mysite.sbb.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Entity
@Table(name = "board_image")
public class BoardImageEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "bimg_seq") 
    private Long bimgSeq;
    
    @Column(name = "bimg_name")
    private String bimgName;
    
    @Column(name = "bimg_uri")
    private String bimgUri;
    
    @Column(name = "bimg_status")
    private Integer bimgStatus;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    @JoinColumn(name = "bimg_bi_seq")
    private BoardInfoEntity bimgBiSeq;
}

 

테이블 명 comment_info 설명 댓글
순번 칼럼명 타입 null 기본값 설명
1 ci_seq bigint not null auto_increment primary key 번호
2 ci_mi_seq int not null   fk mi_seq 회원번호
3 ci_content text not null     내용
4 ci_reg_dt datetime not null default current_timestamp   작성일
5 ci_edit_dt datetime null     수정일
6 ci_ci_seq bigint null   fk ci_seq 상위댓글 번호
7 ci_bi_seq bigint not null   fk bi_seq 게시글 번호
8 ci_status int not null default 0   0정상 1삭제
package com.mysite.sbb.entity;

import java.time.LocalDateTime;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Entity
@Table(name = "comment_info")
public class CommentInfoEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ci_seq") 
    private Long ciSeq;
    
    @Column(name = "ci_content")
    private String ciContent;

    @Column(name = "ci_reg_dt")
    private LocalDateTime ciRegDt;

    @Column(name = "ci_edit_dt")
    private LocalDateTime ciEditDt;

    @Column(name = "ci_status")
    private Integer ciStatus;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ci_mi_seq")
    private MemberInfoEntity memberInfo;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ci_bi_seq")
    private BoardInfoEntity boardInfo;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ci_ci_seq")
    private CommentInfoEntity commentInfo;

}

 

테이블 명 board_likes 설명 게시판 추천
순번 칼럼명 타입 null 기본값 설명
1 bl_seq bigint not null auto_increment primary key 번호
2 bl_mi_seq int not null   fk mi_seq 회원번호
3 bl_bi_seq int not null   fk bi_seq 글번호
4 bl_status int null     상태(0추천 1비추천)
package com.mysite.sbb.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Entity
@Table(name = "board_likes")
public class BoardLikesEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "bl_seq")
    private Long blSeq;

    @Column(name = "bl_status")
    private Integer blStatus;

    @ManyToOne(fetch = FetchType.LAZY) @JsonIgnore
    @JoinColumn(name = "bl_mi_seq")
    private MemberInfoEntity memberInfo;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bl_bi_seq")
    private BoardInfoEntity boardInfo;
}