为JPA的本机查询API键入安全查询

当您使用JPA时-有时-JPQL无法解决问题,您将不得不使用本机SQL。 从一开始,像Hibernate这样的ORM就为这些情况保留了一个开放的“后门”,并为Spring的JdbcTemplateApache 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[] 。 我们期待着将来支持ScalaCeylon之类的元组(甚至记录)类型的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之间(特别是在HibernatejOOQ之间)进行选择并不总是那么容易。

  • 当涉及到应用对象图持久性时,即当您有很多复杂的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值