본문 바로가기

1.프로그래밍/Java

[Spring Boot] Spring Data JPA 기초(코드로 배우는 스프링 부트 웹 프로젝트 )

728x90
반응형

[Spring Boot] Spring DATA JPA 기초 (코드로 배우는 스프링 부트 웹 프로젝트 )

해당 글은 코드로 배우는 스프링 부트 웹 프로젝트 - 구멍가게 코딩단(남가람북스)의 책을 참고하여
공부한 것을 정리한 글입니다.



JPA(Java Persistence API)는 Java 언어를 통해 데이터베이스와 같은 영속 계층을 처리하고자 하는 API이다.

JPA를 이해하기 위해서 우선저으로 ORM(Object Relational Mapping)이라는 기술을 알아야 한다.

ORM 이란?

ORM(Object Relational Mapping)이란 객체지향 패러다임을 관계형 데이터베이스에 보존하는 기술이다.
패러다임 입장에서 생각하자면 객체지향 패러다임을 관계형 패러다임으로 매핑(mapping)해주는 개념이라고 볼 수 있다.


ORM의 시작은 아주 단순해서 객체지향의 구조가 관계형 데이터베이스와 유사하다는 점에서 시작한다.


예를 들어, 객체지향언어에서 클래스를 생성하여 필드를 설계하는 것과
관계형 데이터베이스에서 테이블을 생성하여 컬럼들을 설계하는 것을 유사하다는 관점으로 보는 것이다.


이러한 특징에 기초해서 객체지향을 자동으로 관계형 데이터베이스에 맞게 처리해주는 기법에서 기반하여 시작된 것이 ORM이다.


ORM은 완전히 새로운 패러다임을 주장하는 것이 아니라 객체지향과 관계형 사이의 변환 기법을 의미하는 것이다.

따라서 특정 언어에 국한되는 개념이 아니고, 관계형 패러다임을 가지고 있다면 데이터베이스의 종류를 구분하지 않는다.

현실적으로 이미 여러 객체지향을 지원하는 언어에서 ORM을 위한 여러 프레임워크들이 존재하고 있다.


그렇다면 즉, JPA(Java Persistence API)는 ORM을 Java 언어에 맞게 사용하는 스펙인 것이다.
따라서 ORM이 좀 더 상위 개념이라고 볼 수 있다.


또한, JPA는 단순한 스펙이기 떄문에 여러 프레임워크가 존재한다.


그 중 가장 유명한것이 Hibernate이다.

Hibernate 란?

Spring Boot는 JPA의 구현체 중에서 Hibernate라는 구현체를 이용한다.
Hibernate는 오픈소스로 ORM을 지원하는 프레임워크이다.


또한, 단독으로 프로젝트에 적용이 가능한 독립된 프레임워크이다.
따라서 Spring Boot가 아닌 Spring만을 이용한다고 해도 Hibernate와 연동해서 JPA를 사용할 수 있다.

Gradle

// https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client
    implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.3'

Spring Boot에서 위의 코드를 통해 라이브러리를 가져오게 되면, 쉽게 사용할 수 있는 추가적인 API들을 제공받을 수 있다.

Spring Boot에서 JPA를 사용한다면 그냥 무조건 사용하자.


Spring Data JPA는 단 두가지 종류로 사용

  • JPA를 통해서 관리하게 되는 객체(Entity Object)를 위한 엔티티 클래스
  • 엔티티 객체들을 처리하는 기능을 가진 Repository

이 중에서 Repository는 Spring Data JPA에서 제공하는 인터페이스로 설계하는데 스프링 내부에서 자동으로 객체를 생성하고 실행하는 구조라 개발자 입장에서는 단순히 인터페이스를 하나 정의하는 작업만으로도 충분하다.


기존 Hibernate는 모든 코드를 직접 구현하고 트랜잭션 처리가 필요했지만, Spring Data JPA는 자동으로 생성되는 코드를 이용하므로 단순 CRUD나 페이지 처리 등의 개발에 코드를 개발하지 않아도 된다.

JPA 기본 Annotation

@Entity

엔티티 클래스는 Spring Data JPA에서는 반드시 @Entity라는 어노테이션을 추가해야만 한다.


@Entity는 해당 클래스가 엔티티를 위한 클래스이며, 해당 클래스의 인스턴스들이 JPA로 관리되는 엔티티 객체라는 것을 의미한다.


또한 @Entity가 붙은 클래스는 옵션에 따라서 자동으로 테이블을 생성할 수도 있다.
이 경우 클래스의 멤버 변수에 따라서 자동으로 칼럼들도 생성된다.

@Table

@Entity 어노테이션과 같이 사용할 수 있는 어노테이션으로 데이터베이스상에서 엔티티 클래스를 어떠한 테이블로 생서할 것인지에 대한 정보를 담기 위한 어노테이션이다.


예를 들어, @Table(name = "t_book") 와 같이 지정하는 경우 생성되는 테이블 이름이 't_book'으로 생성된다.

@Id 와 @GeneratedValue

@Entity가 붙은 클래스는 Primary Key에 해당하는 특정 필드를 @Id로 지정해야만 한다.


PK값에 사용자의 입력값을 사용하는 경우가 아니면 보통 Auto_Increment를 많이 사용한다.
그 때 @GeneratedValue를 사용한다.


@GeneratedValue(strategy = GenerationType.IDENTITY) 부분은 PK를 자동으로 생성하고자 할 때 사용한다.
만약 사용하는 데이터베이스가 MySQL 혹은 MariaDB 라면 auto increment를 기본으로 적용된다.

@Column

만일 추가적인 Column이 필요한 경우에도 마찬가지로 어노테이션을 활용한다.
이때는 @Column을 이용해서 다영한 속성을 지정할 수 있다.
주로, nullable, name, length 등을 이용해서 데이터베이스 column에 필요한 정보를 제공한다.

application.properties 설정

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true

spring.jpa.hibernate.ddl-auto=update은 프로젝트 실행 시에 자동으로 DDL(create, alter, drop 등)등을 생성할 것인지를 결정하는 설정이다.


설정값은 create, update, create-drop, validate가 있다.
예를 들어, create의 경우에는 매번 테이블 생성을 새로 시도한다.
예제에서는 update를 이용하여 변경이 필요한 경우에는 alter로 변경되고,
테이블이 없는 경우에는 create가 되도록 설정하였다.


spring.jpa.hibernate.format_sql은 실제 JPA의 구현체인 Hibernate가 동작하면서 발생하는
SQL을 포맷팅해서 출력한다. 실행되는 SQL의 가독성을 높여준다.


spring.jpa.show-sql은 JPA 처리 시에 발생하는 SQL을 보여줄 것인지를 결정한다.

JpaRepository 란?

Spring Data JPA는 JPA의 구현체인 Hibernate를 이용하기 위한 여러 API를 제공한다.
그 중 가장 많이 사용하는 것이 JpaRepository라는 인터페이스이다.


Spring Data JPA에는 여러 종류의 인터페이스의 기능을 통해서 JPA관련 작업을 별도의 코드 없이 처리할 수 있게 지원한다.


예를 들어 CRUD 작업이나, 페이징, 정렬 등의 처리도 인터페이스의 메서드를 호출하는 형태로 처리하는데
기능에 따라서 상속 구조로 추가적인 기능을 제공한다.


image


일반적인 기능만을 사용할 때는 CrudRepository를 사용하는 것이 좋고, 모든 JPA 관련 기능을 사용하고 싶을 떄는 JpaRepository를 이용한다.
특별한 경우가 아니라믄 JpaRepository를 이용하는 것이 가장 무난한 선택이다.


Spring Data JPA는 이를 상속하는 인터페이스를 선언한느 것만으로 모든 처리가 끝이나고,
실제 동작 시에는 스프링이 내부적으로 해당 인터페이스에 맞는 코드를 생성하는 방식을 이용한다.


package org.zerok.ex2.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.zerok.ex2.entity.Memo;

public interface MemoRepository extends JpaRepository<Memo, Long> {


}

MemoRepository는 인터페이스 자체이고, JpaRepository 인터페이스를 상속하는 것만으로 모든 작업이 끝난다.
이때 JpaRepository<Memo, Long> 엔티티 타입 정보(Memo 클래스 타입)와 @Id의 타입을 지정하게 된다.
이러한 선언만으로 자동으로 스프링 빈으로 등록된다.


JpaRepository의 경우 4가지의 메서드를 활용하여 CRUD 작업을 진행한다.

  • insert : save(엔티티 객체)
  • select : findById(키 타입), getOne(키 타입)
  • update : save(엔티티 객체)
  • delete : deletedById(키 타입), delete(엔티티 객체)

여기서 getOne()이라는 메서드가 존재한다.
별로 익숙하지 않은 메서드일 것이다. findById() 와의 차이점은
getOne()메서드는 lazyLoading 으로 실제 객체가 필요한 순간까지 SQL을 실행하지 않고,
실행되는 순간 SQL이 동작하는 방법이다.


JpaRepository 공식문서


위의 공식문서에서 확인하면 더 자세히 알 수있다.
하지만 Deprecated 처리되어 있는것을 보면 곧 사라지게 될건가 보다.

JpaRepositroy Paging and Sort

Spring Data JPA에서 페이징 처리와 정렬은 findAll()이라는 메서드를 사용한다.


findAll()는 JpaRepository 인터페이스의 상위인 PagingAndSortRepository의 메서드로
파라미터로 전달되는 Pageable이라는 타입의 객체에 의해서 실행되는 쿼리를 결정하게 된다.


단, 한 가지 주의할 사항은 리턴 타입을 Page 타입으로 지정하는 경우에는 반드시 파라미터를 Pageable 타입을 이용해야 한다는 점이다.


페이지 처리를 위한 가장 중요한 존재는 org.springframework.data.domain.Pageable인터페이스 이다.


Pageable 인터페이스는 페이지 처리에 필요한 정보를 전달하는 용도의 타입으로, 인터페이스이기 때문에 실제 객체를 생성할 때는 구현체인 org.springframework.data.domain.PageRequest라는 클래스를 사용한다.


PageRequest 클래스의 생성자는 특이하게도 protected로 선언되어 new를 이용할 수 없다.
그래서 static 메서드인 of()라는 메서드를 이용하여 객체 생성을 처리한다.


페이징 처리

@Test
public void testPageDefault() {

    Pageable pageable = PageRequest.of(0, 10);

    Page<Memo> result = memoRepository.findAll(pageable);

    System.out.println(result);

    System.out.println("========================");

    /* 총 몇 페이지 */
    System.out.println("Total Pages: " + result.getTotalPages());

    /* 전체 갯수 */
    System.out.println("Total Coung: " + result.getTotalElements());

    /* 현재 페이지 번호 0부터 시작 */
    System.out.println("Page Numeber: " + result.getNumber());

    /* 페이지당 데이터 개수 */
    System.out.println("Page Size:" + result.getSize());

    /* 다음 페이지 존재 여부 */
    System.out.println("has next page?: " + result.hasNext());

    /* 시작 페이지(0) 여부 */
    System.out.println("first page?: " + result.isFirst());


    for (Memo memo : result.getContent()) {
        System.out.println(memo);
    }
}

/*
Total Pages: 9
Total Coung: 90
Page Numeber: 0
Page Size:10
has next page?: true
first page?: true
Memo(mno=10, memoText=Sample...10)
Memo(mno=11, memoText=Sample...11)
Memo(mno=12, memoText=Sample...12)
Memo(mno=13, memoText=Sample...13)
Memo(mno=14, memoText=Sample...14)
Memo(mno=15, memoText=Sample...15)
Memo(mno=16, memoText=Sample...16)
Memo(mno=17, memoText=Sample...17)
Memo(mno=18, memoText=Sample...18)
Memo(mno=19, memoText=Sample...19)
*/

페이징 처리 정렬

@Test
public void testSort() {
    Sort sort1 = Sort.by("mno").descending();

    Pageable pageable = PageRequest.of(0, 10, sort1);

    Page<Memo> result = memoRepository.findAll(pageable);

    result.get().forEach(memo ->{
        System.out.println(memo);
    });

    Sort sort2 = Sort.by("memoText").ascending();
    Sort sortAll = sort1.and(sort2);    // and를 이용하여 연결

    Pageable pageable2 = PageRequest.of(0, 10, sortAll);
}
/*
Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    order by
        memo0_.mno desc limit ?

========= And 연결 =========

Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    order by
        memo0_.mno desc,
        memo0_.memo_text asc limit ?

Memo(mno=99, memoText=Sample...99)
Memo(mno=98, memoText=Sample...98)
Memo(mno=97, memoText=Sample...97)
Memo(mno=96, memoText=Sample...96)
Memo(mno=95, memoText=Sample...95)
Memo(mno=94, memoText=Sample...94)
Memo(mno=93, memoText=Sample...93)
Memo(mno=92, memoText=Sample...92)
Memo(mno=91, memoText=Sample...91)
Memo(mno=90, memoText=Sample...90)
*/

위와 같이 정렬을 사용할 수 있다.
여러개의 정렬조건을 연결하여 사용도 가능하다.

@Query Annotation

Spring Data JPA가 제공하는 쿼리 메서드는 복잡한 조건을 처리해야 하는 경우 불편할 때가 많다.
그래서, @Query 에노테이션을 사용하는 것이다.


@Query의 경우 메서드의 이름과 상관없이 메서드에 추가한 에노테이션을 통해 원하는 처리가 가능하다.

@Query의 vlaue는 JPQL(Java Persistence Query Language)로 작성하고, 이를 객체지향 쿼리라고 부른다.

객체지향 쿼리는 테이블 대신에 엔티티 클래스를 사용하고, 테이블의 칼럼 대신 클래스에 선언된 필드를 이용해 쿼리문을 작성한다.


@Query를 이용한 mno의 역순정렬

@Query("select m from Memo m order by m.mno desc")
List<Memo> getListDesc();

@Query 파라미터 바인딩

@Query의 파라미터 바인딩에는 크게 3가지의 방식이 존재한다.

  • '?1 , ?2' 와 1부터 시작하는 파라미터의 순서를 이용하는 방식
  • ':파라미터 이름' 을 활용하는 방식
  • ':#{}' 와 같이 자바 빈 스타일을 이용하는 방식

':파라미터 이름' 사용

@Transactional
@Modifying
@Query("update Memo m set m.memoTEst = :memoText where m.mno = :mno")
int updateMemoText(@Param("mno") Long mno, @Param("memoText") String memoText)

':#{}' 사용

@Transactional
@Modifying
@Query("update Memo m set m.memoText = :#{#param.memoText} where m.mno = :#{#param.mno}")
int updateMemoText(@Param("param") Memo memo);

이 외에도 @Query를 이용하여 페이징처리 countQuery 등이 존재한다.
또한 클래스 필드에 존재하지 않는 값 (에: CURRENT_DATE)등을 참조하여 Object[]로 반환 받을 수 있고,


데이터베이스 고유의 SQL 구문으 그대로 활용하여 쿼리문을 작성 할 수 있다.
이 경우는 주로 JOIN구문 같은 경우 많이 사용한다.

@Query(value = "select * from memo where mno > 0", nativeQuery = true)


위 처럼 nativeQuery = true로 설정을 해줘야 한다.

728x90
반응형