영벨롭 개발 일지

[Spring] 스프링 + JPA + MySQL 연동 본문

Back-end/Spring

[Spring] 스프링 + JPA + MySQL 연동

영벨롭 2023. 8. 13. 20:25

★ 스프링 프로젝트 생성

Dependencies

  • Spring Web
  • MySQL Driver
  • Spring Data JPA


★ application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 드라이버 설정
    url: jdbc:mysql://127.0.0.1:3306/jpa_schema # DB source URL
    username: <username> # DB username
    password: <password> # DB password
  jpa:
    hibernate:
      ddl-auto: update # DDL 작성: 테이블 생성 및 수정 옵션
    show-sql: false # JPA 쿼리문 확인 여부
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect

 application.yml 파일에 DB 정보를 등록합니다. 

 

 ddl-auto 옵션

 ddl-auto 옵션은 엔티티만 등록해 놓으면 DDL 을 자동으로 작성하여 테이블을 생성하거나 수정해주는 설정입니다. 

 ddl-auto 에는 다음 5가지 속성이 있습니다. 

  • create: 기존 테이블을 삭제 후, 다시 생성합니다.
  • create-drop: create 와 유사하지만, 종료 시점에 테이블을 삭제합니다.
  • update: 엔티티로 등록된 클래스와 매핑되는 테이블이 없으면 새로 생성하며, DB 테이블과 엔티티 매핑 정보를 비교하여 변경 사항만 수정합니다.
  • validate: DB 테이블과 엔티티 매핑 정보를 비교합니다. 이때 차이가 있으면 어플리케이션을 실행하지 않습니다.
  • none: default 속성으로, 사실 속성이 존재하는 것이 아닌 위 4가지 경우를 제외한 모든 경우에 해당합니다. ddl-auto 옵션을 아예 작성하지 않았을 때와 동일합니다.

 주의해야할 점은, 실제 운영되는 서버에는 create, create-drop, update 는 사용하면 안되고 로컬 환경에서만 사용해야합니다. 

 


★ 프로젝트 폴더 구성


★ JPA Hibernate Entity 생성

 @Entity 어노테이션을 사용하며, @Table 어노테이션을 이용하여 테이블명을 지정합니다. 

package com.dev.jpapractice.entity;

import javax.persistence.*;

@Entity
@Table(name = "post")
public class PostEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // MySQL 의 AUTO_INCREMENT 방식
    private Long id;
    @Column(length = 50, nullable = false)
    private String title;
    @Column(nullable = false)
    private String content;
    @Column(length = 10, nullable = false)
    private String writer;

    public PostEntity() {
    }

    public PostEntity(Long id, String title, String content, String writer) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.writer = writer;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getWriter() {
        return writer;
    }

    public void setWriter(String writer) {
        this.writer = writer;
    }

    @Override
    public String toString() {
        return "PostEntity{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", writer='" + writer + '\'' +
                '}';
    }
}

 위 엔티티 작성 후 어플리케이션을 실행시키면 다음과 같이 DB 에 동일한 구조의 테이블이 생성된 것을 확인할 수 있습니다. 


★ DTO 생성

 생성한 Entity 와 동일한 구조의 DTO 를 생성합니다. 

package com.dev.jpapractice.dto;

import com.sun.istack.NotNull;

public class PostDto {
    private Long id;
    private String title;
    private String content;
    private String writer;

    public PostDto() {
    }

    public PostDto(Long id, String title, String content, String writer) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.writer = writer;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getWriter() {
        return writer;
    }

    public void setWriter(String writer) {
        this.writer = writer;
    }

    @Override
    public String toString() {
        return "PostDto{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", writer='" + writer + '\'' +
                '}';
    }
}

 


★ JPA Repository 생성

 Spring Data JPA 는 Hibernate 를 이용하기 위한 여러 API 를 제공합니다. 

 그 중 가장 많이 사용하는 것이 JpaRepository 인터페이스이며, JpaRepository 를 상속하는 것만으로도 작성된 테이블에 SQL 문 없이 CRUD 작업을 수행할 수 있습니다. 

 데이터베이스에서 사용하고자 하는 <엔티티, 기본키 타입> 을 JpaRepository 의 제네릭 타입으로 지정하면, 해당 Repository 를 Bean 으로 주입받아 사용할 수 있습니다.

package com.dev.jpapractice.repository;

import com.dev.jpapractice.entity.PostEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<PostEntity, Long> {
}

★ DAO 생성

 @Repostiory 어노테이션을 사용하여 Bean 으로 등록 후, 앞서 JpaRepository 를 상속받아 생성한 PostRepository 로 CRUD 로직을 작성합니다.

 PostRepository 에 정의된 함수들을 PostDao 에서 사용하여, PostDto 의 데이터를 정리해 PostEntity 로 전환합니다.

package com.dev.jpapractice.dao;

import com.dev.jpapractice.dto.PostDto;
import com.dev.jpapractice.entity.PostEntity;
import com.dev.jpapractice.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Repository;
import org.springframework.web.server.ResponseStatusException;

import java.util.Iterator;
import java.util.Optional;

@Repository
public class PostDao {
    private final PostRepository postRepository;

    public PostDao(
            @Autowired PostRepository postRepository
    ) {
        this.postRepository = postRepository;
    }

    public void createPost(PostDto dto) {
        // PostDto -> PostEntity 로 전환하여 저장
        PostEntity postEntity = new PostEntity();

        postEntity.setTitle(dto.getTitle());
        postEntity.setContent(dto.getContent());
        postEntity.setWriter(dto.getWriter());

        this.postRepository.save(postEntity);
    }
    
    public PostEntity readPost(Long id) {
        // 기본키가 {id} 에 해당하는 엔티티 find
        Optional<PostEntity> postEntity = this.postRepository.findById(id);
        if(postEntity.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        return postEntity.get();
    }
    
    public Iterator<PostEntity> readPostAll() {
        return this.postRepository.findAll().iterator();
    }
    
    public void updatePost(Long id, PostDto dto) {
        Optional<PostEntity> targetEntity = this.postRepository.findById(id);
        
        if(targetEntity.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        
        PostEntity postEntity = targetEntity.get();
        postEntity.setTitle(dto.getTitle() == null ? postEntity.getTitle() : dto.getTitle());
        postEntity.setContent(dto.getContent() == null ? postEntity.getContent() : dto.getContent());
        
        this.postRepository.save(postEntity);
    }
    
    public void deletePost(Long id) {
        Optional<PostEntity> targetEntity = this.postRepository.findById(id);

        if(targetEntity.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND);
        }
        
        this.postRepository.delete(targetEntity.get());
    }
    
}
  • save(T entity): 인자로 받은 T 타입의 Entity 를 생성 및 수정
  • findById(ID id): 인자로 받은 ID 를 가진 T 를 반환
  • findAll(): 모든 T 를 반환
  • delete(T entity): 전달받은 T 를 삭제
Optional<PostEntity>
: Optional<T> 는 NPE(NullPointerException) 을 발생하지 않도록 도와주는 Wrapper 클래스
: T 타입의 value 를 저장하여, get() 메소드를 사용하여 value 를 전달받을 수 있다. 
: isEmpty() 메소드를 사용하여 value 가 null 인지 판별할 수 있다. 

Iterator<PostEntity>
: Iterator 는 List, Set, Map, Queue 등과 같은 Collection Framework 에서 값을 가져오거나 삭제할 때 사용한다.
: Iterator<데이터타입> 변수명 = 컬렉션.iterator();

★ Service 생성

 @Service 어노테이션을 사용하여 Bean 으로 등록 후, PostDao 에서 만든 함수를 PostService 와 연결하여 사용합니다. 

 PostDao 로부터 받은 Entity 를 DTO 로 전환하여 컨트롤러에게 전달하거나, 컨트롤러에게 받은 DTO 를 PostDao 에게 전달합니다. 

package com.dev.jpapractice.service;

import com.dev.jpapractice.dao.PostDao;
import com.dev.jpapractice.dto.PostDto;
import com.dev.jpapractice.entity.PostEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

@Service
public class PostService {
    private final PostDao postDao;

    public PostService (
            @Autowired PostDao postDao
    ) {
        this.postDao = postDao;
    }

    public void createPost(PostDto dto) {
        // DTO 를 DAO 에 전달
        this.postDao.createPost(dto);
    }
    
    public PostDto readPost(Long id) {
        // Entity 를 DTO 로 전환
        PostEntity postEntity = this.postDao.readPost(id);
        
        PostDto postDto = new PostDto(
                postEntity.getId(),
                postEntity.getTitle(),
                postEntity.getContent(),
                postEntity.getWriter()
        );
        return postDto;
    }
    
    public List<PostDto> readPostAll() {
        Iterator<PostEntity> postEntityIterator = this.postDao.readPostAll();
        List<PostDto> postDtoList = new ArrayList<>();
        
        while(postEntityIterator.hasNext()) {
            PostEntity postEntity = postEntityIterator.next();
            PostDto postDto = new PostDto(
                    postEntity.getId(),
                    postEntity.getTitle(),
                    postEntity.getContent(),
                    postEntity.getWriter()
            );
            postDtoList.add(postDto);
        }
        
        return postDtoList;
    }
    
    public void updatePost(Long id, PostDto dto) {
        this.postDao.updatePost(id, dto);
    }
    
    public void deletePost(Long id) {
        this.postDao.deletePost(id);
    }
}

★ Controller 생성

 @Controller 또는 @RestController 어노테이션을 사용하여 Bean 으로 등록 후, PostService 에서 생성한 함수를 PostController 와 연결하여 생성합니다. 

 자연스럽게 Controller - Service - Repository 의 형태로 어플리케이션이 구성됩니다. 

package com.dev.jpapractice.controller;

import com.dev.jpapractice.dto.PostDto;
import com.dev.jpapractice.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("post")
public class PostController {
    public final PostService postService;

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

    @PostMapping("")
    public void createPost(@RequestBody PostDto dto) {
        this.postService.createPost(dto);
    }

    @GetMapping("{id}")
    public PostDto readPost(@PathVariable("id") Long id) {
        return this.postService.readPost(id);
    }

    @GetMapping("")
    public List<PostDto> readPostAll() {
        return this.postService.readPostAll();
    }

    @PutMapping("{id}")
    public void updatePost(
            @PathVariable("id") Long id,
            @RequestBody PostDto dto
    ) {
        this.postService.updatePost(id, dto);
    }

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

★ 테스트

createPost

readPost

readPostAll

updatePost

deletePost

반응형