JPA 쿼리 매개 변수 사용

1. 소개

JPA를 사용하여 쿼리를 작성하는 것은 어렵지 않습니다. 그러나 우리는 때때로 큰 차이를 만드는 단순한 것을 잊어 버립니다.

이 중 하나는 JPA 쿼리 매개 변수이며 이것이 우리가 이야기 할 것입니다.

2. 쿼리 매개 변수 란 무엇입니까?

쿼리 매개 변수가 무엇인지 설명하는 것으로 시작하겠습니다.

쿼리 매개 변수는 매개 변수화 된 쿼리를 작성하고 실행하는 방법입니다. 따라서 대신 :

SELECT * FROM employees e WHERE e.emp_number = '123';

우리는 :

SELECT * FROM employees e WHERE e.emp_number = ?;

JDBC 준비 명령문을 사용하여 쿼리를 실행하기 전에 매개 변수를 설정해야합니다.

pStatement.setString(1, 123);

3. 쿼리 매개 변수를 사용해야하는 이유는 무엇입니까?

하지만 쿼리 매개 변수를 사용하는 대신 리터럴을 사용하도록 선택할 수 있었지만, 지금 보게 될 것처럼 권장하는 방법은 아닙니다.

JPA API를 사용하여 emp_number로 직원을 가져 오도록 이전 쿼리를 다시 작성해 보겠습니다 .하지만 매개 변수를 사용하는 대신 리터럴을 사용하여 상황을 명확하게 설명 할 수 있습니다.

String empNumber = "A123"; TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = '" + empNumber + "'", Employee.class); Employee employee = query.getSingleResult();

이 접근 방식에는 몇 가지 단점이 있습니다.

  • 매개 변수를 포함하면 보안 위험이 발생하여 JPQL 주입 공격에 취약합니다. 예상 값 대신 공격자가 예상치 못한 위험한 JPQL 표현식을 주입 할 수 있습니다.
  • 우리가 사용하는 JPA 구현과 응용 프로그램의 휴리스틱에 따라 쿼리 캐시가 고갈 될 수 있습니다. 새로운 쿼리는 각각의 새 값 / 매개 변수와 함께 사용할 때마다 빌드, 컴파일 및 캐시 될 수 있습니다. 최소한 효율적이지 않으며 예기치 않은 OutOfMemoryError 가 발생할 수도 있습니다.

4. JPA 쿼리 매개 변수

JDBC 준비 명령문 매개 변수와 유사하게 JPA는 다음을 사용하여 매개 변수화 된 쿼리를 작성하는 두 가지 방법을 지정합니다.

  • 위치 매개 변수
  • 명명 된 매개 변수

위치 또는 명명 된 매개 변수를 사용할 수 있지만 동일한 쿼리 내에서 혼합해서는 안됩니다.

4.1. 위치 매개 변수

위치 매개 변수를 사용하는 것은 앞서 언급 한 문제를 방지하는 한 가지 방법입니다.

위치 매개 변수의 도움으로 이러한 쿼리를 작성하는 방법을 살펴 보겠습니다.

TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = ?1", Employee.class); String empNumber = "A123"; Employee employee = query.setParameter(1, empNumber).getSingleResult();

이전 예제에서 살펴본 것처럼 물음표 다음에 양의 정수를 입력하여 쿼리 내에서 이러한 매개 변수를 선언합니다 . 1 부터 시작하여 앞으로 이동하여 매번 하나씩 증가시킵니다.

동일한 쿼리 내에서 동일한 매개 변수를 두 번 이상 사용하여 이러한 매개 변수를 명명 된 매개 변수와 더 유사하게 만들 수 있습니다.

매개 변수 번호 지정은 유용성, 가독성 및 유지 관리를 향상시키기 때문에 매우 유용한 기능입니다.

위치 매개 변수 바인딩은 네이티브 SQL 쿼리에서도 지원 된다는 점을 언급 할 가치가 있습니다 .

4.2. 컬렉션 값 위치 매개 변수

앞서 언급했듯이 컬렉션 값 매개 변수를 사용할 수도 있습니다.

TypedQuery query = entityManager.createQuery( "SELECT e FROM Employee e WHERE e.empNumber IN (?1)" , Employee.class); List empNumbers = Arrays.asList("A123", "A124"); List employees = query.setParameter(1, empNumbers).getResultList();

4.3. 명명 된 매개 변수

명명 된 매개 변수는 위치 매개 변수와 매우 유사합니다. 그러나이를 사용하여 매개 변수를 더 명시 적으로 만들고 쿼리를 더 읽기 쉽게 만듭니다.

TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.empNumber = :number" , Employee.class); String empNumber = "A123"; Employee employee = query.setParameter("number", empNumber).getSingleResult();

이전 샘플 쿼리는 첫 번째 쿼리와 동일하지만 ? 1 대신 명명 된 매개 변수 인 : number를 사용했습니다 .

We can see we declared the parameter with a colon followed by a string identifier (JPQL identifier) which is a placeholder for the actual value that will be set at runtime. Before executing the query, the parameter or parameters have to be set by issuing the setParameter method.

One interesting thing to remark is that the TypedQuery supports method chaining which becomes very useful when multiple parameters have to be set.

Let's go ahead and create a variation of the previous query using two named parameters to illustrate the method chaining:

TypedQuery query = em.createQuery( "SELECT e FROM Employee e WHERE e.name = :name AND e.age = :empAge" , Employee.class); String empName = "John Doe"; int empAge = 55; List employees = query .setParameter("name", empName) .setParameter("empAge", empAge) .getResultList();

Here, we're retrieving all employees with the given name and age. As we clearly see and one may expect, we can build queries with multiple parameters and as many occurrences of them as required.

If for some reason we do need to use the same parameter many times within the same query, we just need to set it once by issuing the “setParameter” method. At runtime, the specified values will replace each occurrence of the parameter.

Lastly, it's worth mentioning that the Java Persistence API specification does not mandate named parameters to be supported by native queries. Even when some implementations like Hibernate do support it, we need to take into account that if we do use it, the query will not be as portable.

4.4. Collection-Valued Named Parameters

For clarity, let's also demonstrate how this works with collection-valued parameters:

TypedQuery query = entityManager.createQuery( "SELECT e FROM Employee e WHERE e.empNumber IN (:numbers)" , Employee.class); List empNumbers = Arrays.asList("A123", "A124"); List employees = query.setParameter("numbers", empNumbers).getResultList();

As we can see, it works in a similar way to positional parameters.

5. Criteria Query Parameters

A JPA query may be built by using the JPA Criteria API, which Hibernate's official documentation explains in great detail.

In this type of query, we represent parameters by using objects instead of names or indices.

Let's build the same query again but this time using the Criteria API to demonstrate how to handle query parameters when dealing with CriteriaQuery:

CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cQuery = cb.createQuery(Employee.class); Root c = cQuery.from(Employee.class); ParameterExpression paramEmpNumber = cb.parameter(String.class); cQuery.select(c).where(cb.equal(c.get(Employee_.empNumber), paramEmpNumber)); TypedQuery query = em.createQuery(cQuery); String empNumber = "A123"; query.setParameter(paramEmpNumber, empNumber); Employee employee = query.getResultList();

For this type of query, the parameter's mechanic is a little bit different since we use a parameter object but in essence, there's no difference.

Within the previous example, we can see the usage of the Employee_ class. We generated this class with the Hibernate metamodel generator. These components are part of the static JPA metamodel, which allows criteria queries to be built in a strongly-typed manner.

6. 결론

이 기사에서는 JPA 쿼리 매개 변수 또는 입력 매개 변수를 사용하여 쿼리를 작성하는 메커니즘에 중점을 두었습니다.

두 가지 유형의 쿼리 매개 변수, 위치 및 이름이 있다는 것을 배웠습니다. 어떤 것이 우리의 목표에 가장 적합한지는 우리에게 달려 있습니다.

또한 모든 쿼리 매개 변수는 in 표현식을 제외하고 단일 값이어야한다는 점도 주목할 가치가 있습니다 . 의 경우 표현, 우리는 배열 또는 콜렉션 값 입력 매개 변수를 사용할 수있다 목록 앞의 예에서와 같이들.

이 튜토리얼의 소스 코드는 평소처럼 GitHub에서 사용할 수 있습니다.