当您使用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 {
@Id
public int id;
@Column(name = "title")
public String title;
@ManyToOne
public Author author;
}
@Entity
@Table(name = "author")
public class Author {
@Id
public 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 JPA
return 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 here
authors.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 it
return 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的类型系统,从而可以更流畅地集成SQL,JPQL和实体持久性。
翻译自: https://www.javacodegeeks.com/2015/05/type-safe-queries-for-jpas-native-query-api.html