2010-06-29 24 views
40

Tôi đang sử dụng Hibernate/JPA để thực thi truy vấn PostGIS gốc. Vấn đề với các truy vấn này là chúng cần các tham số không thuộc dạng X = 'value' cổ điển.JPA/Hibernate Native Queries không nhận ra tham số

Ví dụ, những dòng sau sụp đổ

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(:lon :lat)'),4326), 0.1)"; 
    Query query = Cell.em().createNativeQuery(queryString, Cell.class); 
    query.setParameter("lon", longitude); 
    query.setParameter("lat", latitude); 

play.exceptions.JavaExecutionException: org.hibernate.QueryParameterException: could not locate named parameter [lon] 
at play.mvc.ActionInvoker.invoke(ActionInvoker.java:259) 
at Invocation.HTTP Request(Play!) 
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryParameterException: could not locate named parameter [lon] 
at org.hibernate.ejb.QueryImpl.setParameter(QueryImpl.java:358) 

Các truy vấn sau đây làm việc tuy nhiên:

String queryString = String.format("select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(%f %f)'),4326), 0.1)", longitude, latitude); 
Query query = Cell.em().createNativeQuery(queryString, Cell.class); 

(nhưng nó là SQL injection dễ xảy ra ...)

Có ai biết cách sử dụng setParameter() trong trường hợp này?

Trả lời

17

lẽ bạn có thể thay thế

'POINT(:lon :lat)' 

với

'POINT(' || :lon || ' ' || :lat || ')' 

Bằng cách này các thông số nằm ngoài các chuỗi liên tục và cần được công nhận bởi các phân tích cú pháp truy vấn.

+0

Waoo ... Cảm ơn bạn! điều này dường như làm việc !!! – user99054

+3

+1 Bí quyết thú vị thực sự xác nhận vấn đề xuất phát từ dấu ngoặc kép, không phải từ các tham số được đặt tên (mà bạn nên tránh với JPA). –

+0

Hoạt động trong Dữ liệu mùa xuân với chú thích NamedNativeQuery, do đó không yêu cầu tạo ra việc triển khai Kho lưu trữ. – lreeder

77

Việc sử dụng các tham số được đặt tên không được xác định cho truy vấn gốc. Từ đặc điểm kỹ thuật JPA (phần 3.6.3 Named Parameters):

thông số được đặt tên theo các quy tắc cho định danh quy định tại Mục 4.4.1. Việc sử dụng các thông số được đặt tên áp dụng cho ngôn ngữ truy vấn Java Persistence, và không được xác định cho truy vấn gốc. Chỉ ràng buộc tham số vị trí có thể được sử dụng hợp lý cho truy vấn gốc.

Vì vậy, hãy thử như sau thay vì:

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(?1 ?2)'),4326), 0.1)"; 
Query query = Cell.em().createNativeQuery(queryString, Cell.class); 
query.setParameter(1, longitude); 
query.setParameter(2, latitude); 

Lưu ý rằng trong JPA> = 2.0, bạn có thể sử dụng tên tham số trong truy vấn nguồn gốc.

+4

+1 để trích dẫn thông số và nhắc tôi rằng đây không phải là di động. Tôi đã thực sự sử dụng các tham số có tên trong các truy vấn nguyên gốc trong một dự án ngủ đông, nó hoạt động tốt. Trong một dự án cũ hơn, dựa trên các yếu tố cần thiết, tất cả các truy vấn gốc đều sử dụng các tham số vị trí, vì vậy tôi phải biết điều này trước và quên :) –

+0

Xin chào, cảm ơn câu trả lời của bạn. Tuy nhiên, nó vẫn có vẻ sụp đổ trên setParameter (1, kinh độ) org.hibernate.QueryParameterException: Vị trí vượt quá số tham số thứ tự khai báo. Hãy nhớ rằng các tham số thứ tự là dựa trên 1! Chức vụ: 1 bất kỳ ý tưởng nào? – user99054

+1

@samokk: Tôi tự hỏi nếu các dấu nháy đơn xung quanh POINT không phải là vấn đề ở đây. –

2

Vì vậy, ý tưởng là sử dụng thủ thuật nối được đề xuất bởi Jörn Horstmann để buộc postgres nhận ra các tham số. Mã sau hoạt động:

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(' || :lon || ' ' || :lat || ')'),4326), 0.2)"; 
Query query = Cell.em().createNativeQuery(queryString, Cell.class); 
query.setParameter("lon", longitude); 
query.setParameter("lat", latitude); 

Cảm ơn rất nhiều câu trả lời của bạn!

+1

Trên thực tế, không phải bưu điện không nhận ra các thông số, nhưng nhà cung cấp JPA của bạn (vì các dấu nháy đơn). –

2

Bạn cũng có thể thoát khỏi toàn bộ

ST_GeomFromEWKT('POINT(' || :lon || ' ' || :lat || ')') 

cuộc gọi và thay thế bằng

ST_Point(:lon,:lat) 

Sau đó, bạn không cần phải lo lắng về dấu ngoặc kép.

2

Câu trả lời của Pascal là chính xác, nhưng ... Làm thế nào là giải pháp của bạn SQL injection dễ bị? Nếu bạn đang sử dụng String.format và loại parmater %f trong ví dụ của bạn thì bất kỳ điều gì khác ngoài số ném java.util.IllegalFormatConversionException. Không có giá trị vượt qua sở hữu như "xxx" HOẶC 1 = 1 - ".

Hãy cẩn thận, sử dụng %s trong String.format là sẵn sàng cho SQL injection.

4

Tôi gặp sự cố tương tự và nhận thấy rằng các tham số có thể được đặt bằng dấu hỏi trong truy vấn gốc. Hãy thử cách này:

String queryString = "select * from Cell c where ST_DWithin(c.shape, SetSRID(ST_GeomFromEWKT('POINT(? ?)'),4326), 0.1)"; 

Query query = Cell.em().createNativeQuery(queryString, Cell.class); 
query.setParameter(1, longitude); 
query.setParameter(2, latitude); 
Các vấn đề liên quan