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功能的有趣博客系列:
- 结果集映射:基础
- 结果集映射:复杂映射
- 结果集映射:构造函数结果映射
- 结果集映射:Hibernate特定功能
结论
在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的类型系统,从而可以更流畅地集成SQL,JPQL和实体持久性。
翻译自: https://www.javacodegeeks.com/2015/05/type-safe-queries-for-jpas-native-query-api.html
jpa的查询api