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

TopOfTheHead·2025년 11월 11일

Spring JPA

목록 보기
6/10

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
	);

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의 원리
Query method 호출 시 Java Reflection을 활용하여 런타인 환경에서 실행메서드명동적으로 가져와서 JPQL문을 설정 후 동작

 Optional<Orders> findByCodeAndEmail(String code, String email);
SELECT o
     FROM Orders o
     WHERE o.Code = ?1 
     and o.Email = ?2

。다음 코드의 경우, findByCodeAndEmail() 호출 시 메서드명에 포함된 CodeEmail을 추출 후, JPQLSELECT문WHERE 절동적으로 삽입되어 동작


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 Method페이징 처리
    레포구현체.findAll(Pageable객체) 전달 시 Page<Entity>로 반환

@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개의 댓글