본문 바로가기
스프링(부트)/스프링 내용 정리

[스프링] Spring Data

by doflamingo 2020. 5. 24.

대부분의 애플리케이션은 다양한 데이터 리포지터리와 통신한다.

자바EE에서 제공하는 가장 기본적인 API는 JDBC이다.

 

일반적인 JDBC코드에는 다음이 포함된다. 

  • 실행할 쿼리
  • 스테이트먼트 객체에 대한 쿼리 매개변수를 설정하는 코드 
  • 쿼리 실행 결과(ResultSet)를 빈에 옮기는 코드 

그러나 JDBC 코드는 작성하고 관리하기 번거롭다. 

다음 두 가지 프레임워크가 JDBC 위에 추가 레이어를 제공하기 위해 널리 사용됐다. 

 

  • Mybatis(Ibatis): Mybatis는 수동으로 코드를 작성해 매개변수를 설정하고,  결과를 검색할 필요가 없다. 자바 POJO를 DB에 매핑하는
    간단한 XML 또는 어노테이션 기반 구성을 제공한다. 
  • Hibernate: Hibernate는 ORM 프레임워크다. ORM 프레임워크는 관계형 데이터베이스의 테이블에 객체를 매핑하는데 유용하다. 
    Hibernate의 가장 큰 장점은 개발자가 직접 쿼리를 작성할 필요가 없다는 것이다. 

자바 EE는 Hibernate 프레임워크를 기반으로 대략 정의된 JPA라는 API를 만들었다. 

 

 

Spring Data JPA

 

스프링 데이터의 목표는 서로 다른 종류의 데이터 리포지터리에서 데이터에 액세스하는 일관된 모델을 제공하는 것이다. 

스프링 데이터 JPA는 JPA기반 리포지터리를 쉽게 구현할 수 있게 만든 모듈이다. 

 

이 글에서는 Spring Data JPA의 핵심 인터페이스인 Repository를 통해서 스프링 데이터를 설명한다. 

 

 

기본적으로 사용하는 Repository는 네가지 종류가 있는데, 하나하나씩 살펴보겠다.

 

  1.  Repository
  2.  CrudRepository
  3.  PagingAndSortingRepository
  4.  JpaRepository

 

그 전에 예제로 볼 Repository의 Entity를 먼저 정의해야한다. 

Todo라는 Entity 클래스와 User라는 Entity 클래스를 정의할 것이다.

 

@Entity
public class Todo{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userid")
    private User user;
    @NotNull
    private String name;
    private String desc;
    private Date dueDate;
    private boolean isDone;

    /* 생성자 및 Getter, Setter 생략*/
}
@Entity
public class User{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String userid;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Todo> todos;

    /* 생성자 및 Getter, Setter 생략*/
}

 

아래와 같은 ERD가 형성됨을 알 수 있다. 

 

user 와 todo의 ERD

 

 

아무튼 이렇게 DB를 설계하고 나서 이제 본론으로 들어가서 Repository에 들어가면

 

 

1. Repository

 

Repository는 아래와 같은 형태로 되어 있다. 

 

public interface TodoRepository extends Repository<Todo, Long> {
    Iterable<Todo> findAll();
    long count();
}

Repository라는 인터페이스를 상속하고 Repository<T, ID>의 형태로 구성된다. 

T에는 내가 관리해야하는 Entity가 들어가고 ID가 같이 들어간 형태로 구성된다. 

 

이제 내가 찾고자하는 것들의 함수를 정의해주면 되는데, 여기서는 findAll 과 count를 예로 들었다. 

그러나 findBy[FieldName] 의 형태로 내가 찾고자하는 기준을 선정해서 메서드를 구성하면 알아서 

JPA를 통해 SQL 작성없이 데이터베이스에서 데이터를 가져올 수 있다. 

 

 

Repository 테스트

(미리 DB에는 데이터를 넣어놓았다. todo 3개와 user 4개)

 

@DataJpaTest
@RunWith(SpringRunner.class)
public class TodoRepositoryTest {

    @Autowired
    private TodoRepository todoRepository;

    @Test
    public void check_todo_count() {
        assertEquals(3, todoRepository.count());
    }
}

위의 코드로 테스트를 수행하면 아래와 같이 sql문이 자동으로 호출됨을 알 수 있다. 

 

Repository 테스트

 

이렇게 Repository 인터페이스를 이용하면 sql문과 dbconnection없이도 db에서 데이터를 가져오고 추가할 수 있다.

그러나 이런 Repository인터페이스는 인터페이스에 실제 메서드를 정의해야한다는 귀찮음이 있다.

그래서 이용하는 것이 CrudRepository이다. 

 

2. CrudRepository

 

CrudRepository를 이용하면 Repository에서 메서드를 정의하지 않아도 CRUD를 자동으로 사용할 수 있다. 

 

public interface TodoRepository extends CrudRepository<Todo, Long> {

}

이렇게 정의한 후 사용하면 기본 CRUD를 메서드 정의없이 사용 할 수 있다. 

 

※ 그러나 기본적으로 ID를 통한 CRUD가 가능하고, 다른 필드를 이용하려면 따로 정의를 해줘야한다. 

 

 

CrudRepositoryTest

 

 

@Test
public void saveTest() {
    Todo todo = new Todo(105L,"something","Todo", new Date(),false);
    todoRepository.save(todo);
    assertEquals(4,todoRepository.count());
}

@Test
public void findOne() {
    Optional<Todo> todo = todoRepository.findById(101L);
    assertEquals("Todo Desc 1", todo.get().getDesc());
}

@Test
public void exists() {
    assertFalse(todoRepository.existsById(105L));
    assertTrue(todoRepository.existsById(101L));
}


@Test public void delete() {
    todoRepository.deleteById(101L);
    assertEquals(2,todoRepository.count());
}

@Test
public void deleteAll() {
    todoRepository.deleteAll();
    assertEquals(0,todoRepository.count());
}

@Test
public void update() {
    Todo todo = todoRepository.findById(101L).get();
    todo.setDesc("Todo Desc Updated");
    todoRepository.save(todo);

    Todo updatedTodo = todoRepository.findById(101L).get();
    assertEquals("Todo Desc Updated", updatedTodo.getDesc());
}

 

 

CrudRepository 테스트

 

각각의 테스트가 잘 성공한 것을 알 수 있다. 

 

 

3. PagingAndSortingRepository

 

PageAndSortingRepository는 CrudRepository를 확장하고 Paging기능과 Sorting기능을 제공한다. 

데이터가 너무 많아서 보기 힘들 때는 보통 페이징 방식을 사용하는데 그럴때 유용하게 사용할 수 있다. 

 

public interface UserRepository extends PagingAndSortingRepository<User,Long> {

}

 

PagingAndSortingRepositoryTest

 

@Test
public void testing_sort_stuff() {
    Sort sort = Sort.by(Sort.Direction.DESC,"name").and(Sort.by(Sort.Direction.ASC,"userid"));
    Iterable<User> users = userRepository.findAll(sort);
    for(User user: users) {
        System.out.println(user);
    }
}

@Test
public void using_pageable_stuff() {
    PageRequest pageable = PageRequest.of(0,2);

    Page<User> userPage = userRepository.findAll(pageable);
    System.out.println(userPage);
    System.out.println(userPage.getContent());
}

 

Sort클래스를 통해서 어떤 property를 오름차순 혹은 내림차순으로 정렬할지 선언하고 findAll의 파라미터로 넣어준다. 

기본 CrudRepository에서 원래 사용하던 메서드에 인자로 sort를 하나 더 추가하면 되는 것이다. 

 

page도 마찬가지로 기본 CrudRepository에서 사용하던 메서드에 인자로 page에 관한 정보를 넣어주면 된다.

위의 예제에서는 PageRequest를 통해서 불러오는데 PageRequest에 대한 정보는 아래 글을 펼쳐보길 바란다.

더보기

PageRequest.of(param1, param2) 

param1: 가져올 페이지 인덱스 번호를 넘겨준다. 

param2: 페이지 단위를 정해주는 사이즈를 넘겨준다.

 

 

ex) PageRequest.of(0, 2)

한 페이지의 사이즈는 2개이고 그 중 0번째(첫번째) 페이지를 가져온다.

 

Sort 결과

테스트 결과를 보면 name은 내림차순 userid는 오름차순으로 정렬하도록 한 Sorting이 잘 수행된 것을 볼 수 있다. 

 

paging 결과

paging은 page사이즈를 2로 했고 그중에 첫번째 페이지를 가져오게 해서 나온 결과 값이다. 

 

이렇게 SortingAndPagingRepository를 이용하면 원하는 결과를 정제해서 가져올 수 있다. 

 

4. JpaRepository

 

JpaRepository는 PagingAndSortingRepository를 확장한 형태로 거의 유사한 기능을 가지고 있다. 

그러나 JpaRepository가 가지는 차이점은 데이터베이스로 직접 flush하거나 배치에서 레코드를 삭제할 수 있는 기능을 가지고 있다. 

 

예제는 PagingAndSortingRepository와 거의 동일하므로 생략하겠다. 

 

 

위의 4개 이외에도 다른 방법으로 데이터를 접근하고 처리할 수 있는 방법이 있다. 
예를 들어 Query 어노테이션을 통해 직접 SQL을 작성해서 커스텀 쿼리를 처리하도록 하는 방법이 존재한다. 

 

그러나 가장 대표적으로 사용하는 유형을 적어놓았고 다음에 기회가 되면 Spring Data Jpa에 대해서 더 깊게 다뤄보도록 하겠다. 

 

댓글