관리 메뉴

bright jazz music

JpaRepository + @Query, nativeQuery = true 본문

Framework/Spring

JpaRepository + @Query, nativeQuery = true

bright jazz music 2022. 6. 23. 15:24
package org.zerok.ex2.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.zerok.ex2.entity.Memo;

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {
    //JpaRepository를 사용할 때는 엔티티 타입 정보(여기서는 Memo클래스)와 @Id 타입을 지정한다.
    //SpringDataJpa는 인터페이스 선언만으로도 자동으로 bean으로 등록한다.
    //(내부적으로는 인터페이스 타입에 맞는 객체를 생성해서 빈으로 등록한다.)
    //선언이 끝났으면 test폴더-repository패키지생성-MemoRepositoryTests클래스를 작성해서 진행



    List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
    //쿼리메소드와 @Query를 사용하기 위해 선언. 이 메소드의 이름 자체가 질의문이 된다.

    void deleteMemoByMnoLessThan(Long num);


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

    @Transactional
    @Modifying
    @Query("update Memo m set m.memoText = :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 updateMemoText2(@Param("param") Memo memo);
    
    
    @Query(value = "select m from Memo m where m.mno > :mno",
            countQuery = "select count(m) from Memo m where m.mno > :mno" )
    Page<Memo> getListWithQuery(Long mno, Pageable pageable);
    
    
    
    
    
    @Query(value = "select m.mno, m.memoText, CURRENT_DATE from Memo m " +
            "where m.mno > :mno",
                            countQuery = "select count(m) from Memo m where m.mno > :mno" )
    Page<Object[]> getListWithQueryObject(Long mno, Pageable pageable);
    
    
    
    @Query(value="select * from tbl_memo where mno > 0", nativeQuery = true)
    List<Object[]> getNativeResult();
    
    //DB의 테이블명을 그대로 사용해야 한다. tbl_memo를 그냥 memo라고 썼다가 오류발생하였다.


}
    
    
    

}

 

 

 

 

 

package org.zerok.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.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.test.annotation.Commit;
import org.zerok.ex2.entity.Memo;

import javax.transaction.Transactional;
import java.util.List;
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());

        /*
         * testClass()메서드는 MemoRepository 인터페이스 타입의 실제 객체가 어떤 것인지 확인한다.
         * 스프링이 내부적으로 해당 클래스를 자동생성(AOP)하는데, 이 때 선언한 적 없는 클래스의 이름이
         * com.sun.proxy.$Proxy110 와 같이 콘솔에 출력된다.(동적 프록시 방식으로 생성됨)
         *
         *
         * */
    }


    @Test //등록작업 테스트
    public void testInsertDummies(){
        IntStream.rangeClosed(1,100).forEach(i -> {
            Memo memo = Memo.builder().memoText("Sample..." + i).build();
            memoRepository.save(memo);
        });

        //한 번에 여러 개의 객체를 저장하도록 작성. 100개의 Memo 객체를 생성하고 MemoRepository를 이용해서 insert.
        // 두 번 실행하면 테이블에 행이 200번까지 만들어진다.
    }

    @Test   //조회작업 테스트: findById()사용
    public void testSelect(){
        //DB에 존재하는 mno
        Long mno = 100L;

        Optional<Memo> result = memoRepository.findById(mno);
        //100번이 존재하지 않더라도 오류를 반환하지 않음. 물론 객체도 반환되지 않음.

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

        if(result.isPresent()) {
            Memo memo = result.get();
            System.out.println(memo);
        }
        /*
        * 조회 작업의 테스트는 findById()나 getOne()을 이용해서 엔티티 객체를 조회할 수 있다.
        * findById()와 getOne()은 동작하는 방식이 조금 다르다.
        * DB를 먼저 이용하는지, 필요한 순간까지 미루는지에 대한 차이이다.
        *
        * findById()는 DB를 먼저 이용해서 값을 가져온다.
        * findById()를 실행한 순간에 이미 sql은 처리되었고
        *
        * findById()의 경우 java.util 패키지의 Optional 타입으로 반환되기 때문에
        * 한 번 더 결과가 존재하는지 체크하는 형태로 작성하게 된다.
        * "==============================" 부분은 SQL 처리 이후에 실행된다.
        * */
    }

    @Transactional //트랜잭션 처리를 위해 사용하는 어노테이션. 없으면 오류 반환: no session
    @Test   //조회작업 테스트: getOne()사용
    public void testSelect2(){
        Long mno = 101L;

        Memo memo = memoRepository.getOne(mno);
        //리턴 값은 해당 객체이지만 필요한 순간까지 sql을 실행하지 않는다.
        //해당 값이 테이블에 존재하지 않으면 오류 반환: Unable to find org.zerok.ex2.entity.Memo with id 100
        //getOne()은 depricated됨. getReferenceById()로 대체. testSelect3()에서 사용

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

        System.out.println(memo);//실제 객체를 사용하는 순간에 SQL이 동작한다.

    }

    @Transactional //트랜잭션 처리를 위해 사용하는 어노테이션. 없으면 오류 반환: no session
    @Test   //조회작업 테스트: getOne()사용
    public void testSelect3(){
        Long mno = 101L;

        Memo memo = memoRepository.getReferenceById(mno); //getOne() 대신에 사용
        //리턴 값은 해당 객체이지만 필요한 순간까지 sql을 실행하지 않는다.
        //해당 값이 테이블에 존재하지 않으면 오류 반환 : Unable to find org.zerok.ex2.entity.Memo with id 100
        //getOne()은 depricated됨. getReferenceById()로 대체. testSelect3()에서 사용

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

        System.out.println(memo);//실제 객체를 사용하는 순간에 SQL이 동작한다.

    }

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

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

        /*
        * 수정작업은 등록작업과 동일하게 save()를 이용해서 처리한다.
        * 내부적으로 해당 엔티티의 @Id 값이 일치하는지 확인해서 insert 혹은 update 작업을 처리한다.
        *
        * 여기서는 100번의 Memo객체를 만들고, save()를 호출한다.
        * 호출결과를 보면 내부적으로 select 쿼리로 해당 번호의 Memo 객체를 확인하고
        * 이를 update하는 것을 볼 수 있다.
        *
        * JPA는 엔티티 객체들을 메모리 상에 보관하려고 하기 때문에 특정한 엔티티 객체가 존재하는지
        * 확인하는 select가 먼저 실행되고 해당 @Id를 가진 엔티티 객체가 있다면 update를,
        * 그렇지 않다면 insert를 실행한다.
        * */
    }

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

        /*
        * 삭제 작업도 위와 동일한 개념이 적용된다. 삭제하려는 번호(mno)의 엔티티 객체가 있는지
        * 먼저 확인(select)하고 이를 삭제 시도한다.
        * deleteById()의 리턴 타입은 void이고 만일 해당 데이터가 존재하지 않으면
        * org.springframework.dao.EmptyResultAccessException 예외를 발생시킴.
        * */
    }

    @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 Count: " + result.getTotalElements());    // 총 개수
        System.out.println("Total 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);
        }
    }

    @Test
    public void testSort(){
        Sort sort1 = Sort.by("mno").descending();
        Sort sort2 = Sort.by("memoText").ascending();
        Sort sortAll = sort1.and(sort2); //and를 이용한 연결. memo는 desc로, memoText는 asc로 정렬

        Pageable pageable = PageRequest.of(0, 10, sortAll);
        Page<Memo> result = memoRepository.findAll(pageable);

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

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

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


    @Commit
    @Transactional
    @Test
    public void testDeleteQueryMethod(){
        memoRepository.deleteMemoByMnoLessThan(20L); //10번 전까지의 데이터를 삭제

        /*
        * @Transactional과 @Commit 어노테이션 함께 사용.
        * select문으로 해당 엔티티 객체들을 가져오는 작업과 각 엔티티를 삭제하는 작업이 같이 이뤄지기 때문.
        *
        * @Commit은 최종 결과를 커밋하기 위해서 사용. 이를 적용하지 않으면 쿼리가 기본적으로 롤백 처리되어
        * 결과가 반영되지 않는다.
        *
        * deleteBy는 실제 개발에는 일반적으로 사용되지 않는다. 그 이유는 SQL을 이용하듯 한 번에 삭제가
        * 이뤄지는 것이 아니라 각 엔티티 객체를 하나씩 삭제하기 때문이다. 여기의 예제의 경우는 10번을 삭제하는 것이다.
        * 실제 개발에는 deleteBy등의 쿼리메소드보다는 @Query 어노테이션을 이용해서 이와 같은 비효율을 개선한다.
        *
        * */
    }

    @Test
    public void queryAnnotationTest(){
        memoRepository.getListDesc();   //@Query("select m from Memo m order by m.mno desc") List<Memo> getListDesc();
    }

    @Test
    public void queryAnnotationTest2(){
        memoRepository.updateMemoText(95L, "new text!!");
    }
    
     @Test
    public void queryAnnotationTest3(){
        Memo memo = Memo.builder().memoText("update memo3 !!!!").mno(94L).build();
        int n = memoRepository.updateMemoText2(memo);

        System.out.println("==> " + n);
    }
    
    @Test
    public void queryAnnotationTest4(){
        Pageable pageable = PageRequest.of(0, 10, Sort.by("memoText").descending());
        Page<Memo> pagelist = memoRepository.getListWithQuery(100L, pageable);

        pagelist.forEach(r -> {
            System.out.println(r);
        });
    }
    
    
    
    
    
    
    
    
    @Test
    public void queryAnnotationTest5(){
        Pageable pageable = PageRequest.of(0, 10, Sort.by("memoText").descending());
        Page<Object[]> listWithQueryObject = memoRepository.getListWithQueryObject(50L, pageable);

        listWithQueryObject.forEach(r -> {
            Arrays.stream(r).forEach(c -> {
                System.out.println(c.toString());
            });
            System.out.println("----------------------------------------");
        });
    }
    
    
    
    @Test
    public void
    testNativeObject(){
        List<Object[]> listNativeQueryObject = memoRepository.getNativeResult();
        System.out.println(listNativeQueryObject);
        listNativeQueryObject.forEach(r -> {
            Arrays.stream(r).forEach(c -> {
                System.out.println(c.toString());
            });
            System.out.println("----------------------------------------");
        });
    }



}

 

 

 

 

 

@Test

public void queryAnnotationTest2()

 

 

 

 

 

 

@Test

public void queryAnnotationTest3()

 

 

 

 

 

 

@Test

public void queryAnnotationTest4()

 

 

 @Test
    public void queryAnnotationTest5(){

 

 

 

 

    @Test
    public void
    testNativeObject()

 

//MemoRepository.java

@Query(value="select * from tbl_memo where mno > 0", nativeQuery = true)
List<Object[]> getNativeResult();

위의 쿼리를 사용해서 아래의 결과를 도출한다.

 

 

 

 

 

 

**** 주의 **** 

 

아래와 같은 오류 발생 시.

2022-06-25 08:24:36.090 ERROR 16600 --- [    Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper   : (conn=629) Table 'bootex.memo' doesn't exist

could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet

 

증상:

 

 

원인:

@Query(value="select * from memo where mno > 0", nativeQuery = true)
List<Object[]> getNativeResult();

//위와 같이 코드가 쓰여 있다. 이를 그대로 적용하면 위와 같은 오류가 발생한다.
// DB에 bootex.memo 테이블이 없기 때문에 resultSet을 추출하지 못했다는 것이다.
// 즉 실습용으로 생성한 bootex라는 DB에 memo 테이블이 없다는 뜻이다.

// 실제로 DB를 확인해 보면 memo 테이블은 존재하지 않는다.
// 대신 tbl_memo라는 이름의 테이블이 존재한다.

// 이는 우리가 Memo.java 클래스에서 @Table(name="tbl_memo") 코드를 사용하여
// 테이블 이름을 tbl_memo로 생성하였기 때문이다.
// 아무래도 책에서 테이블 명을 오기한 듯하다.


// 어쨌든 위 쿼리의 memo를 tbl_memo로 변경해주면 @test가 정상 구동한다.


// memo  ==> tbl_memo 변경 후의 쿼리


@Query(value="select * from tbl_memo where mno > 0", nativeQuery = true)
List<Object[]> getNativeResult();

 

 

 

 

 

 

Comments