백엔드/Spring Boot

JPQL 예제 실습 - @Query

두개의 문 2023. 8. 24. 12:50

◉ @Query

 ▪︎ JPA에서 직접 쿼리를 작성할 수 있게 해주는 애너테이션 

 ▪︎ JPA가 쿼리를 자동으로 생성해주지만, 상황에 따라 직접 쿼리를 작성할 필요가 생기기도 함 

   - 이럴 경우를 대비해, JPA에서 직접 쿼리를 작성할 수 있는 방법에 대해 알아보자 

     

 

◉ JPA에서 직접 쿼리 작성하는 방법 

  1. JPQL로 작성 

  2. 일반 SQL로 작성 

 

 ▪︎ JPQL : JPA의 일부분으로 정의된 플랫폼 독립적인 객체지향 쿼리 언어 

    일반 SQL : 데이터베이스를 보고 작성 / JPQL : 엔티티 클래스를 보고 작성 

 

 ▪︎ JPQL과 SQL 모두 직접 쿼리를 작성하는 방법은 동일하게 @Query 어노테이션 이용 

    - @Query 어노테이션의 nativeQuery 속성 이용하여 JPQL로 작성할지, SQL로 작성할지 결정 

      • nativeQuery=true    → SQL

      • nativeQuery=false   → JPQL

 

 

 ▪︎ Snack.java 생성 ( 엔티티 추가 )

@Data
@NoArgsConstructor
@AllArgsConstructor

@Entity
@Table(name="snack")	// DB의 경우, 스네이크 표기법 이용 
public class Snack {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "snack_id")
	private int id;	// 스낵 과자의 구분 번호 
	
	private String name; 	// 스낵 과자 이름 
	private int price;	// 스낵 과자 가격 
	
}

 

 


SELECT구문 쿼리 

 

▪︎ SnackRepository 생성  

   < @Query 사용 방법 >
  - 사용자 정의 Query 메서드 작성으로, 메서드 이름 자유롭게!  
  - 데이터베이스에 특화된 쿼리(nativeQuery)가 아닌 경우, Entity에 친화된 JPA 쿼리(JPQL)를 쓴다는 의미로
    메서드명 뒤에 'JPQL'이라고 붙임 

 

 

 - JPQL과 일반 쿼리일 때 비교해보자 

public interface SnackRepository extends JpaRepository<Snack, Integer>{

	// * 일반 JPQL 쿼리 
	//   - nativeQuery = false인 경우로, 생략 가능 
	//   - from 뒤에는 엔티티명 ( 소문자로 할 경우, 에러 발생 )
	//   - Entity로 반환되므로, 별칭만 적어두어도 전체 데이터 반환됨 
	@Query(value = "Select sn From Snack sn")
	public List<Snack> selectAllJPQL();
	
	
	// * 일반 SQL 쿼리 ( = 순수 SQL ) 
	//   - nativeQuery = true인 경우 
	//   - nativeSQL로, DB마다 약간씩 문법 다름 
	//   - from 뒤에는 엔티티명이 아닌 테이블명
	@Query(value = "Select snack_id, name, price From snack", nativeQuery = true)
	public List<Snack> selectAllSQL();

 

 

- 실행 결과 콘솔창에 출력된 로그 

 


DML구문 쿼리 

 

▪︎ 일반 SQL 쿼리 중 SELECT문이 아닌 DML(insert, update, delete문) 사용 시, 다음의 어노테이션 추가 

 

 1. @Modifying

  - DML구문 뿐만 아니라 DDL구문을 사용할 때도 표기해주어야 함 

  - 엔터티를 수정하고 버퍼를 비운 후, 새로운 내용으로 새로고침 

  - 이유 : 영속성 컨텍스트에 오래된 데이터를 비워주고 새로운 데이터를 읽어오기 위해서

              즉, DB에 변화가 생기는 경우 이 애너테이션이 필요하다고 생각하면 됨 

              ( 영속성 데이터를 DB 동기화시킬 이용 ) 

 

 2. @Transactional  

  - UPDATE, DELETE , 표기를 해주어야 정상 실행이  

  - 쿼리 처리 과정에서 오류가 날 경우를 대비해서 이 애너테이션을 사용해야 함 

    즉, @Modifying 중에 예외가 발생하면, 원상복귀하는 기능 설정 

 

 

 

 

▪︎ SnackRepository.java

	
	// 1) UPDATE구문
	//  - 검색 후 업데이트 진행 → 메서드의 파라미터로 검색할 내용이 들어감 
	//  - 매개변수로 모든 필드를 넘겨주기 보다는 엔터티 자체를 넘겨주기 
	//    @Param 어노테이션 이용 ( value 속성을 이용해 명명된 파라미터에서 사용된 별명 지정 ) 
	//  - 수정된 레코드의 개수가 리턴됨 
	@Query(value = "UPDATE Snack SET name = :#{#paramSnack.name}, price = :#{#paramSnack.price} WHERE id = :#{#paramSnack.id}")
	@Modifying(clearAutomatically = true, flushAutomatically = true)
	@Transactional
	public int updateJPQL(@Param(value = "paramSnack")Snack snack);

 

 

 

▪︎ DemoApplication.java 

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
	// * @Slf4j 어노테이션의 실제 구현의 예시 
	//   - 생성자 패턴 ( 팩토리 패턴 ) 
	private static final Logger logger = LoggerFactory.getLogger(DemoApplication.class);
	
	// CommandLineRunner의 run 메서드에서 사용할 carRepository 멤버 변수를 선언 ( 의존성 주입 이용 )
	@Autowired
	private SnackRepository snackRepository;
	
	
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
		logger.info("Application started");
	}

	@Override
	public void run(String... args) throws Exception {
		// * Snack 테이블과 작업하기 
		//   selectAllJPQL() 메서드 결과, List<Snack> 
		//   -> Object 객체의 toString() 이용해서 문자열로 콘솔에 출력 
		logger.info(snackRepository.selectAllJPQL().toString());
		logger.info(snackRepository.selectAllSQL().toString());
		
		// UPDATE JPQL 사용
		// 1) updateJPQL() 메서드의 매개변수인 엔터티 객체(데이터 변경 가능한 DTO)생성 
		//    - 실제 업데이트하고 싶은 스낵 레코드가 3번이라고 가정하자 
		Snack snack = snackRepository.findById(3).get();
		// 2) 업데이트할 내용으로 변경 
		snack.setName("메로나");
		snack.setPrice(1000);
		// 3) snack DTO는 검색한 내용을 새로운 업데이트 내용으로 변경함 
		snackRepository.updateJPQL(snack);
		
		// 4) 변경 후, 새로운 내용으로 수정되었는지 확인 - logger 이용 
		logger.info(snackRepository.findById(3).toString());

	}
}

 

 

- 실행 결과 콘솔창에 출력된 로그 

 

 

// 1) INSERT구문 
	//  - 검색 쿼리 : @Query
	//  - INSERT문 : @Modifying
	@Query(value = "INSERT INTO snack (name, price) VALUES (?1, ?2)", nativeQuery = true)
	@Modifying(clearAutomatically = true, flushAutomatically = true)
	@Transactional
	public int insertSQL(String name, int price);	// 몇 건의 엔터티를 수정했는지 int 타입으로 반환