JPA - 커스텀 메서드 작성 ( Native SQL, JPQL , Query method ) : @Query

이정수·2025년 11월 11일

Spring JPA

목록 보기
7/9

JPA에서 SQL Query를 통해 사용자사용자 정의Repository 인터페이스메서드 작성하는 방법
JpaRepository에서 기본 구현된 method ( save(), findById() , ... ) 외 다른 기능이 필요한 경우 Repository 인터페이스 내 직접 SQL을 작성하여 다른 기능을 지닌 메서드를 정의
▶ 주로 Query Method방식을 사용하며 복잡한 SQL Query의 경우 JPQL 또는 QueryDSL를 사용

JpaRepository확장하는 Repository 인터페이스메서드로 정의하여 사용

사용자가 정의 및 생성한 사용자 정의 메서드의 경우 Test Class에서 검증 작업이 필요

public interface MemberRepository extends JpaRepository<MemberEntity, UUID> {
	// Query Method 방식
	Boolean existsByLoginId(String loginId);
	// JPQL 방식
	@Query("""
		SELECT EXISTS ( SELECT m FROM MemberEntity m WHERE m.loginId = ?1 )
	""")
	Boolean existsByLoginIdByJPQL(String loginId);
	// Native SQL 방식
	@Query(value = """
		SELECT EXISTS (SELECT * FROM Member WHERE loginId = ?1);
	""",nativeQuery = true)
	Boolean existsByLoginIdByNativeSQL(String loginId);
}
  • Native SQL
    개발자DB Table을 대상으로하는 SQL Query을 직접 작성 후 사용

    @Query(SQL문, nativeQuery = true)를 통해 활용

    。매개변수를 매핑SQL내에는 :변수명 설정 및 메서드 매개변수에는 @Param("변수명") 설정

    SQL에서 return하는 필드명Entity필드명이 동일해야함.
@Query(value = """
			 	WITH cte_buffer as (
                SELECT ST_transform(
		                ST_Difference(
											ST_Buffer(ST_transform(ST_SetSRID(ST_MakePoint(:longitude,:latitude), 4326),5174), :distance),  
											ST_Buffer(ST_transform(ST_SetSRID(ST_MakePoint(:longitude,:latitude), 4326),5174), :distance)   
                ),4326) AS geom)
				SELECT n.*
						FROM nodenetwork n
						JOIN cte_buffer cte
						on ST_Within(n.geom,cte.geom)
		""", nativeQuery = true)
	Optional<Node> findByDistance(
		@Param("longitude") Double longitude,
		@Param("latitude") Double latitude,
		@Param("distance") Double distance
	);
  • JPQL ( Java Persistence Query Language )
    JPA의 일부로서 DB Table이 아닌 영속성 컨텍스트DB Entity를 대상으로 SQL Query를 작성
    Native SQL과 거의 동일하나 컴파일러의 도움을 받을 수 있다.

    。주로 복잡한 Query를 사용할 필요가 있을때 Query method 대신 사용
// JPQL 방식 : Entity의 alias를 설정
	@Query("""
		SELECT EXISTS ( SELECT m FROM MemberEntity m WHERE m.loginId = ?1 )
	""")
	Boolean existsByLoginIdByJPQL(String loginId);

@QueryJPQL 문을 작성하며 EntityAlias를 지정하여 Query를 하는것을 원칙으로한다.
DB Table명으로 Query하는게 아닌 DB Entity명으로 Query를 수행

JPQL인자 전달매개변수로 입력 시 순서대로 JPQL?1, ?2 ...으로 전달
▶ 또는 WHERE m.loginId = :loginId:매개변수명으로 직접 명시적으로 할당가능

  • Query method :
    JpaRepository확장Repository Interface 내부에 메서드명Query처럼 작성하여 커스텀 Method를 설정
    ▶ 가장 많이 사용되는 방식
    ex ) selectById(Integer id)

    Query method를 호출하는 경우 메서드명에 따라 JPQL로 변환되어 동작

    Query method는 즉 사람이 작성한 SQLEntity 기반으로 작성한 것이므로 Human error를 방지하기위해 test가 필요
    JpaRepository에서 기본적으로 제공하는 save(), delete(), findById() 등의 메서드에 대해서는 검증할 필요가 없음.

    findById() 같은 많이 사용하는 메서드는 사전에 구현됨

    복잡한 Query의 경우 메서드명도 비정상적으로 길어지므로 JPQL을 사용
    ex ) findAllByNameContaining()

    Query method 명명규칙
    findBy필드명(필드타입 필드명)에서 필드명의 경우 반드시 @Entity ClassField명Data type이 동일해야한다.
    ex ) Field명username이고, Data type : String인 경우, findByusername(String username)

    Containing 등의 옵션도 존재

    • findBy필드명(필드타입 필드명) : 식별
      DB Table 내 해당 field의 데이터에 해당하는 DB Entity가 존재 시 DB Entity를 return

    • existsBy필드명(필드타입 필드명) : 존재
      DB Table 내 해당 field의 데이터에 해당하는 DB Entity의 존재여부에 따라 boolean으로 return

    • deleteBy필드명(필드타입 필드명) : 삭제
      DB Table 내 해당 field의 데이터에 해당하는 DB Entity가 존재 시 soft delete

@Query("""JPQL Query""") : org.springframework.data.jpa.repository.Query
Spring Data JPA를 통해 SQL Query를 수행 시 JpaRepository를 확장한 Repository 인터페이스메서드에 선언하는 어노테이션
▶ 주로 JPQL을 통한 복잡한 Query를 처리 시 활용

// JPQL 방식
	@Query("""
		SELECT EXISTS ( SELECT m FROM MemberEntity m WHERE m.loginId = ?1 )
	""")
	Boolean existsByLoginIdByJPQL(String loginId);
  • @Query("""SQL Query""", nativeQuery = true)
    Entity와 상호작용하는 JPQL이 아닌 DB Table과 상호작용하는 Native SQL을 사용하도록 설정

@Modifying
@Query를 통한 JPQL 또는 Native SQL을 통해 INSERT/UPDATE/DELETE를 수행하는 경우 @Query에 추가로 @Modifying, @Transactional을 선언
Native SQL에서 Update한 내용을 바로 Entity 반영 할 필요가 있는 경우 @Modifying(clearAutomatically = true) 선언하여 적용 시 Entity에서도 변경내용을 바로 반영

	@Modifying
	@Transactional
	@Query("""
		delete from Route
				where Route.account.id = ?1
				and Route.routeStatus = 'TEMP'
		""")
	void deleteTempByAccountId(UUID accountId);
	@Modifying(clearAutomatically = true)
	@Transactional
	@Query(value = """
		insert into routelink(
  	id, geojson, link_length,
  	link_slope, link_cost, link_type,
  	drink_toilet, route_id
		)
		select
			uuid_generate_v4() as id,
			st_asgeojson(x.geom) as geojson,
			link_length,
			link_slope,
			link_cost,
			link_type,
			drink_toilet,
			:route_id as route_id
			from linknetwork as x
		join (
  		select *  
				from pgr_dijkstraVia(
					:dijkstra_sql,
					:node_ids ::bigint[]
			)
			where agg_cost < :max_distance
  	) as y
		on x.id = y.edge
		order by y.seq
		""", nativeQuery = true)
	void createPaths(
		@Param("node_ids") Long[] nodeIds,
		@Param("max_distance") int maxDistance,
		@Param("dijkstra_sql") String dijkstra_sql,
		@Param("route_id") UUID routeId
	);
profile
공부기록 블로그

0개의 댓글