MySQL과 연동하고 실습을 통해 JPA를 알아봅니다.

학습 로드맵

  • 스프링부트에 MySQL을 연동합니다.
  • 스프링부트에서 JPA를 사용할 수 있도록 합니다.
  • CrudRepository, JpaRepository 인터페이스를 상속받아 CRUD 작업을 해봅니다.
  • 쿼리 메소드를 이용하여 메소드 이름만으로 원하는 SQL을 실행합니다.
  • @Query를 이용하여 JPQL 처리를 합니다.
  • 스프링 data의 Pageable, PageRequest를 간단히 살펴봅니다.

선행 조건

  • intelliJ spring initializer를 사용하여 프로젝트를 생성합니다.
    • 굳이 인텔리제이가 아니여도 됩니다.
    • 여기서도 프로젝트를 생성할 수 있습니다.
  • Lombok을 사용합니다.

참고

  • Github에서 springboot-mysql-board 프로젝트를 참고하시면 됩니다.

프로젝트 구조

스크린샷 2019-08-28 오후 2.44.12.png

JPA 및 MySQL 설정

1. 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

2. application.yml 작성

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://디비주소
    username: 아이디
    password: 비밀번호
  jpa:
    hibernate:
      ddl-auto: update
    generate-ddl: true
    show-sql: true
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect



logging:
  level:
    org:
      hibernate: info

Board 클래스 작성

import lombok.*;

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

@Entity
@Getter
@Setter
@NoArgsConstructor
@ToString
public class Board {
    @Id
    @GeneratedValue
    Long id;

    private String username;
    private String content;

    @Builder
    public Board(String username, String content) {
        this.username = username;
        this.content = content;
    }
}

엔티티와 관련된 포스팅은 아래 두개 링크 걸어 뒀습니다!

BoardRepository 작성

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface BoardRepository extends JpaRepository<Board, Long> {
    List<Board> findBoardByContent(String content);
    // id > ? ORDER BY id DESC limit ?, ?
    List<Board> findByIdGreaterThanOrderByIdDesc(Long id, Pageable paging);
    // id > ? limit ?, ?
    Page<Board> findByIdGreaterThan(Long id, Pageable paging);

    @Query(value = "select * from board b where b.username like %?1%", nativeQuery = true)
    List<Board> findBoardByUsername(String username);
}

JPA는 메소드의 이름만으로 원하는 쿼리실행하는 방법을 제공합니다. 이때 쿼리는 select에만 해당합니다.

쿼리 메소드는 다음과 같은 단어들로 시작합니다.

  • find...By...
  • read...By...
  • query...By...
  • get...By...
  • count...By...

예를 들어, find...By...을 사용하면 find 뒤에 엔티티 타입을 지정합니다. Board 클래스라면 findBoardBy...이 됩니다. 만약 타입을 지정하지 않으면, 현재 실행하는 Repository타입 정보를 기준으로 동작합니다.
여기선 JpaRepository<Board, Long>Board 타입이 되겠죠👍?

By 뒤쪽에는 컬럼명을 이용하여 구성합니다. 예를 들어 username 컬럼을 이용해 검색하려면 findBoardByUsername이 됩니다.

쿼리 메소드의 리턴 타입은 Page<T>, Slice<T>, List<T>와 같은 Collection<T> 형태가 됩니다.

쿼리 메소드 작성 방법은 여기서 확인 가능합니다.

테스트 코드 작성

테스트 목표

테스트 코드를 통해 하고자 하는 것은 다음과 같습니다.

  • db에 데이터 insert
  • jpa를 이용한 select
  • PageRequest, Pageable 사용
  • @Query를 이용한 쿼리 작성
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Collection;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootMysqlBoardApplicationTests {

    @Autowired
    BoardRepository boardRepository;

    @Test
    public void insert10() {
        for(int i = 0; i < 10; i++) {
            Board board = Board.builder()
                    .username("testUser" + i)
                    .content("test")
                    .build();
            boardRepository.save(board);
        }
    }


    @Test
    public void findByContent() {
        boardRepository.findBoardByContent("test")
                .forEach(board -> System.out.println(board));
    }

    @Test
    public void testIdOrderByPaging() {
        Pageable pageable = PageRequest.of(0, 5);
        Collection<Board> boards = boardRepository.findByIdGreaterThanOrderByIdDesc(0l, pageable);
        boards.forEach(board -> System.out.println(board));
    }

    @Test
    public void testPagingSort() {
        Pageable pageable = PageRequest.of(0, 5, Sort.Direction.DESC, "id");
        Page<Board> result = boardRepository.findByIdGreaterThan(0L, pageable);

        System.out.println("PAGE SIZE: " + result.getSize());
        System.out.println("TOTAL PAGE: " + result.getTotalPages());
        System.out.println("TOTAL COUNT: " + result.getTotalElements());
        System.out.println("NEXT: " + result.nextPageable());

        List<Board> list = result.getContent();
    }

    @Test
    public void testJpaQueryByUsername() {
        boardRepository.findBoardByUsername("testUser7")
                .forEach(board -> System.out.println(board));
    }

    @Test
    public void contextLoads() {
    }

}

페이징 처리와 정렬

쿼리 메소드들에는 마지막 파라미터로 페이지 처리를 할 수 있는 Pageable 인터페이스와 정렬을 처리하는 Sort 인터페이스를 사용할 수 있습니다.

Pageable 인터페이스는 말 그대로 페이징 처리에 필요한 정보를 제공합니다. 흔히 많이 사용하는 것은 org.springframework.data.domain.Pageable 인터페이스를 구현한 클래스 중 PageRequest 클래스를 이용합니다.

스프링 부트 2.0의 경우 new PageRequest()deprecated이기 때문에, PageRequest.of()를 사용합니다.

PageRequest의 of() 기능

  • PageRequest.of(int page, int size)
    • 페이지 번호(0부터 시작), 페이지당 데이터의 수
  • PageRequest.of(int page, int size, Sort.Direction direction, String...props)
    • 페이지 번호, 페이지당 데이터의 수, 정렬 방향, 속성(컬럼)뜰
  • PageRequest.of(int page, int size, Sort sort)
    • 페이지 번호, 페이지당 데이터의 수, 정렬 방향

Page<T> 타입

Spring Data JPA에서 결과가 여러 개인 경우 List<T> 타입을 이용하기도 하지만, Page<T> 타입을 이용하면 상당히 편하게 페이징 할 수 있습니다.

Page<T> 인터페이스

  public interface Page<T> extends Slice<T> {
    static <T> Page<T> empty() {
        return empty(Pageable.unpaged());
    }

    static <T> Page<T> empty(Pageable pageable) {
        return new PageImpl(Collections.emptyList(), pageable, 0L);
    }

    int getTotalPages();

    long getTotalElements();

    <U> Page<U> map(Function<? super T, ? extends U> var1);
}

Slice<T> 인터페이스


public interface Slice<T> extends Streamable<T> {
    int getNumber();

    int getSize();

    int getNumberOfElements();

    List<T> getContent();

    boolean hasContent();

    Sort getSort();

    boolean isFirst();

    boolean isLast();

    boolean hasNext();

    boolean hasPrevious();

    default Pageable getPageable() {
        return PageRequest.of(this.getNumber(), this.getSize(), this.getSort());
    }

    Pageable nextPageable();

    Pageable previousPageable();

    <U> Slice<U> map(Function<? super T, ? extends U> var1);
}

정리

스프링부트에서 mysql을 연동하고 jpa를 이용한 crud 방법을 간단하게 알아보았습니다.

다음에는 동적SQL 처리를 위한 Querydsl을 알아보겠습니다.