[Spring] 스프링 + JPA + MySQL 연동
★ 스프링 프로젝트 생성
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