항해99 4주차 WIL - SQL / ORM / MVC

Ming-Gry·2022년 10월 16일
1

항해99 WIL

목록 보기
4/12
post-thumbnail

어느 정도 수준까지 블로그를 작성해야 하는지는 항상 고민거리다. 어쨌든 이번 포스팅도 내가 공부한 만큼, 그리고 내가 설명 가능한 부분까지 작성해 보겠다.

1) SQL

1-1) SQL 이란?

SQL 의 뜻

SQL 이란 Structured Query Language 의 약자로, '구조화 질의어' 라는 뜻이다. RDBMS 의 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어이다.

SQL : https://ko.wikipedia.org/wiki/SQL

역시나 이렇게 간단하게 끝날 내 포스팅이 아니다. RDBMS 와 SQL 이 어떤 것인지 조금 더 자세히 알아보자.

DB 와 DBMS, RDBMS

DB(Database) 는 사실 그 정의를 논하는 것이 무의미할 정도로 많은 사람들이 알고 사용하는 단어인 것 같다. 그럼에도 불구하고 아래의 유튜브에서 정의내린 것을 빌려 말하자면, 전자적으로 저장되고 사용되는 관련있는 데이터들의 조직화된 집합을 말한다.

DBMS(Database Management Systems) 는 영어 뜻대로 DB 를 관리할 수 있도록 만들어주는 소프트웨어 시스템이다.

그 중에서도 RDBMS(Relational Database Management Systems) 는 관계형 데이터베이스 시스템으로, 데이터베이스 테이블이 마치 엑셀처럼 행과 열로 구분되는 것을 말한다. 특히 각 행이 구분될 수 있도록 그 행이 갖는 고유한 값인 PK(Primary Key, 기본 키) 를 두고, FK(Foreign Key, 외래 키) 를 사용해 관련있는 테이블 사이의 관계를 만들 수 있다는 특징이 있다.

결국, SQL 은 이 RDBMS 를 관리(데이터의 삽입, 수정, 삭제 등) 을 할 수 있도록 하는 프로그래밍 언어라는 뜻이다.

그렇다면 SQL 로 RDBMS 를 어떻게 관리하는 가에 대한 내용은 아래에서 조금 더 자세히 설명하도록 하겠다.

백엔드에서 사용되는 데이터베이스(database) 기본 개념 : https://www.youtube.com/watch?v=aL0XXc1yGPs&list=WL&index=18

1-2) SQL 을 공부해야 하는 이유

항해 과정 중 미니 프로젝트와 클론 프로젝트 중에는 SQL 공부가 필요하다는 생각을 전혀 해본 적이 없었다. JPA 만으로도 충분했기 때문이다. 그러나 실전 프로젝트에 들어가며 JPA 의 한계를 맞이하게 되었고, 성능 개선을 위해 Query Tuning 및 QueryDSL 사용이 필요했고, SQL 문 공부가 필요한 시점이 찾아오게 되었다.

이 글을 보고 있는 당신이 아직 초보자이고 아직 JPA 로도 충분하다면 지금은 프로젝트에 충실히 임하고, 정말 필요할 때 다시 공부하기를 권장한다. 아직 Spring 이나 Java 도 익숙하지 않은데 SQL 까지 공부하려면 정말 숨이 턱 막힌다.

일단은 JPA 의 다양한 사용법 (findTop5, findByNameLike 등과 같은) 까지 충분히 익힌 후에 공부해도 늦지 않다고 생각한다. 그리고 JPA 의 다양한 사용법을 익힌다면 SQL 도 충분히 이해가 잘 될 정도로 성장해 있을 것이다.

중요한 것은 DB 는 백엔드와 맞닿아 있기 때문에 좋든 싫든 개발 및 현업 중에 반드시 알고 사용할 수 있어야 한다는 것이다. 물론 아직 현업자는 아니지만... 위에서도 썼고, 아래에서도 다루겠지만 JPA 의 한계가 명확하기 때문에 SQL 을 다룰 줄 아는 것이 백엔드 개발자의 기본 소양 이라고 생각한다. 아직 엄청 잘 다루는 것은 아니지만...

어쨌든 여기에서는 복잡한 SQL 문을 다루거나 Query Tuning 및 QueryDSL 은 다루지 않을 것이고, 간단한 테이블을 만들어보며 SQL 문이 어떻게 작성되고 어떻게 생겼는지 구경(?) 해보도록 하자.

[Spring JPA] Query Method : https://velog.io/@seongwon97/Spring-Boot-Query-Method

RDBMS 테이블 만들기

ERD 설계하기

ERD 란 Entity Relationship Diagram 의 약자로, 개체 관계 다이어그램이라는 뜻을 갖고 있다. 예를 들어 우리가 Spring 과 RDBMS, 그중에서도 MySQL 을 사용하여 게시판 기능을 만든다고 해보자. 특히 해당 게시판에는 댓글을 작성할 수 있다고 가정해 보면, 하나의 게시글은 여러 개의 댓글을 포함하는 관계를 맺게 될 것이다. 이러한 관계를 다이어그램으로 나타낸 것이 ERD 이다.

사진에서 보다시피 Post 테이블에는 제목에 해당하는 title내용에 해당하는 postContent 를, Reply 테이블에는 댓글에 해당하는 replyContent 를 입력하여 간단한 테이블 설계까지 마쳤다. 여기서 특히 주목해야할 점은 게시글과 댓글은 1:N 의 관계를 맺고 있는 것을 볼 수 있다. 이 사진은 아래의 예시에서도 계속 사용할 것이기 때문에 잘 기억해놓도록 하자.

이 포스팅에서는 연관관계에 대한 내용을 다루고자 하는 것이 아니기 때문에 관련 내용은 아래의 포스팅을 참고하도록 하자.

연관관계 매핑 기초 : https://catsbi.oopy.io/ed9236a0-6521-471d-8a0d-b852147b5980
JPA 연관 관계 한방에 정리 : https://jeong-pro.tistory.com/231
[DB] 📈 데이터 모델링 개념 & ERD 다이어그램 그리기 💯 총정리 : https://inpa.tistory.com/entry/DB-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81-1N-%EA%B4%80%EA%B3%84-%F0%9F%93%88-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8

RDBMS Data Type

위의 사진에서 보다시피, 조금 생소한 녀석이 눈에 띈다. long 옆에 BIGINT 로, String 옆에는 VARCHAR(255) 라고 되어 있는데, 이렇게 작성해 놓은 이유는 Java 에서 사용하는 데이터 타입과 RDBMS 에서 사용하는 데이터 타입이 다르기 때문이다.

RDBMS 종류에 따라 데이터 타입이 일부 지원하지 않거나 다른 경우도 있지만 거의 표준에 가까운 MySQL 의 데이터 타입 및 Java 와 비교 자료가 있으니 아래의 포스팅을 참고해보도록 하자.

어쨌든 지금 하려는 것은 SQL 문을 작성하여 직접 MySQL 에 테이블을 생성한다는 가정이니 Java 와 비교 자료는 아래의 JPA 에서 확인하는 것이 좋을 것 같다.

MYSQL 데이터 타입 : https://blog.martinwork.co.kr/mysql/2020/01/17/mysql-data-type.html
MySQL 데이터 타입 Java 데이터 타입 비교 : https://goldfishhead.tistory.com/2
SQL의 개념과 SQL로 데이터베이스를 정의하는 법 : https://www.youtube.com/watch?v=c8WNbcxkRhY&list=WL&index=31

SQL 문 작성해보기

위의 SQL의 개념과 SQL 로 데이터베이스를 정의하는 법을 꼭 보길 바란다. 정말 정리가 잘되어 있다. 어쨌든 해당 영상을 참고해 Constraint 중 하나인 Not Null 을 추가하고 Foreign Key 의 Cascade 옵션을 사용하여 PK의 업데이트와 삭제가 Reply 테이블에 반영되도록 하였다.

게시글의 제목과 내용에 각각 50자, 255자의 제한을 걸어주었고, 댓글에도 100자 제한을 걸어주었다.

create table Post ( //테이블 생성 메소드 입력
	id	BIGINT	PRIMARY KEY, //Primary Key 정의, 확장성을 생각해 long 에 대응되는 BIGINT 사용
    title	VARCHAR(50) NOT NULL, //가변 문자열 50 자 제한
    post_content	VARCHAR(255) NOT NULL //가변 문자열 255 자 제한
);
create table Reply ( //테이블 생성 메소드 입력
	id	BIGINT	PRIMARY KEY, //Primary Key 정의, 확장성을 생각해 long 에 대응되는 BIGINT 사용
    post_id	BIGINT //post 테이블 PK 의 타입을 그대로 가져옴
    FOREIGN KEY (post_id)
    	references Post(id) //post 테이블의 id 를 Foreign Key 로 사용
        on delete CASCADE //PK 삭제 시 Cascade 옵션 적용
        on update CASCADE //PK 변경 시 Cascade 옵션 적용
    reply_content	VARCHAR(100) NOT NULL, //가변 문자열 100 자 제한
);

간단한 테이블을 만드는 코드라 그렇게 어렵진 않은 것 같다. 그렇다고 잘 짠 것 같지도 않지만... 어떤가 생각보다 SQL 을 짜보는 것도 할만한 것 같지 않은가? 생각보다 굉장히 직관적인 코드라서 한 번 배워두면 굉장히 유용할 것이다.

CRUD 해보기

Create

insert into Post //정해진 attribute 순서대로 입력
	values (1, 'SQL 에 대한 모든 것', 'SQL 문 그까이꺼 별 거 없죠?');
insert into Post (title, post_content, id) //attribute 순서 custom
	values ('ORM 은 뭔데?', 'ORM 도 해야되는데 큰일 났어요', 2);
insert into Post values //다중 tuple 입력
	(3, 'No-SQL 은 뭘까?', 'NO! SQL 아닙니다. Not only SQL 입니다.'),
    (4, 'JDBC 는?', 'Java Database Connectivity 랍니다.'),
    (5, 'MVC Pattern 이란?', 'Model, View, Controller라는 디자인 패턴이죠');

insert into 후에 테이블 이름을 입력하고 value 에 입력하려는 값을 넣었다. 맨 위에 입력한 SQL 문 처럼 정해진 순서대로 입력할 수도, attribute 순서를 내 맘대로 입력할 수도(물론 입력하지 않는다면 null 이 입력된다, 위의 경우에는 모든 attribute 에 not null constraints 가 걸려있기 때문에 null 을 입력하면 에러가 발생한다), 다중으로 여러 개의 데이터를 입력할 수도 있다.

insert into Reply values 
	(1, 2, '괜찮아요! 하면 되죠!'),
    (2, 5, 'MVVC 패턴이라는 것도 있던데요!!'),
    (3, 1, '그러게요ㅋㅋㅋ 생각보다 할 만 하네요'),
    (4, 3, 'NO-SQL 의 종류도 궁금해요'),
    (5, 4, '지금까지 JDBC 와 JPA 를 혼동해서 알고 있었습니다...'),
    (6, 4, '이게 끝인가요?');

그리고 댓글 테이블에도 순서대로 1~5 까지의 댓글을 만들고, Post 의 PK 를 입력해 연관 관계를 나타내주었다. 댓글 1번은 2번 게시글과, 댓글 5번과 6번은 4번 게시글에 대응되는 댓글이다.

Read

select * from Post;

select 는 RDB 의 열을 선택하는 것이고, from 은 테이블을 선택하는 명령어이다. 여기서 select 에 와일드 카드인 * 를 입력하여 모든 열을 불러오도록 했고 from 에 Post 를 넣어 Post 테이블의 모든 열을 불러오도록 했다. 뒤에 어떠한 조건절도 없으므로 아래처럼 모든 행이 검색되어 나온다.

id	|	title	|	post_content
1	|	'SQL 에 대한 모든 것'	|	'SQL 문 그까이꺼 별 거 없죠?'
2	|	'ORM 은 뭔데?'	|	'ORM 도 해야되는데 큰일 났어요'
3	|	'NO-SQL 은 뭘까?'	|	'NO! SQL 아닙니다. Not Only SQL 입니다.'
4	|	'JDBC 는?'	|	'Java Database Connectivity 랍니다.'
5	|	'MVC Pattern 이란?'	|	'Model, View, Controller라는 디자인 패턴이죠'
select id, reply_content from Reply where post_id = 4;

4번 게시글의 댓글 중 댓글 번호와 댓글 내용만 알고 싶다면 위 처럼 select 문에 열의 이름을 적고 where 문에 조건을 입력한다면 아래처럼 검색되어 결과가 나올 것이다.

id	|	post_content
5	|	'지금까지 JDBC 와 JPA 를 혼동해서 알고 있었습니다...'
6	|	'이게 끝인가요?'

Update

update Reply set reply_content = '패턴에 대해 더 공부해봐야겠습니다!' where id = 2;

2번 댓글을 수정하는 SQL 문이다. 테이블을 선택한 후 set 이후에 수정하려는 attribute 의 값을 넣고 where 절의 조건을 통해 2번 댓글을 선택해주었다. 점점 SQL 문에 자신감이 생기는 자신을 발견할 수 있을 것이다.

Delete

delete from Post where id = 5;

삭제는 더 쉽다. delete from 으로 테이블을 선택하고 where 에 조건을 통해 5번 게시글을 삭제하였다. 이때 기억해야할 것이 Cascade 옵션이다. 우리는 댓글 테이블을 만들 때 Cascade 옵션을 주었기 때문에 5번 게시글이 삭제될 때 5번 게시글에 해당하는 댓글도 함께 삭제될 것이다. 따라서 2번 댓글도 함께 삭제된다.

생각보다 그렇게 어렵진 않다. SQL 문 또한 다른 프로그래밍 언어와 마찬가지로 많이 연습해보고 눈에 익히는게 가장 좋은 것 같다. 아래의 SQL Tutorial 사이트에서 연습할 수 있으니 이것 저것 많이 사용해보자 특히나 where 절을 통해 다양한 조건을 줄 수 있으니 이를 꼭! 연습해보고 숙달시키도록 하자.

SQL로 DB에 데이터를 추가(insert)하고 수정(update)하고 삭제(delete)하는 방법 : https://www.youtube.com/watch?v=mgnd5JWeCK4&list=WL&index=22
SQL로 데이터 조회하기 : https://www.youtube.com/watch?v=dTBwgWMUguE&list=WL&index=6
SQL Tutorial : https://www.w3schools.com/sql/default.asp

1-3) NO-SQL 은 뭐야?

NO-SQL 이란 NO SQL 이 아니라 Not Only SQL 의 약자로, SQL 이 아닌 모든 DBMS 를 말한다. 사실 SQL 과 NO-SQL 이 아니라 RDBMS 와 NO-SQL 을 비교하는 게 의미가 있을 것 같다.

간단하게나마 짚고 넘어가면, RDBMS 는 관계형으로 데이터를 저장하지만 NO-SQL 은 MongoDB 와 같은 Document Model, CassandraDB 와 같은 Key-Value Model, Neo4j 와 같은 Graph Model 처럼 다양한 방식으로 데이터를 저장한다.

어쨌든 여기서 다룰 것은 RDBMS 와 NO-SQL 을 비교하고자 하는 것이 아니기 때문에 자세한 내용은 아래의 유튜브를 보고 공부해보도록 하자.

RDB vs NoSQL : https://www.youtube.com/watch?v=4PxBaojUnU4&list=WL&index=12
SQL vs NoSQL 5분컷 설명 : https://www.youtube.com/watch?v=Q_9cFgzZr8Q

2) ORM

2-1) ORM 이란?

ORM 이란 Object Relation Mapping 의 약자로, 객체와 DB의 테이블을 Mapping 시켜 RDB 테이블을 객체지향적으로 사용하게 해주는 기술이다. 위에서 봤듯이 RDB데이터를 정교화해서 잘 보관하는 것이 목표라면, Java 는 필드 메소드를 캡슐화해서 사용되는 것을 지향하기 때문이다. 이것을 패러다임의 불일치라고 하는데, 이를 해결해주는 것이 ORM 이며 대표적으로 JPA 와 그의 구현체인 Hibernate 가 있다.

JPA 를 알아보기 이전에 JDBC 와 SQL Mapper 부터 살펴보도록 하자.

2-2) JDBC 와 SQL Mapper

JDBC 란?

JDBC 란 Java Database Connectivity 의 약자로, 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API 이다. 이름에서 보다시피 자바를 이용해 다양한 DB 에 접근할 수 있도록 도와주는 기술이다.

만약에 MySQL, OracleDB, PostgreSQL 을 모두 사용하고 있는 프로젝트가 있다고 해보자. DB 에 접근하려면 DB Connection 을 얻어 SQL 을 전달 및 실행한 후 DB Connection 을 다시 닫아줘야 한다. JDBC 가 없다면, 각 DB 에 맞는 코드를 작성해야하는 상황이 생길 것이다. 지금 사용하고 있는 DB 가 세 개나 되다보니 저장할 데이터에 따라 각 DB 에 맞는 Connection 을 연결해서 SQL 을 전달하고 Connection 을 닫아주도록 코드를 작성해야될 것이다. 더해서 DB 를 다른 DB로 변경하거나 DB 를 추가하는 일이 생긴다면 서버에 개발된 DB 코드도 함께 변경하거나 추가해야되는 상황이 된다.

JDBC 는 각 DB 에 종속되지 않도록 일관된 언어로 개발을 진행할 수 있다.

JDBC 예시

아래의 코드를 보면 String 으로 SQL 문을 작성한 후 Connection, PrepareStatement, ResultSet 객체를 선언해주고 try catch 문을 사용해 DB 에 Member 를 삽입하고 검색하는 것을 확인할 수 있다.

    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs); }
    }

    @Override
    public Optional<Member> findById(Long id) {
        String sql = "select * from member where id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

코드를 보면 알겠지만 SQL 문을 잘못짰더라도 컴파일 시점에 이를 알 방법이 없다. 특히 데이터 접근 관련 코드가 중복되는 경우가 많고 핵심 관심사 외에 부가적인 코드가 더 길어진다는 한계가 있다.

SQL Mapper

SQL Mapper 란?

SQL MapperSQL 문과 객체를 매핑하는 기술이다. MyBatis 가 대표적인 예이다.

MyBatis 예시

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN">
<mapper namespace = "org.mybatis.example.UserMapper">
  <select id = "selectUser" resultType = "User">
    select * from user where id = #{id}
  </select>
</mapper>

위와 같은 식으로 xml 에서 SQL 을 작성한 후

public interface UserMapper {
	User selectUser(Long id);
}

정의해놓은 메소드로 SQL 문에서 받아온 데이터를 객체화할 수 있다.

그나마 JDBC 보다는 편해졌지만 여전히 SQL 문을 직접 작성해야된다는 단점이 존재한다. 그리고 xml 코드가 보기 싫다...

2-3) JPA

JPA 란 Java Persistence API 의 약자로, Java 의 대표적인 ORM 기술이다. JPA 는 인터페이스 모음으로, 사용 시 JPA Repository 를 extends 받아서 사용해야한다.

JPA 를 사용하기 전 콘솔 창에 SQL 문을 볼 수 있도록 설정부터 해보자.

Spring 설정 해보기

이미 알고 있겠지만, Spring 에서 설정을 조금 바꿔준다면 JPA 가 작동할 때 Query 가 작동하는 모습을 볼 수 있다.

application.properties 파일에 아래의 문장을 추가해주자.

spring.jpa.show-sql=true

그 후에 RDBMS 에 CRUD 가 일어난다면 아래의 사진과 같이 select, from, insert 등과 같이 어떤 Query 문이 어떻게 작성되었는지 확인할 수 있게 된다.

JPA 맛보기

Entity 만들어보기

Entity 객체RDB 테이블의 하나의 튜플이 될 것이다. Entity 객체 하나가 생성되어 Repository 에 저장되면 그것이 RDB 테이블에 하나의 행이 된다. SQL 문을 작성할 때의 예시를 가져와 Post 와 Comment 객체를 만들어봤다.

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Post {

	@Id
    @Column(name = "post_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String title;
    
    @Column(nullable = false)
    private String postContent;

}
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Reply {

	@Id
    @Column(name = "comment_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch = fetchType.Lazy)
    @JoinColumn(name = "post_id")
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Post post;
    
    @Column(nullable = false)
    private String replyContent;

}

실제 RDB 에 입력될 데이터를 Entity Class 에 정의해줬다. Entity 의 각 필드가 RDB 의 열이 된다. @Id 로 PK 를 정의해주었고, @Column 을 달아서 열임을 인식시킴과 동시에 열의 이름 또한 커스텀 하였다. 특히 @GeneratedValue 를 사용해 id 가 어떻게 생성될 것인지 옵션을 지정해주었다.

Comment Entity 에는 ManyToOne 으로 연관관계를 매핑해주었으며, Post 객체가 열로 삽입되는 것을 확인할 수 있다. 또한 @Column 에 nullable 옵션을 주어 null 값이 입력되지 않도록 하였다. 여기서는 @ManyToOne을 사용해 관계를 맺어주었으므로 @OneToMany 와 다르게 cascade 옵션을 주지 않고 @OnDelete 어노테이션을 사용했다. 관련 내용은 아래의 포스팅을 참고하자.

또 Cascade 와 Column, GeneratedValue 에는 다양한 옵션들이 있는데 이거는 따로 찾아보도록 하자.

Cascade vs @Delete : https://gilssang97.tistory.com/71

Repository 만들어보기

public interface PostRepository extends JpaRepository<Post, Long> {

}
public interface ReplyRepository extends JpaRepository<Reply, Long> {

	List<Reply> findAllByPostOrderByIdDesc(Post post)
    Long countByPost(Post post)
    Boolean existByPost(Post post)
}

너무 간단해서 할 말이 없다. PostRepository 에서는 JpaRepository 가 주는 기본 Method 를 사용하기 위해 아무것도 입력하지 않았지만, ReplyRepository 에 구현해준 것처럼 각종 메소드 들을 구현해 입맛에 맞게 사용할 수 있다.

위에 구현한 메소드 들은 예시를 위해 간단한 메소드를 사용하였지만 아래의 포스팅에 있는 것처럼 복잡한 메소드 들도 사용이 가능하다.

그래도 위의 예시를 조금 더 설명해보자면, ReplyRepository 에는 FK 로 검색하기 위해 Post 객체를 넣어준 것을 확인할 수 있다. Post 객체를 넣으면 그에 해당하는 모든 Reply 객체를 가져올 수도, 몇 개인지 셀 수도, 있는지 없는지 true / false 로 확인할 수도 있도록 하였다.

상세 예시를 들면, 2번 게시글의 모든 댓글을 알고 싶다면 PostRepository 에서 2번 게시글에 해당하는 Post 객체를 먼저 찾고 해당 객체를 ReplyRepository 의 메소드에 파라미터로 넘기면 List, Long, Boolean 의 타입에 맞게 응답될 것이다.

[Spring JPA] Query Method : https://velog.io/@seongwon97/Spring-Boot-Query-Method

Service 에 구현하기


@Service
@RequiredArgsConstructor
public class PostService {

	private final PostRepository postRepository;
    private final ReplyRepository replyRepository;
    
    @Transactional
    public ReponseEntity<?> createPost(PostRequestDto postRequestDto) {
    
    	Post post = Post.builder()
        	.title(postRequestDto.getTitle())
            .postContent(postRequestDto.getPostContent())
            .build();
            
        postRepository.save(post); //게시글 저장
        
        return ResponseEntity.ok("게시글 작성 완료!");
    }

	public ResponseEntity<?> getPostList() {
    
    	List<Post> postList = postRepository.findAll(); //리스트 조회
        List<PostResponseDto> postResponseDtoList = new ArrayList<>();
        
        for (Post post : postList){
        	postResponseDtoList.add(
            	PostResponseDto.builder()
                	.postId(post.getId())
                	.title(post.getTitle())
                    .replyOrNot(replyRepository.existByPost(post)) //boolean Response
                    .countReply(replyRepository.countByPost(post)) //Long Response
                    .build();
        }
        
        return ResponseEntity.ok(postResponseDtoList);
    }
    
   	public ResponseEntity<?> getPost(Long postId) {
    
    	Post post = postRepository.findById(postId).orElseThrow(
        	() -> new NullPointerException("게시글이 존재하지 않습니다.")
        ); //PK 로 단건 조회
        
        PostResponseDto postResponseDto = PostResponseDto.builder()
        	.postId(post.getId())
        	.title(post.getTitle())
            .postContent(post.getPostContent())
            .replyResponseDtoList(replyRepository.findAllByPostOrderByIdDesc(post).stream()
            	.map(r -> ReplyResponseDto.builder()
                		.replyContent(r.getReplyContent())
                        .build())
                .collect(Collectors.toList())) //댓글 List Stream 하여 Response
            .build();
        
        return ResponseEntity.ok(postReponseDto);
    }
    
    @Transactional
    public ReponseEntity<?> deletePost(Long postId) {
    
    	postRepository.deleteById(postId); //PK 로 삭제
        
        return ResponseEntity.ok("게시글 삭제 완료!");
    }
}

실제 프로젝트라면 RequestDto 의 NPE 를 방지하기 위해 더 메소드를 길게 짜겠지만 예시이기 때문에 간단히 짜보았다. Service에 @RequiredArgsConstuctor 를 사용해 PostRepository 와 ReplyRepository 를 주입시키고 Post 에는 기본 메소드를 사용해 PK 값으로 조회, 삭제를 진행하였다. 또한 기본 메소드의 save 에 객체를 넣어 튜플을 생성해주도록 했다. findAll 은 모든 post 를 불러오는 것인데, 이를 List 로 저장하여 Response 해주었다.

잘 보면 Post를 리스트 조회한 후 ReplyRepository 의 existBy 메소드와 countBy 메소드를 사용해 해당 게시글에 댓글이 존재하는지와 댓글이 몇개 존재하는지를 함께 Response 해주었다. 그리고 게시글 단건 조회 시 builder 패턴 내에 findAll 메소드를 사용해 이를 Stream 으로 List 화 하여 Response 해주었다.

아주 간단한 JPA 의 예시 코드를 작성해보았는데 JDBC 와 MyBatis 에 비해 훨씬 객체 지향적이고 코드가 간결해진 것을 볼 수 있을 것이다. 또한 간단한 메소드로 구현하여 코딩 및 컴파일 시에 에러가 바로바로 보여 사전에 에러를 차단할 가능성이 매우 높아졌다. (현직자에 따르면 이게 정말 최대의 장점이라고 한다. 서비스 오픈했는데 쿼리문에서 오타로 제대로 작동하지 않는 것을 생각하면 식은땀이 절로 날 것이다...)

그렇다고 JPA 가 만능은 아니다. 프로젝트의 규모가 커지고 복잡해지면 속도가 저하될 수 있고 복잡한 쿼리문은 사용할 수 없다. 물론 이는 @Query 를 사용해 네이티브 쿼리를 사용하거나 QueryDSL 을 사용하면 이 문제를 해결할 수 있다. 이에 대해서는 언젠가 또 포스팅을 남기도록 하겠다.

3) MVC

3-1) MVC 란?

MVC 란 Model-View-Controller 의 약자이며, 어플리케이션을 구성하는 요소를 역할에 따라 세 가지 모듈로 나누어 구분한 패턴이다. 이를 사용하는 이유는 모듈의 역할을 나누어 각각 맡은 바에 집중하여 유지보수, 확장성, 유연성을 증가시키기 위함이다.

Model

어플리케이션의 데이터이며, 모든 데이터 정보를 가공하여 가지고 있는 컴포넌트이다. 사용자가 이용하려는 모든 데이터를 가지고 있어야 하며, View 또는 Controller에 대해 어떠한 정보도 알 수 없어야 한다. 위의 JPA 예시로 본다면, Entity 와 DB, 그리고 Service 가 이에 해당한다고 볼 수 있다.

View

시각적인 요소를 지칭하는 용어이다. Model 이 갖고 있는 데이터를 저장하면 안되고, Model 이나 Controller 에 대한 정보를 알면 안되단순히 표시를 해주는 역할만 가지고 있다. 위의 예시에 프론트엔드 개발이 이에 해당한다고 볼 수 있다.

Controller

Model 과 View 를 연결해주는 역할을 한다. 그러기 위해 Model 또는 View 의 정보를 알아야 한다. 위의 예시에서는 Controller 를 짜보지 않았지만 해당하는 Contoller 를 만들어보도록 하자.

@RestController //Json 형식으로 Response
@RequiredArgsConstructor //생성자 주입을 위한 Annotation
@RequestMapping("/api/post") //PostController 공통 URI
public class PostController {

	private final PostService postService;
    
    @PostMapping //게시글 작성
    public ResponseEntity<?> createPost(@RequestBody PostRequestDto postRequestDto) {
    	return postService.createPost(postRequestDto);
    }
    
    @GetMapping //게시글 리스트 조회
    public ResponseEntity<?> getPostList() {
    	return postService.getPostList(postRequestDto);
    }
    
    @GetMapping("/{postId}") //게시글 단건 조회
    public ResponseEntity<?> getPost(@PathVariable Long postId) {
    	return postService.getPost(postId);
    }
    
    @DeleteMapping("/{postId}") //게시글 삭제
    public ResponseEntity<?> deletePost(@PathVariable Long postId) {
    	return postService.deletePost(postId);
    }
}

현재 서비스에 게시글 작성, 리스트 조회, 단건 조회, 삭제 기능이 있다고 가정했을 때의 Controller 를 작성해보았다. View 에 해당하는 프론트엔드에 Response 해주기 위해 각 기능별 URI 와 HttpMethod 에 맞도록 Annotation 을 달아주었다. Create 에는 PostMapping, Read 에는 GetMapping, Delete 에는 DeleteMapping 을 사용한 것을 볼 수 있다. 그렇게 View 에서 원하는 URI 에 접속하면 postService 의 메소드에서 각 정보를 CRUD 할 수 있도록 Response 하고 있다.

위에서 설명했듯이 View 와 Model 을 연결하는 역할을 하고 있는 것이다. 로컬 환경에서 개발 중이라면 http://localhost:8080/api/post 로 Post 요청을 보내면 게시글 작성(물론 Dto 양식에 맞게 보내야겠지만) 을 할 수 있고, Get 요청을 보내면 게시글 리스트 조회를 할 수 있을 것이다. 게시글 단건 조회나 게시글 삭제에는 게시글을 특정할 수 있도록 해야하는데, 이를 URI 로 받기 위해 @PathVariable 을 사용하였다. 예를 들어 2번 게시글을 읽거나 삭제하고 싶다면 http://localhost:8080/api/post/2 로 Get 이나 Delete 요청을 보내면 된다.

@PathVariable 외에 Controller 에서 파라미터를 받는 방법은 아래의 포스팅을 참고하도록 하자.

스프링 controller에서 파라미터를 받는 다양한 방법 : https://velog.io/@shson/%EC%8A%A4%ED%94%84%EB%A7%81-controller%EC%97%90%EC%84%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-%EB%B0%9B%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95-RequestParam-RequestBody-PathVariable

참고 :
SQL : https://ko.wikipedia.org/wiki/SQL
[DB] ORM이란 : https://gmlwjd9405.github.io/2019/02/01/orm.html
[데이터베이스] ORM이란? : https://hanamon.kr/orm%EC%9D%B4%EB%9E%80-nodejs-lib-sequelize-%EC%86%8C%EA%B0%9C/
[DB] ORM (Object Relational Mapping) 사용 이유, 장단점 : https://eun-jeong.tistory.com/31
[Spring Boot] ORM과 JDBC, JPA : https://programming-workspace.tistory.com/58
[10분 테코톡] 범고래, 소주캉의 JDBC, SQL Mapper, ORM : https://www.youtube.com/watch?v=NFK9qLWpujY&list=WL&index=1
SQL의 개념과 SQL로 데이터베이스를 정의하는 법 : https://www.youtube.com/watch?v=c8WNbcxkRhY&list=WL&index=30
백엔드에서 사용되는 데이터베이스(database) 기본 개념 : https://www.youtube.com/watch?v=aL0XXc1yGPs&list=WL&index=17
[Java] JDBC를 사용한 데이터베이스 연동 : https://devlog-wjdrbs96.tistory.com/139
MyBatis와 JPA의 차이, MyBatis보다 JPA를 사용해야 하는 이유 : https://mangkyu.tistory.com/20
[Spring JPA] JPA 란? : https://dbjh.tistory.com/77
JPA VS JDBC : https://sowon-dev.github.io/2021/03/22/210323jpaVSjdbc/
Spring MVC 동작 구조 : https://ss-o.tistory.com/160
Controller, Service, Repository 가 무엇일까? : https://velog.io/@jybin96/Controller-Service-Repository-%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
스프링 controller에서 파라미터를 받는 다양한 방법 : https://velog.io/@shson/%EC%8A%A4%ED%94%84%EB%A7%81-controller%EC%97%90%EC%84%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%EB%A5%BC-%EB%B0%9B%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95-RequestParam-RequestBody-PathVariable
[Spring JPA] Query Method : https://velog.io/@seongwon97/Spring-Boot-Query-Method
RDB vs NoSQL : https://www.youtube.com/watch?v=4PxBaojUnU4&list=WL&index=12
https://www.youtube.com/watch?v=Q_9cFgzZr8Q
SQL로 DB에 데이터를 추가(insert)하고 수정(update)하고 삭제(delete)하는 방법 : https://www.youtube.com/watch?v=mgnd5JWeCK4&list=WL&index=22
SQL로 데이터 조회하기 : https://www.youtube.com/watch?v=dTBwgWMUguE&list=WL&index=6
SQL Tutorial : https://www.w3schools.com/sql/default.asp
MYSQL 데이터 타입 : https://blog.martinwork.co.kr/mysql/2020/01/17/mysql-data-type.html
MySQL 데이터 타입 Java 데이터 타입 비교 : https://goldfishhead.tistory.com/2
SQL의 개념과 SQL로 데이터베이스를 정의하는 법 : https://www.youtube.com/watch?v=c8WNbcxkRhY&list=WL&index=31
연관관계 매핑 기초 : https://catsbi.oopy.io/ed9236a0-6521-471d-8a0d-b852147b5980
JPA 연관 관계 한방에 정리 : https://jeong-pro.tistory.com/231
[DB] 📈 데이터 모델링 개념 & ERD 다이어그램 그리기 💯 총정리 : https://inpa.tistory.com/entry/DB-%F0%9F%93%9A-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%AA%A8%EB%8D%B8%EB%A7%81-1N-%EA%B4%80%EA%B3%84-%F0%9F%93%88-ERD-%EB%8B%A4%EC%9D%B4%EC%96%B4%EA%B7%B8%EB%9E%A8
백엔드에서 사용되는 데이터베이스(database) 기본 개념 : https://www.youtube.com/watch?v=aL0XXc1yGPs&list=WL&index=18

profile
항상 진심이지만 뭔가 안풀리는 개발 (주의! - 코린이가 배우고 이해한 내용을 끄적이는 공간이므로 실제 개념과 일부 다를 수 있음!)

0개의 댓글