본문 바로가기
Spring/JPA

[JPA] @Query, 직접 쿼리 작성

by J4J 2021. 3. 29.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 JPA에서 직접 쿼리를 작성할 수 있게 해주는 @Query에 대해 적어보는 시간을 가져보려고 합니다.

 

 

JPQL

 

JPA가 쿼리를 자동으로 생성해주지만 상황에 따라 직접 쿼리를 작성할 필요가 생기게 됩니다.

 

JPA에서 직접 쿼리를 작성할 수 있는 방법은 다음과 같이 2가지가 있습니다.

 

  • JPQL로 작성
  • 일반 SQL로 작성

 

JPQL이라고 하는 것은 JPA의 일부분으로 정의된 플랫폼 독립적인 객체지향 쿼리 언어입니다.

 

JPA에서 사용할 수 있는 쿼리 언어로 일반 SQL이 데이터베이스를 바라보고 작성한다면 JPQL은 엔티티 클래스를 바라보고 작성해야 합니다.

 

 

JPQL과 SQL모두 직접 쿼리를 작성하는 방법은 동일하게 @Query어노테이션을 이용하면 됩니다.

 

그리고 @Query어노테이션에 들어있는 nativeQuery라는 속성을 이용하여 JPQL로 작성한 것인지 SQL로 작성한 것인지를 구분할 수 있습니다.

 

  • nativeQuery = true → SQL
  • nativeQuery = false (default) → JPQL

 

또한 메서드 명은 기존 자동생성 방식과 달리 자유롭게 작성할 수 있습니다.

 

 

관련 코드를 생성하여 어떻게 작성할 수 있는지를 보여드리도록 하겠습니다.

 

 

반응형

 

 

기본 세팅

 

먼저 테이블, 엔티티 생성 등의 기본 세팅을 해주도록 하겠습니다.

 ※ JPA 환경 설정은 여기를 참고하세요.

 

주제는 과자로 하겠습니다.

 

[ 1. MySQL에 테이블 / 데이터 생성 ]

 

create table snack (
    snack_id int auto_increment,
    name varchar(50),
    price int,
    primary key(snack_id)
);

insert into snack(name, price) values ('허니버터칩', 1500);
insert into snack(name, price) values ('스윙칩', 2000);
insert into snack(name, price) values ('롤리폴리', 1200);
insert into snack(name, price) values ('포스틱', 1600);
insert into snack(name, price) values ('감자깡', 1600);

 

 

[ 2. Snack 엔티티 클래스 ]

 

package com.spring.jpa.dto;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "snack")
public class Snack implements Serializable {
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Id
	@Column(name = "snack_id")
	private int id;
	
	private String name;
	private int price;
}

 

 

[ 3. SnackRepository 클래스 ]

 

package com.spring.jpa.repository;

import java.util.List;

import javax.transaction.Transactional;

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 com.spring.jpa.dto.Snack;

public interface SnackRepository extends JpaRepository<Snack, Integer>{ // 제네릭 타입: <엔티티 클래스, 엔티티클래스의 기본키>

}

 

 

[ 4. 단위 테스트 ]

 

package com.spring.jpa;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.jpa.config.RootContext;
import com.spring.jpa.dto.Snack;
import com.spring.jpa.repository.SnackRepository;

import lombok.extern.slf4j.Slf4j;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RootContext.class)
@Slf4j
public class JPATest {
	
	@Autowired
	SnackRepository snackRepository;
	
	@Test
	public void snackTest() {
		
	}
}

 

 

 

 

@Query 사용 방법 (일반 쿼리)

 

  • Repository
package com.spring.jpa.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import com.spring.jpa.dto.Snack;

public interface SnackRepository extends JpaRepository<Snack, Integer>{ // 제네릭 타입: <엔티티 클래스, 엔티티클래스의 기본키>
	// 일반 JPQL쿼리, from뒤는 엔티티 명 (소문자로 할 시 에러)
	@Query(value = "select sn from Snack sn")
	public List<Snack> selectAllJPQL();
	
	// 일반 SQL쿼리
	@Query(value = "select snack_id, name, price from snack", nativeQuery = true)
	public List<Snack> selectAllSQL();
}

 

 

  • 단위 테스트
package com.spring.jpa;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.jpa.config.RootContext;
import com.spring.jpa.dto.Snack;
import com.spring.jpa.repository.SnackRepository;

import lombok.extern.slf4j.Slf4j;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RootContext.class)
@Slf4j
public class JPATest {
	
	@Autowired
	SnackRepository snackRepository;
	
	@Test
	public void snackTest() {
		log.info(snackRepository.selectAllJPQL().toString());
		log.info(snackRepository.selectAllSQL().toString());
	}
}

 

 

  • 실행 결과 로그
Hibernate: 
    /* select
        sn 
    from
        Snack sn */ select
            snack0_.snack_id as snack_id1_1_,
            snack0_.name as name2_1_,
            snack0_.price as price3_1_ 
        from
            snack snack0_
INFO : com.spring.jpa.JPATest - [Snack(id=1, name=허니버터칩, price=1500), Snack(id=2, name=스윙칩, price=2000), Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]
Hibernate: 
    /* dynamic native SQL query */ select
        snack_id,
        name,
        price 
    from
        snack
INFO : com.spring.jpa.JPATest - [Snack(id=1, name=허니버터칩, price=1500), Snack(id=2, name=스윙칩, price=2000), Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]

 

 

 

@Query 사용 방법 (파라미터 사용)

 

  • Repository
package com.spring.jpa.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.spring.jpa.dto.Snack;

public interface SnackRepository extends JpaRepository<Snack, Integer>{ // 제네릭 타입: <엔티티 클래스, 엔티티클래스의 기본키>
	// JPQL은 엔티티의 변수명이 id이기 때문에 조건절에 sn.id
	// JPQL 일반 파라미터 쿼리, @Param 사용 X
	@Query(value = "select sn from Snack sn where sn.id > ?1")
	public List<Snack> selectJPQLById1(int id);
	
	// JPQL 일반 파라미터 쿼리, @Param 사용 O
	@Query(value = "select sn from Snack sn where sn.id > :id")
	public List<Snack> selectJPQLById2(@Param(value = "id") int id);
	
	// JPQL 객체 파라미터 쿼리
	@Query(value = "select sn from Snack sn where sn.id > :#{#paramSnack.id}")
	public List<Snack> selectJPQLById3(@Param(value = "paramSnack") Snack snack);
	
	// SQL은 테이블의 컬럼명이 snack_id이기 때문에 조건절에 snack_id
	// SQL 일반 파라미터 쿼리, @Param 사용 X
	@Query(value = "select snack_id, name, price from snack where snack_id > ?1", nativeQuery = true)
	public List<Snack> selectSQLById1(int id);
	
	// SQL 일반 파라미터 쿼리, @Param 사용 O
	@Query(value = "select snack_id, name, price from snack where snack_id > :id", nativeQuery = true)
	public List<Snack> selectSQLById2(@Param(value = "id") int id);
	
	// SQL 객체 파라미터 쿼리
	@Query(value = "select snack_id, name, price from snack where snack_id > :#{#paramSnack.id}", nativeQuery = true)
	public List<Snack> selectSQLById3(@Param(value = "paramSnack") Snack snack);
}

 

 

  • 단위 테스트
package com.spring.jpa;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.jpa.config.RootContext;
import com.spring.jpa.dto.Snack;
import com.spring.jpa.repository.SnackRepository;

import lombok.extern.slf4j.Slf4j;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RootContext.class)
@Slf4j
public class JPATest {
	
	@Autowired
	SnackRepository snackRepository;
	
	@Test
	public void snackTest() {
		log.info(snackRepository.selectJPQLById1(2).toString());
		log.info(snackRepository.selectJPQLById2(2).toString());
		log.info(snackRepository.selectJPQLById3(new Snack(2, null, 0)).toString());
		log.info(snackRepository.selectSQLById1(2).toString());
		log.info(snackRepository.selectSQLById2(2).toString());
		log.info(snackRepository.selectSQLById3(new Snack(2, null, 0)).toString());
	}
}

 

 

 

  • 실행 결과 로그
Hibernate: 
    /* select
        sn 
    from
        Snack sn 
    where
        sn.id > ?1 */ select
            snack0_.snack_id as snack_id1_1_,
            snack0_.name as name2_1_,
            snack0_.price as price3_1_ 
        from
            snack snack0_ 
        where
            snack0_.snack_id>?
INFO : com.spring.jpa.JPATest - [Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]
Hibernate: 
    /* select
        sn 
    from
        Snack sn 
    where
        sn.id > :id */ select
            snack0_.snack_id as snack_id1_1_,
            snack0_.name as name2_1_,
            snack0_.price as price3_1_ 
        from
            snack snack0_ 
        where
            snack0_.snack_id>?
INFO : com.spring.jpa.JPATest - [Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]
Hibernate: 
    /* select
        sn 
    from
        Snack sn 
    where
        sn.id > :__$synthetic$__1 */ select
            snack0_.snack_id as snack_id1_1_,
            snack0_.name as name2_1_,
            snack0_.price as price3_1_ 
        from
            snack snack0_ 
        where
            snack0_.snack_id>?
INFO : com.spring.jpa.JPATest - [Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]
Hibernate: 
    /* dynamic native SQL query */ select
        snack_id,
        name,
        price 
    from
        snack 
    where
        snack_id > ?
INFO : com.spring.jpa.JPATest - [Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]
Hibernate: 
    /* dynamic native SQL query */ select
        snack_id,
        name,
        price 
    from
        snack 
    where
        snack_id > ?
INFO : com.spring.jpa.JPATest - [Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]
Hibernate: 
    /* dynamic native SQL query */ select
        snack_id,
        name,
        price 
    from
        snack 
    where
        snack_id > ?
INFO : com.spring.jpa.JPATest - [Snack(id=3, name=롤리폴리, price=1200), Snack(id=4, name=포스틱, price=1600), Snack(id=5, name=감자깡, price=1600)]

 

 

 

 

@Modifying / @Transactional

 

위의 쿼리처럼 select구문이 아닌 DML(insert, update, delete) 구문을 사용할 때는 다음의 어노테이션을 추가적으로 사용해야 됩니다.

 

  • @Modifying
  • @Transactional

 

먼저 @Modifying은 insert, update, delete 뿐만 아니라 DDL구문을 사용할 때도 표기를 해줘야 됩니다.

 

왜냐하면 영속성 컨텍스트에 오래된 데이터를 비워주고 새로운 데이터를 읽어오기 위해서입니다.

 

그리고 @Transactional은 update, delete를 할 때 표기를 해줘야 정상 실행이 됩니다.

 

update구문으로 예제 코드를 만들어 보겠습니다.

 

 

  • Repository
package com.spring.jpa.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 com.spring.jpa.dto.Snack;

public interface SnackRepository extends JpaRepository<Snack, Integer>{ // 제네릭 타입: <엔티티 클래스, 엔티티클래스의 기본키>
	// JPQL은 엔티티의 변수명이 id이기 때문에 조건절에 sn.id
	// JPQL 쿼리
	@Query(value = "update Snack set name = :#{#paramSnack.name}, price = :#{#paramSnack.price} where id = :#{#paramSnack.id}")
	@Modifying
	@Transactional
	public int updateJPQL(@Param(value = "paramSnack") Snack snack);
	
	// SQL은 테이블의 컬럼명이 snack_id이기 때문에 조건절에 snack_id
	// SQL 쿼리
	@Query(value = "update snack set name = :#{#paramSnack.name}, price = :#{#paramSnack.price} where snack_id = :#{#paramSnack.id}", nativeQuery = true)
	@Modifying
	@Transactional
	public int updateSQL(@Param(value = "paramSnack") Snack snack);
}

 

 

 

  • 단위 테스트
package com.spring.jpa;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import com.spring.jpa.config.RootContext;
import com.spring.jpa.dto.Snack;
import com.spring.jpa.repository.SnackRepository;

import lombok.extern.slf4j.Slf4j;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = RootContext.class)
@Slf4j
public class JPATest {
	
	@Autowired
	SnackRepository snackRepository;
	
	@Test
	public void snackTest() {
		Snack snack = snackRepository.findById(3).get();
		snack.setName("메로나");
		snack.setPrice(1000);
		snackRepository.updateJPQL(snack);
		log.info(snackRepository.findById(3).toString());
		
		snack.setName("츄파츕스");
		snack.setPrice(500);
		snackRepository.updateSQL(snack);
		log.info(snackRepository.findById(3).toString());
	}
}

 

 

  • 실행 결과 로그
Hibernate: 
    select
        snack0_.snack_id as snack_id1_1_0_,
        snack0_.name as name2_1_0_,
        snack0_.price as price3_1_0_ 
    from
        snack snack0_ 
    where
        snack0_.snack_id=?
Hibernate: 
    /* update
        Snack 
    set
        name = :__$synthetic$__1,
        price = :__$synthetic$__2 
    where
        id = :__$synthetic$__3 */ update
            snack 
        set
            name=?,
            price=? 
        where
            snack_id=?
Hibernate: 
    select
        snack0_.snack_id as snack_id1_1_0_,
        snack0_.name as name2_1_0_,
        snack0_.price as price3_1_0_ 
    from
        snack snack0_ 
    where
        snack0_.snack_id=?
INFO : com.spring.jpa.JPATest - Optional[Snack(id=3, name=메로나, price=1000)]
Hibernate: 
    /* dynamic native SQL query */ update
        snack 
    set
        name = ?,
        price = ? 
    where
        snack_id = ?
Hibernate: 
    select
        snack0_.snack_id as snack_id1_1_0_,
        snack0_.name as name2_1_0_,
        snack0_.price as price3_1_0_ 
    from
        snack snack0_ 
    where
        snack0_.snack_id=?
INFO : com.spring.jpa.JPATest - Optional[Snack(id=3, name=츄파츕스, price=500)]

 

 

 

 

참조

 

JPQL이란?

[스프링 데이터 JPA] 4-6. 스프링 데이터 JPA: Update 쿼리 메소드

 

 

 

 

이상으로 JPA에서 직접 쿼리를 작성할 수 있게 해주는 @Query에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

728x90
반응형

댓글