Uknow's Lab.
article thumbnail

 

 

지난 동계 방학기간 동안 인턴을 나가게 되었는데,

인턴쉽 기간 동안 파이썬 장고를 사용해 홈페이지 백엔드를 개발하였습니다.

파이썬은 원래 익숙했기에 큰 거부감이 없었고,

장고도 생각보다 간단하여 꽤 쉽게 배울 수 있었습니다. (물론 깊게 들어가면 한 없이 어렵겠지만)

 

그런데 웬걸... 학과 팀 프로젝트 / 과제용으로 php로 아주아주 간단하게 끄적이다가,

백엔드를 제대로 해보니 너무나도 신세계였습니다.

백엔드가 생각보다 재밌던 저는 인턴이 끝나고 제 주 언어인 자바/코틀린의 백엔드 프레임워크인 스프링을 파보기 시작했는데요.

사실 제대로 공부하려고 하면 계속 새 일이 생겨갖고 'ㅅ'; 자는 시간을 줄이며 틈틈히 인프런 김영한님의 강의를 듣고 있었습니다...

 

이제 MVC 1편 강의를 다 봤는데, 스프링으로 이제 자그만한 프로젝트는 정도는 만들 수 있지 않을까? 하는 생각에

야생형 개발자로 그냥 무작정 게시판을 한 번 만들어 봤습니다 '-'

게시글 테이블 C(Create)R(Read)U(Update)D(Delete)로 시작해 조금씩 키워봐야겠네요.

 

 

 

프로젝트 생성 - Spring Initializr

 

https://start.spring.io/

 

 

저는 프로젝트를 Spring Initializr를 통해 생성했는데요.

Project - Gradle - Groovy, Language - Java, Version - 2.7.11을 선택했습니다.

메타데이터는 자유롭게 선택하면 됩니다.

 

Dependencies는 Lombok, Spirng Web, JPA 등 생각나는대로 일단 넣긴 했는데,

어차피 추후 추가가 가능하니 일단 3개 정도만 넣었습니다.

 

 

Entity

import lombok.Getter;
import lombok.NoArgsConstructor;

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

@Entity
@Getter
@NoArgsConstructor
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;

    public Post(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 

저는 게시판의 기본키가 될 id, 제목, 내용만 간단하게 넣어봤습니다.

Lombok을 통해 Getter, NoArgsConstructor 등을 아주 간편하게 넣을 수 있습니다.

사실 코틀린을 하다가 자바를 쓰려니 조금 불편했던 부분이였는데, Lombok 덕분에 너무 편하네요.

 

@Entity  어노테이션은 테이블과 매칭되는 클래스임을 나타냅니다.

실제 DB 테이블과 매칭되는 클래스인데요.

 

처음에 무심코 @Setter도 넣었다가, 넣는 순간 뭔가 쌔 하더라고요...

어... 근데 이거 넣지 말라했던 거 같은데...? 아 근데 왜 안됬더라...????

 

아 맞다!!! 클래스의 필드의 변경이 무분별하게 일어나면 흐름을 파악하기 어렵기 때문이지!

 

 

 

Entity - DTO

DTO란 Data Transfer Object의 약자로, 데이터를 주고받기 위한 객체입니다.

비지니스 로직이나 도메인 속성은 고려되지 않으며

Register, Response, Update에 사용되는 데이터는 일반적으로  Entity가 그대로 사용되진 않습니다.

변경과 확장에 유리하게 하기 위함도 있고,

컨트롤러와 서비스간에 과한 의존성을 피하기 위함도 있습니다.

 

PostResponseDto

 

import lombok.*;
import uknow.board.practice.entity.Post;

@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostResponseDto {
    private String title;
    private String content;

    public PostResponseDto(Post post) {
        this.title = post.getTitle();
        this.content = post.getContent();
    }
}

 

 

 

PostRegisterDto

import lombok.*;
import uknow.board.practice.entity.Post;

@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostRegisterDto {
    private String title;
    private String content;

    public Post toEntity() {
        return new Post(title, content);
    }
}

 

 

 

 

JPA Repository

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

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
    // 추가적인 메소드 불필요
}

 

데이터베이스 접근은 JpaRepository를 사용해 아주 간편하게 사용할 수 있었습니다.

JPA는 객체 관계 매핑(ORM, Object-Relational Mapping) 을 위한 표준 인터페이스 인데요.

이는 쉽게 말하면 SQL을 자동으로 생성해주어, 객체로 데이터에 접근한다고 생각하면 편합니다.

 

php에 SQL을 삽입해 테이블에서 직접 데이터를 CRUD 하곤 했던지라,

장고를 처음 할 때 어? SQL이 필요가 없네? 하며 신기했던 기억이 있었습니다.

JPA도 제대로 하려면 스프링 만큼이나 깊게 파봐야 한다고 했는데,

나중에 더 깊게 공부해봐야겠네요.

 

 

 

PostService

import org.springframework.stereotype.Service;
import uknow.board.practice.controller.dto.PostRegisterDto;
import uknow.board.practice.controller.dto.PostResponseDto;
import uknow.board.practice.repository.PostRepository;
import uknow.board.practice.entity.Post;

import javax.transaction.Transactional;
import java.util.List;

@Service
public class PostService {

    private final PostRepository postRepository;

    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @Transactional
    public List<Post> getAllPost() {
        return postRepository.findAll();
    }

    @Transactional
    public PostResponseDto getPostById(Long id) {
        Post post = postRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("잘못된 Post ID 입니다."));
        return new PostResponseDto(post);
    }

    @Transactional
    public Post createPost(PostRegisterDto post) {
        return postRepository.save(post.toEntity());
    }

    @Transactional
    public Post updatePost(Long id, PostRegisterDto updatedPost) {
        Post post = postRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("잘못된 Post ID 입니다."));
        post.update(updatedPost.getTitle(), post.getContent());

        return postRepository.save(post);
    }

    @Transactional
    public void deletePost(Long id) {
        postRepository.deleteById(id);
    }
}

 

PostService 입니다.

Service는 Controller와 데이터 엑세스의 중간역할로, 비지니스 로직을 처리하는 부분입니다.

 

 

 

PostController

import org.springframework.web.bind.annotation.*;
import uknow.board.practice.controller.dto.PostRegisterDto;
import uknow.board.practice.controller.dto.PostResponseDto;
import uknow.board.practice.service.PostService;
import uknow.board.practice.entity.Post;

import java.util.List;

@RestController
@RequestMapping("/post")
public class PostController {

    private final PostService postService;

    public PostController(PostService postService) {
        this.postService = postService;
    }

    @GetMapping
    public List<Post> getAllPost() {
        return postService.getAllPost();
    }

    @GetMapping("/{id}")
    public PostResponseDto getPostById(@PathVariable Long id) {
        return postService.getPostById(id);
    }

    @PostMapping
    public Post createPost(@RequestBody PostRegisterDto post) {
        return postService.createPost(post);
    }

    @PutMapping("/{id}")
    public Post updatePost(@PathVariable Long id, @RequestBody PostRegisterDto updatedPost) {
        return postService.updatePost(id, updatedPost);
    }

    @DeleteMapping("/{id}")
    public void deletePost(@PathVariable Long id) {
        postService.deletePost(id);
    }
}

 

controller는 MVC(Model-View-Controller) 중 컨트롤러 파트로써,

클라이언트의 요청에 응답하고, 반환하는 역할을 합니다.

 

현재 구조에서는 컨트롤러(클라이언트 요청 응답/반환)이 서비스를 호출하여 비즈니스 로직을 처리합니다.

이는 관심사를 분리함으로써, 코드의 재사용성과 유지보수를 원활하게 하기 위함입니다.

 

저는 RestAPI를 목적으로 만들었기 때문에,

@RestController를 붙여줬고,

@RequestMapping("/post")를 붙임으로써, url이 post로 시작하는 요청을 처리합니다.

@PostMapping, @GetMapping, @DeleteMapping, @PutMapping으로

각각 POST, GET, DELETE, PUT 요청을 처리합니다.

이들은 CRUD에서 Post - Create, Get - Read, PUT - Update, Delete - DELETE에 대응됩니다.

 

 

 

MySQL에 연동하기

사실 위 부분 까지는 강의를 들으며 각각의 개념이 정확이 무엇인지 완벽하게 이해하진 못해도,

어느정도 활용을 할 줄은 알기에 큰 어려움 없이 만들 수 있었으나,

MySQL DB에 연결해보는 것은 해보지 않아 조금 애먹었습니다

 

MySQL에 연동하려면 일단 MySQL이 설치되어 있어야 하는데, 설치 과정은 패스하겠습니다.

 

build.gradle의 dependencies에 mysql 의존성을 추가해야 합니다. Gralde 동기화는 필수입니다!

implementation 'mysql:mysql-connector-java:8.0.28'

저는 한참동안이나 뭐가 문제인지... 생각했는데 알고보니 뒤의 버전을 빼먹었더라고요.

 

application.properties에 아래 정보를 입력합니다.

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/(데이터베이스 이름)?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=(your-password)
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.defer-datasource-initialization=true

spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

 

url부분에 데이터베이스 이름과, password 부분을 본인의 mysql 비밀번호를 입력하면 됩니다.

 

저는 여기서 오류가 발생했는데, MySQL에 Post 테이블이 없어 객체와 DB간의 관계를 매핑하지 못했는데요.

MySQL에 테이블을 생성해 해결하였습니다.

JPA를 사용해 엔티티를 기반으로 테이블을 만드는 방법도 있다는데, 더 살펴 봐야겠습니다.

 

 

 

PostMan으로 테스트해보기

 

 

다행이도... 잘 들어갑니다..!

 

 

 

GET 요청도 잘 온 모습입니다.

 

 

 

MySQL Workbench에서도 잘 확인이 되네요.

 

 

 

후기

스프링을 공부한지 한 달 정도 됬지만,

사실 그 한 달도 시간이 엄청 부족했던 터라ㅠㅠ... 밤잠을 조금씩 줄여 배운 탓에 진도가 잘 안나갔는데,

최근에야 좀 여유가 생겨 스프링에 집중할 수 있었습니다.

그래도 작게나마 한 번 만들어보니 굉장히 재밌네요.

자바는 원래부터 제 주 언어였고,

백엔드 프레임워크도 파이썬 장고를 먼저 해봤기에 그래도 좀 이해하기 수월했던 것 같네요.

(백엔드 프레임워크가 그렇게 낮설진 않았다는 것이지, 둘의 구조 자체는 매우 다릅니다!)

 

여기에 태그기능, 로그인 & 회원가입 기능, 검색 기능 등등 이것저것 추가해보고 싶네요.

profile

Uknow's Lab.

@유노 Uknow

인생은 Byte와 Double 사이 Char다. 아무말이나 해봤습니다.