도서 관리 서비스 - 스프링부트 CRUD 구현하기
회사에 막 입사한 주니어 개발자, 개발을 막 시작해서 포트폴리오 작성하려는 취업준비생 등 간단한 프로젝트를 만들 때 언제나 마주치는 CRUD 구현을 위한 튜토리얼입니다.
저같은 경우는 비전공자로 개발을 시작하다 보니 Python 밖에 공부할 기회가 없었고, 자바를 시간들여 공부할 시간이 없었던 상황인데 막상 회사에 들어와보니 Java로 개발을 해야할 경우가 훨씬 많더라구요. 그래서 조금씩 공부는 하고 있었지만, 막상 갑자기 토이 프로젝트라도 해보려고 하면 막막했던 게 사실입니다.
다행히 이번에 부담없이 작은 프로젝트에 CRUD 서비스를 구현해볼 기회가 주어져서 공부하면서 만들어본 과정을 포스팅으로 담아봤습니다. 저같이 막막하게 항해하고 계신 분들께 조금이나마 도움 되길 바랍니다!
Project Structure
├── book-service.iml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java.com.demo.book
│ │ └── BookServiceApplication.java
│ │ ├── config
│ │ │ └── SwaggerConfig.java
│ │ ├── controller
│ │ │ └── BookController.java
│ │ ├── dao
│ │ │ └── BookDao.java
│ │ ├── model
│ │ │ └── Book.java
│ │ └── service
│ │ └── BookService.java
│ └── resources
│ └── application.properties
└── test
└── java.com.demo.book
└── BookServiceApplicationTests.java
Steps
- MySQL 설치 및 설정
- DB 연결하기
- Java Springboot Start
- Data Model 생성
- Repository 생성
- Service 생성
- Controller 생성 및 Exception Handling
- Swagger config
1. MySQL 설치 및 설정
MacOS 기준으로 homebrew
로 설치하고 보안 설정을 합니다. 커맨드 라인으로 작업하실 수도 있고, GUI Tool로 Workbench, DataGrip 등 여러 프로그램을 사용하실 수 있습니다.
create database library;
데이터베이스와 테이블을 생성합니다.
create table books
(
id int auto_increment,
book_title varchar(255) not null,
author varchar(255) not null,
publisher varchar(255) not null,
location varchar(45) not null,
owner varchar(255) not null,
updatedAt datetime null,
createdAt datetime null,
constraint books_pk
primary key (id)
);
MySQL 데이터베이스 사용자 생성 및 접근 권한 설정은 블로그 글을 참고했습니다.
2. DB 연결하기
resource 폴더 안에 application.properties 안에 db 속성값을 입력합니다.
(주의) mysql connector가 timezone을 인식 못하는 에러를 방지하기 위해 url에 UTC 서버 타임존을 입력해줘야 합니다.
아래 속성값에는 root 유저를 사용했지만, 저같은 경우는 새로 만든 데이터베이스에 대한 권한을 부여 받은 사용자를 따로 만들어서 사용했습니다.
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/library?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username = root
spring.datasource.password = YOUR_MYSQL_PASSWORD
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
## Show sql query.
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
3. Java Spring Boot Start
Maven 패키지 설정 및 메인 서비스 작성
스프링부트 스타터로 프로젝트를 시작했고, 필요한 maven dependency들을 확인합니다.
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
main 폴더에서 메인이 되는 서비스 클래스를 생성합니다.
BookServiceApplication
package com.demo.book;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BookServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(BookServiceApplication.class, args);
}
}
4. Data Model 생성
도서의 상세 정보에 대한 데이터를 저장, 수정, 삭제 할 모델 클래스를 생성합니다.
model 패캐지를 생성하고 Book.java 만들어 코드작성을 시작합니다.
package com.demo.book.model;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.validation.constraints.NotBlank;
import java.util.Date;
@Entity
@Table(name="books")
@Data
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
private String book_title;
@NotBlank
private String author;
@NotBlank
private String publisher;
@NotBlank
private String location;
@NotBlank
private String owner;
@CreationTimestamp
private Date createAt;
@UpdateTimestamp
private Date updateAt;
}
@Data
는 Lombok annotation으로 getter와 setter 필드 및 toString, equals, hashCode 함수를 컴파일할 때 자동으로 생성해줍니다.
@Entity
는 JPA를 사용해서 테이블과 매핑할 클래스에 붙이는 annotation 입니다. @Table
을 사용하지 않으면 자동으로 테이블 네임을 클래스 이름으로 지정합니다.
5. Dao 생성
다음으로 dao패키지를 만들어서 java에서 데이터를 관리합니다.
dao 패키지 안에 BookDaojava 라는 이름으로 Interface를 작성합니다.
BookDao.java
package com.demo.book.repository;
import com.demo.book.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
}
방금 만든 Book Model과 BookRepository 인터페이스를 연결하는데, JpaRepository 패키지를 불러와서 이미 구현되어 있는 CRUD 관련 함수를 사용할 수 있도록 합니다.
Eg:
.findAll() - to get All datas
.save() - to save the got Data
.delete() - to delete the data
<>
태그 안에는 가져올 모델의 이름과 프라이머리 키의 데이터 타입을 입력합니다.
@Repository : 지속성 계층에서 DAO(Data Access Object) 구성 요소를 나타내는 데 사용되는 주석. 인터페이스가 해당 리포지토리를 사용해 데이터베이스 활동을 수행할 것임을 컴파일러에 알립니다.
6. Service 생성
DAO 인터페이스 함수를 사용해서 sql 쿼리문을 활용할 수 있는 Service class를 생성합니다.
BookService.java
@Service
@Transactional
@RequiredArgsConstructor
public class BookService {
@Autowired
private final BookRepository dao;
public List<Book> findAll () {
return dao.findAll();
}
public Optional<Book> findById(Long id) {
return dao.findById(id);
}
public Book addBook(Book book) {
return dao.save(book);
}
public Book updateBook(Book book) {
return dao.save(book);
}
public void deleteById(Long id) {
dao.deleteById(id);
}
}
7. Controller 생성 및 Exception Handling
log 설정
@Slf4j로 로그 관리를 하기 위해서는 lombok 라이브러리 외에도 추가 라이브러리가 필요합니다.
pom.xml
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
BookController.java
@RestController
@RequestMapping("/api/v1/books")
@Slf4j
@RequiredArgsConstructor
public class BookController {
private final BookService BookService;
// 도서 전체 조회
@GetMapping
public ResponseEntity<List <Book>> findAll() {
return ResponseEntity.ok(BookService.findAll());
}
// 도서 등록
@PostMapping
public ResponseEntity create(@Valid @RequestBody Book book) {
return ResponseEntity.ok(BookService.addBook(book));
}
// 특정 도서 조회
@GetMapping("/{id}")
public ResponseEntity<Book> findById(@PathVariable(value="id") Long id) {
Optional<Book> book = BookService.findById(id);
if (!book.isPresent()){
log.error("Id" + id + " is not existed");
ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(book.get());
}
// 도서 수정
@PutMapping("/{id}")
public ResponseEntity<Book> update(@PathVariable(value="id") Long id, @Valid @RequestBody Book book) {
if (!BookService.findById(id).isPresent()){
log.error("Id" + id + " is not existed");
ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(BookService.updateBook(book));
}
// 도서 삭제
@DeleteMapping("/{id]")
public ResponseEntity delete(@PathVariable(value="id") Long id) {
if (!BookService.findById(id).isPresent()){
log.error("Id" + id + " is not existed");
ResponseEntity.badRequest().build();
}
BookService.deleteById(id);
return ResponseEntity.ok().build();
}
이제 기본으로 제공되는 함수가 아닌, 쿼리문을 직접 작성해서 지정된 컬럼 내용으로 도서를 조회하기 위해 findByEmail
함수를 만들어보겠습니다.
먼저 dao
패키지 BookDao 인터페이스에 쿼리문을 직접 사용해 조회한 데이터로 새로운 List 객체를 만드는 함수를 구현합니다.
BookDao.java
@Repository
public interface BookDao extends JpaRepository<Book, Long> {
@Query(value="SELECT * FROM books WHERE owner LIKE :email", nativeQuery=true)
public List<Book> findByEmail(String email);
}
BookService 클래스에도 해당 함수를 정의해 사용합니다.
BookService.java
public class BookService {
** 중간 생략 **
public List<Book> findByEmail (String email) {
return dao.findByEmail(email);
}
}
이제 Controller로 돌아와서 이메일이 일치하는 데이터를 모두 불러오는 api 를 만들어 보겠습니다.
여기서 주의할 점은 이미 id로 조회/수정/삭제하는 api를 맵핑해두었기 때문에 앞단이 겹치도록 설계하면 작동하지 않을 수 있습니다.
또한, 이메일에는 특수문자가 포함되어 있기 때문에 맵핑시에 이런 부분을 커버할 수 있도록 설정해두어야 합니다. 여기글을 참고했습니다.
BookController.java
// 유저 도서 조회
@GetMapping("/email/{email:.+}")
public ResponseEntity<List<Book>> findByEmail(@PathVariable String email) {
List<Book> books = BookService.findByEmail(email);
if (books.isEmpty()){
return null;
}
return ResponseEntity.ok(books);
}
8. Swagger config
지금까지 완성한 코드를 swagger를 사용하면 https://localhost:8080/swagger-ui.html
에서 api 가 잘 작동하는 것을 확인해 보실 수 있습니다.
config
패키지를 만들고, SwaggerConfig.java
를 작성합니다.
SwaggerConfig.java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.ant("/api/**"))
.build();
}
}
결과 화면
book-controller에 정의한 api 전체 목록
id book Get 조회
id book 조회 response
지금까지 도서관리애플리케이션에서 도서 데이터를 조회/수정/삭제 하는 서비스를 작성하는 법을 알아보았습니다. 앞으로 도서 예약 서비스, 유저 관리 서비스, 로그인 기능 구현을 마치면 오픈시프트에 올려 DevOps 툴체인 구성까지 하나씩 단계 밟아가며 진행할 예정입니다.
Reference
- FreeCodeCamp, (June 17, 2020), https://www.freecodecamp.org/news/how-to-build-a-rest-api-with-spring-boot-using-mysql-and-jpa-f931e348734b/
- Hellokoding, (June 17, 2020), https://hellokoding.com/full-stack-crud-web-app-and-restful-apis-web-services-example-with-spring-boot-jpa-hibernate-mysql-vuejs-and-docker/
- codejava, (June 17, 2020), https://www.codejava.net/frameworks/spring-boot/spring-boot-crud-example-with-spring-mvc-spring-data-jpa-thymeleaf-hibernate-mysql
- javacodegeeks, (June 17, 2020), https://examples.javacodegeeks.com/enterprise-java/spring/boot/spring-boot-crud-operations-example/
- Dreaming For The Future, (June 18, 2020), http://www.chidoo.me/index.php/2016/05/08/spring-data-jpa-for-short-memories/
'📒 Tech Note > 웹 프로그래밍' 카테고리의 다른 글
OAuth 2.0 이해하기 (0) | 2020.08.03 |
---|---|
[Python] 파이썬으로 Json 인코더와 디코더 사용하기(읽고 쓰기) (3) | 2020.08.01 |
[CS] C언어의 기초 (1) (0) | 2020.08.01 |
PyPI 패키지 업로드 해보기 (0) | 2020.07.28 |
Swagger로 Express 시작하기 (1) | 2020.07.14 |