유정잉

🎀 스프링 부트 [ JPA, Repository, @Query ] 본문

개발자 공부/🎀 스프링 공부

🎀 스프링 부트 [ JPA, Repository, @Query ]

유정♡ 2024. 7. 9. 12:15

  - JPA를 통해서 관리하게 되는 객체(이하 엔티티객체(Entity Object))를 위한 엔티티 클래스 

  - 엔티티 객체들을 처리하는 기능을 가진 Repository

 


 

[ @GeneratedValue(strategy = GenerationType.IDENTITY) ]

AUTO(default) : JPA 구현체(스프링부트에서는 Hibernate)가 생성 방식을 결정

IDENTITY : 사용하는 데이터베이스가 키 생성을 결정 MySQL이나 MariaDB의 경우 auto increment 방식을 이용

SEQUENCE : 데이터베이스의 sequence를 이용해서 키를 생성. @SequenceGenerator와 같이 사용

TABLE : 키 생성 전용 테이블을 생성해서 키 생성 @TableGenerator와 함께 사용 

package org.zerock.ex2.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Table(name = "tbl_memo")
@ToString
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Memo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long mno;

    @Column(length = 200, nullable = false)
    private String memoText;
}

[ MySql 연동을 위한 application.properties 코드 ]

spring.application.name=ex2

# MySQL ?????? ?? ??
spring.datasource.url=jdbc:mysql://localhost:3306/yujung
spring.datasource.username=root
spring.datasource.password=00000000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

## JPA (Hibernate) ??
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true

# Open-in-view ????
spring.jpa.open-in-view=false

 


[ CRUD ]

insert : save(엔티티 객체)

select : findById(키 타입), getOne(키 타입)

update : save(엔티티 객체)

delete : deleteById(키 타입), delete(엔티티 객체)

 

package org.zerock.ex2.repository;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import org.zerock.ex2.entity.Memo;

import java.util.Optional;
import java.util.stream.IntStream;

@SpringBootTest
public class MemoRepositoryTests {

    @Autowired
    MemoRepository memoRepository;

    @Test
    public void testClass(){
        System.out.println(memoRepository.getClass().getName());
    }

    @Test
    public void tesInsertDummies(){
        IntStream.rangeClosed(1,100).forEach(i->{
            Memo memo = Memo.builder().memoText("Sample..."+i).build();
            memoRepository.save(memo);
        });
    }

    @Transactional
    @Test
    public void testSelect(){
        //데이터 베이스에 존재하는 memo
        Long mno = 100L;

        Optional<Memo> result = memoRepository.findById(mno);

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

        if(result.isPresent()){
            Memo memo = result.get();
            System.out.println(memo);
        }
    }

    @Test
    public void testUpdate(){
        Memo memo = Memo.builder().mno(100L).memoText("Update Text").build();

        System.out.println(memoRepository.save(memo));
    }

    @Test
    public void testDelete(){
        Long mno = 100L;

        memoRepository.deleteById(mno);
    }
}

[ 페이징/정렬 limits ]

JPA에서 페이징 처리와 정렬은 findAll()이라는 메서드 사용. 단 한가지 주의할 사항은 리턴 타입을 Page<T> 타입으로 지정하는 경우에는 반드시 파라미터를 Pageable 타입을 이용해야 한다는 점이다.

 

PageRequest클래스의 생성자는 특이하게 protected로 선언되어 new를 이용할 수 없다. 객체를 생성하기 위해서는 static한 of()를 이용해서 처리한다. page, size, Sort라는 정보를 이용해서 객체를 생성한다.

 

JPA를 이용할 때 페이지 처리는 반드시 '0'부터 시작한다.

 

of(int page, int size) : 0부터 시작하는 페이지 번호와 개수, 정렬이 지정되지 않음

of(int page, int size, Sort sort) : 페이지 번호와 개수, 정렬 관련 정보

of(int page, int size, Sort.Direction direction, String...props) : 0부터 시작하는 페이지번호와 개수, 정렬의 방향과 정렬기준필드들 

 

package org.zerock.ex2.repository;

import org.junit.jupiter.api.Test;
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.zerock.ex2.entity.Memo;

@SpringBootTest
public class MemoRepositoryTests {

    @Autowired
    MemoRepository memoRepository;

    @Test
    public void testPageDefault() {

        //1페이지 10개
        Pageable pageable = PageRequest.of(0,10);

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

        System.out.println(result);
    }
}

 


[ Page<엔티티타입>을 이용해서 주로 사용하는 메서드들 ]

10개씩 페이징을 처리하기 떄문에 전체 페이지 수는 10페이지이고, 99개의 데이터가 존재하는 등 필요한 정보를 가져온다.

실제 페이지의 데이터를 처리하는 것은 getContent()를 이용해서 List<엔티티 타입>으로 처리하거나 Stream<엔티티 타입>을 반환하는 get()을 이용할 수 있다.

package org.zerock.ex2.repository;

import org.junit.jupiter.api.Test;
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.transaction.annotation.Transactional;
import org.zerock.ex2.entity.Memo;

@SpringBootTest
public class MemoRepositoryTests {

    @Autowired
    MemoRepository memoRepository;

    @Test
    public void testPageDefault() {

        //1페이지 10개
        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 Count : " + result.getTotalElements()); //전체 페이지

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

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

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

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

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

 


[ 정렬 조건 추가하기 ]

정렬은 Sort 타입을 파라미터로 전달할 수 있다. Sort는 한 개 혹은 여러 개의 필드 값을 이용해서 순차적으로 정렬(asc)이나 역순으로 정렬(desc)을 지정할 수 있다.

 

Sort 객체의 and()를 이용해서 여러 개의 정렬 조건을 다르게 지정할 수 있다. memoText는 asc로 하고, mno는 desc로 지정할 수 있다.

package org.zerock.ex2.repository;

import org.junit.jupiter.api.Test;
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.transaction.annotation.Transactional;
import org.zerock.ex2.entity.Memo;

@SpringBootTest
public class MemoRepositoryTests {

    @Autowired
    MemoRepository memoRepository;

    @Test
    public void testSort() {
        Sort sort1 = Sort.by("mno").descending();
        Sort sort2 = Sort.by("memoText").ascending();
        Sort sortAll = sort1.and(sort2); //and()를 이용한 페이징 정렬 연결 

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

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

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

}

 

 


[ 쿼리 메서드(Query Methods) 기능과 @Query ]

다양한 검색조건을 위한 쿼리 메서드. 특정한 범위의 Memo객체를 검색하거나, like 처리가 필요한 경우, 여러 검색 조건이 필요하다.

  - 쿼리 메서드 : 메서드의 이름 자체가 쿼리의 구문으로 처리되는 기능

  - @Query : SQL과 유사하게 엔티티 클래스의 정보를 이용해서 쿼리를 작성하는 기능 

  - Querydsl 등의 동적 쿼리 처리 기능 

 

Memo 객체의 mno값이 70부터 80사이의 객체들을 구하고 mno의 역순으로 정렬하고 싶다면 다음과 같은 인터페이스 추가한다.

package org.zerock.ex2.repository;

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

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {

    List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
}

 

mno을 기준으로 between 구문을 사용하고 order by가 적용해서 Test 작성 

@Test
public void testQueryMethods(){
    List<Memo> list = memoRepository.findByMnoBetweenOrderByMnoDesc(70L, 80L);

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

 

 


[ deleteBy로 시작하는 삭제 처리 ]

deleteBy로 메서드 이름을 시작하면 특정한 조건에 맞는 데이터를 삭제하는 것도 가능하다.

@Transactional과 @commit이라는 어노테이션 같이 사용. 

deleteBy는 별로 좋은 방법이 아님 !! -> @Query를 이용하는 것이 효율적 

package org.zerock.ex2.repository;

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

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {
    
    void deleteMemoByMnoLessThan(Long num);
}
@Commit
@Transactional
@Test
public void testDeleteQueryMethods() {
    memoRepository.deleteMemoByMnoLessThan(10L);
}

 


[ @Query 어노테이션 ]

JPA가 제공하는 쿼리 메서드는 검색과 같은 기능을 작성할 때는 편리하지만, 나중에 조인을 사용하는 등 복잡한 조건을 처리해야 하는 경우는 불편함이 더 많다. 그래서 간단한 처리만 쿼리 메서드를 이용하고, @Query를 이용하는 경우가 더 많다.

 

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

  - 필요한 데이터만 선별적으로 추출하는 기능 가능

  - 데이터베이스에 맞는 순수한 SQL(Native SQL)을 사용하는 기능

  - insert, update, delete와 같은 select가 아닌 DML 등을 처리하는 기능(@Modifying과 함께 사용)

 

public interface MemoRepository extends JpaRepository<Memo, Long> {

    @Query("SELECT m FROM Memo m WHERE m.memoText LIKE %:keyword%")
    List<Memo> findByMemoTextContaining(@Param("keyword") String keyword);
    
    @Query(value = "SELECT * FROM tbl_memo WHERE memo_text LIKE %:keyword%", nativeQuery = true)
    List<Memo> findByMemoTextContainingNative(@Param("keyword") String keyword);
    
    @Query("SELECT m FROM Memo m WHERE m.memoText LIKE %:keyword% AND m.mno > :mno")
    List<Memo> findByMemoTextAndMnoGreaterThan(@Param("keyword") String keyword, @Param("mno") Long mno);
    
    @Query("SELECT m FROM Memo m WHERE m.memoText LIKE %:keyword%")
    Page<Memo> findByMemoTextContainingWithPagination(@Param("keyword") String keyword, Pageable pageable);
    
    @Modifying
    @Query("UPDATE Memo m SET m.memoText = :memoText WHERE m.mno = :mno")
    int updateMemoTextByMno(@Param("memoText") String memoText, @Param("mno") Long mno);
}

 

[ 기본적인 JPQL 쿼리 ]

JPQL(Java Persistence Query Language)은 객체 지향 쿼리 언어로, 엔티티 객체를 대상으로 쿼리를 작성합니다.

  - Memo 엔티티에서 memoText에 특정 키워드가 포함된 레코드를 검색합니다.

  - :keyword는 메서드 매개변수로 전달된 값을 바인딩합니다.

  - @Param("keyword") String keyword: @Query에 사용된 매개변수 :keyword와 메서드 매개변수를 바인딩합니다.

public interface MemoRepository extends JpaRepository<Memo, Long> {

    @Query("SELECT m FROM Memo m WHERE m.memoText LIKE %:keyword%")
    List<Memo> findByMemoTextContaining(@Param("keyword") String keyword);
}

 

[ 네이티브 SQL 쿼리 ]

네이티브 SQL 쿼리는 데이터베이스 벤더에 종속적인 SQL 쿼리를 직접 작성하여 사용할 수 있습니다.

  - 네이티브 SQL 쿼리를 사용하여 tbl_memo 테이블에서 memo_text에 특정 키워드가 포함된 레코드를 검색합니다.

  - nativeQuery = true를 설정하여 네이티브 쿼리를 사용하도록 지정합니다.

public interface MemoRepository extends JpaRepository<Memo, Long> {

    @Query(value = "SELECT * FROM tbl_memo WHERE memo_text LIKE %:keyword%", nativeQuery = true)
    List<Memo> findByMemoTextContainingNative(@Param("keyword") String keyword);
}

 

[ 복잡한 JPQL 쿼리 ]

  - memoText에 특정 키워드가 포함되고 mno가 주어진 값보다 큰 레코드를 검색합니다

  - @Param("keyword") String keyword, @Param("mno") Long mno: @Query에 사용된 매개변수 :keyword와 :mno를 메서드 매개변수와 바인딩합니다.

public interface MemoRepository extends JpaRepository<Memo, Long> {
    
    @Query("SELECT m FROM Memo m WHERE m.memoText LIKE %:keyword% AND m.mno > :mno")
    List<Memo> findByMemoTextAndMnoGreaterThan(@Param("keyword") String keyword, @Param("mno") Long mno);
}

 

[ 페이징과 정렬을 포함한 JPQL 쿼리 ]

  - memoText에 특정 키워드가 포함된 레코드를 검색합니다

  - Page<Memo> findByMemoTextContainingWithPagination(@Param("keyword") String keyword, Pageable pageable):

  - Pageable 인터페이스를 사용하여 페이징과 정렬 정보를 전달합니다.

  - 반환 타입을 Page<Memo>로 설정하여 페이징 결과를 반환합니다.

public interface MemoRepository extends JpaRepository<Memo, Long> {
    @Query("SELECT m FROM Memo m WHERE m.memoText LIKE %:keyword%")
    Page<Memo> findByMemoTextContainingWithPagination(@Param("keyword") String keyword, Pageable pageable);
}

 

[ 업데이트 쿼리 ]

  - @Modifying: 데이터베이스에 변경 작업(업데이트, 삭제 등)을 수행할 때 사용합니다.

  - @Query("UPDATE Memo m SET m.memoText = :memoText WHERE m.mno = :mno"):

  - Memo 엔티티에서 mno가 주어진 값인 레코드의 memoText를 업데이트합니다.

  - int updateMemoTextByMno(@Param("memoText") String memoText, @Param("mno") Long mno):

  - 업데이트된 레코드 수를 반환합니다.

public interface MemoRepository extends JpaRepository<Memo, Long> {
    @Modifying
    @Query("UPDATE Memo m SET m.memoText = :memoText WHERE m.mno = :mno")
    int updateMemoTextByMno(@Param("memoText") String memoText, @Param("mno") Long mno);
}
728x90