为JPA的本机查询API键入安全查询
當您使用JPA時-有時-JPQL無法解決問題,您將不得不使用本機SQL。 從一開始,像Hibernate這樣的ORM就為這些情況保留了一個開放的“后門”,并為Spring的JdbcTemplate , Apache DbUtils或jOOQ提供了類似的API,用于純SQL 。 這很有用,因為您可以繼續將ORM用作數據庫交互的單個入口點。
但是,使用字符串連接編寫復雜的動態SQL既繁瑣又容易出錯,并且是SQL注入漏洞的門戶。 使用像jOOQ這樣的類型安全的API會非常有用,但是您可能會發現僅在10-15個本機查詢中就很難在同一應用程序中維護兩個不同的連接,事務和會話模型。
但事實是:
您可以將jOOQ用于JPA本機查詢!
確實如此! 有幾種方法可以實現此目的。
提取元組(即Object [])
最簡單的方法將不會利用JPA的任何高級功能,而只是為您獲取JPA的本機Object[]形式的元組。 假設這個簡單的實用方法:
public static List<Object[]> nativeQuery(EntityManager em, org.jooq.Query query ) {// Extract the SQL statement from the jOOQ query:Query result = em.createNativeQuery(query.getSQL());// Extract the bind values from the jOOQ query:List<Object> values = query.getBindValues();for (int i = 0; i < values.size(); i++) {result.setParameter(i + 1, values.get(i));}return result.getResultList(); }使用API
這就是您以最簡單的形式橋接這兩個API所需要的,以通過EntityManager運行“復雜”查詢:
List<Object[]> books = nativeQuery(em, DSL.using(configuration).select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, BOOK.TITLE).from(AUTHOR).join(BOOK).on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)).orderBy(BOOK.ID));books.forEach((Object[] book) -> System.out.println(book[0] + " " + book[1] + " wrote " + book[2]));同意的結果中沒有很多類型安全性,因為我們只得到一個Object[] 。 我們期待著將來支持Scala或Ceylon之類的元組(甚至記錄)類型的Java。
因此,更好的解決方案可能是:
獲取實體
假設您具有以下非常簡單的實體:
@Entity @Table(name = "book") public class Book {@Idpublic int id;@Column(name = "title")public String title;@ManyToOnepublic Author author; }@Entity @Table(name = "author") public class Author {@Idpublic int id;@Column(name = "first_name")public String firstName;@Column(name = "last_name")public String lastName;@OneToMany(mappedBy = "author")public Set<Book> books; }并假設,我們將添加一個附加的實用程序方法,該方法還將Class引用傳遞給EntityManager :
public static <E> List<E> nativeQuery(EntityManager em, org.jooq.Query query,Class<E> type ) {// Extract the SQL statement from the jOOQ query:Query result = em.createNativeQuery(query.getSQL(), type);// Extract the bind values from the jOOQ query:List<Object> values = query.getBindValues();for (int i = 0; i < values.size(); i++) {result.setParameter(i + 1, values.get(i));}// There's an unsafe cast here, but we can be sure// that we'll get the right type from JPAreturn result.getResultList(); }使用API
現在這相當靈活,只需將jOOQ查詢放入該API并從中獲取JPA實體-兩者兼有,因為您可以輕松地從獲取的實體中添加/刪除嵌套集合,就好像您是通過JPQL來獲取它們一樣:
List<Author> authors = nativeQuery(em,DSL.using(configuration).select().from(AUTHOR).orderBy(AUTHOR.ID) , Author.class); // This is our entity class hereauthors.forEach(author -> {System.out.println(author.firstName + " " + author.lastName + " wrote");books.forEach(book -> {System.out.println(" " + book.title);// Manipulate the entities here. Your// changes will be persistent!}); });獲取實體結果
如果您比較敢于冒險并且對注釋有一種奇怪的喜好 ,或者只是想在休假前給同事開個玩笑,還可以使用JPA的javax.persistence.SqlResultSetMapping 。 想象以下映射聲明:
@SqlResultSetMapping(name = "bookmapping",entities = {@EntityResult(entityClass = Book.class,fields = {@FieldResult(name = "id", column = "b_id"),@FieldResult(name = "title", column = "b_title"),@FieldResult(name = "author", column = "b_author_id")}),@EntityResult(entityClass = Author.class,fields = {@FieldResult(name = "id", column = "a_id"),@FieldResult(name = "firstName", column = "a_first_name"),@FieldResult(name = "lastName", column = "a_last_name")})} )本質上,以上聲明將數據庫列( @SqlResultSetMapping -> entities -> @EntityResult -> fields -> @FieldResult -> column )映射到實體及其相應屬性。 使用這種強大的技術,您可以從任何類型的SQL查詢結果中生成實體結果。
同樣,我們將創建一個小的實用工具方法:
public static <E> List<E> nativeQuery(EntityManager em, org.jooq.Query query,String resultSetMapping ) {// Extract the SQL statement from the jOOQ query:Query result = em.createNativeQuery(query.getSQL(), resultSetMapping);// Extract the bind values from the jOOQ query:List<Object> values = query.getBindValues();for (int i = 0; i < values.size(); i++) {result.setParameter(i + 1, values.get(i));}// This implicit cast is a lie, but let's risk itreturn result.getResultList(); }請注意, 上面的API使用了anti-pattern ,在這種情況下可以使用,因為JPA首先不是類型安全的API。
使用API
現在,再次,您可以通過上述API將類型安全的jOOQ查詢傳遞給EntityManager ,并傳遞SqlResultSetMapping的名稱,如下SqlResultSetMapping :
List<Object[]> result = nativeQuery(em,DSL.using(configuration.select(AUTHOR.ID.as("a_id"),AUTHOR.FIRST_NAME.as("a_first_name"),AUTHOR.LAST_NAME.as("a_last_name"),BOOK.ID.as("b_id"),BOOK.AUTHOR_ID.as("b_author_id"),BOOK.TITLE.as("b_title")).from(AUTHOR).join(BOOK).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)).orderBy(BOOK.ID)), "bookmapping" // The name of the SqlResultSetMapping );result.forEach((Object[] entities) -> {JPAAuthor author = (JPAAuthor) entities[1];JPABook book = (JPABook) entities[0];System.out.println(author.firstName + " " + author.lastName + " wrote " + book.title); });在這種情況下,結果仍然是Object[] ,但是這一次, Object[]并不表示具有單獨列的元組,而是表示由SqlResultSetMapping注釋聲明的實體。
這種方法很吸引人,當您需要映射查詢的任意結果但仍然需要托管實體時,可能會使用它。 如果您想了解更多信息,我們只能推薦Thorben Janssen關于這些高級JPA功能的有趣博客系列:
- 結果集映射:基礎
- 結果集映射:復雜映射
- 結果集映射:構造函數結果映射
- 結果集映射:休眠特定功能
結論
在ORM和SQL之間(特別是在Hibernate和jOOQ之間)進行選擇并不總是那么容易。
- 當涉及到應用對象圖持久性時,即當您有很多復雜的CRUD(涉及復雜的鎖定和事務策略)時,ORM會大放異彩。
- 當運行批處理SQL(用于讀取和寫入操作),運行分析和報告時,SQL表現出色。
當您“很幸運”時(例如,工作很簡單),您的應用程序僅位于安全柵的一側,您可以在ORM和SQL之間進行選擇。 當您“幸運”時(例如– ooooh,這是一個有趣的問題),您將不得不同時使用兩者。 ( 另請參閱Mike Hadlow關于該主題的有趣文章 )
這里的信息是:可以! 使用JPA的本機查詢API,您可以利用RDBMS的全部功能運行復雜的查詢,并且仍然可以將結果映射到JPA實體。 您不限于使用JPQL。
邊注
盡管過去我們一直在批評JPA的某些方面(有關詳細信息,請參閱JPA 2.1如何成為新的EJB 2.0 ),但我們的批評主要集中在JPA對注釋的濫用上。 當使用諸如jOOQ之類的類型安全API時,可以輕松地向編譯器提供所有必需的類型信息以構造結果。 我們堅信,將來的JPA版本將更積極地使用Java的類型系統,從而可以更流暢地集成SQ??L,JPQL和實體持久性。
翻譯自: https://www.javacodegeeks.com/2015/05/type-safe-queries-for-jpas-native-query-api.html
總結
以上是生活随笔為你收集整理的为JPA的本机查询API键入安全查询的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: InetAddressImpl#look
- 下一篇: 狗篮子什么意思 狗篮子如何解释