JPA有自己的查询语言,称为JPQL。 JPQL与SQL非常相似,主要区别在于JPQL与应用程序中定义的实体一起使用,而SQL与数据库中定义的表和列名称一起使用。 在定义将对定义的Entity类执行CRUD操作的JPA查询时,JPA为我们提供了多种选择。 这些选项是动态查询,命名查询和条件查询。 这篇文章试图详细介绍每个选项,重点放在何时使用每种类型的查询定义,可能的性能问题以及与动态查询相关的一些安全威胁。
动态查询
应用程序在运行时创建的查询称为动态查询。 当我们将简单的JPA兼容查询字符串传递给EntityManager类的createQuery方法时,就会创建动态查询。 定义动态查询有其优点和缺点。 让我们依次查看它们中的每一个。
优点
使用动态查询的主要优点是,在运行时之前以及查询的结构取决于用户输入或其他条件之前,您都不知道查询的外观。
缺点
动态查询的主要缺点是,每次调用时,将JPQL查询转换为SQL都会产生成本。 大多数提供程序可能会尝试缓存从动态查询生成的SQL查询,但是这样做可能并不总是成功的。 当不使用查询将参数值直接绑定到查询字符串中时,查询将不会被缓存或提供程序会发现难以缓存的情况。 在该示例中,由于每次调用JPQL都会由于动态参数而生成新查询,因此不会缓存查询。
@Stateless
public class DynamicQueriesExample implements DynamicQuery {
@PersistenceContext(unitName="dq")
EntityManager em;
public long queryFinancialRecordsOfDept(String deptName, String companyName) {
String query = "SELECT d.records " +
"FROM Department d " +
"WHERE d.name = '" + deptName +
"' AND " +
" d.company.name = '" + companyName + "'";
return em.createQuery(query, Long.class).getSingleResult();
}
}
在上面的查询中,我们在查询字符串中包含了deptName和companyName值。 结果,每次调用queryFinancialRecordsOfDept方法时,都会生成一个新查询。 这个新查询很难缓存,因为变量几乎每次都使String唯一。 因此,如果您的应用程序中有很多动态查询,并且经常调用它们,那么您将需要解决性能问题。
上面编写的动态查询的第二个问题是实际的串联操作。 由于您使用的是简单的String串联,并且String是不可变的,因此JVM将生成许多String对象,其中大多数将最终被丢弃,并一直存在于您的内存中,直到发生下一个垃圾回收为止。 这又可能会影响您的应用程序的性能。
如上所述,动态查询的第三个问题是安全性。 例如,在上面的查询中,黑客可以轻松地输入companyName的值来更改查询以发挥自己的优势。 在应用程序中查找期望的查询比我们想象的要容易。 从应用程序进行的简单堆栈跟踪揭示了许多无法想象的东西。
因此,如果应用程序期望其用户在运行时指定公司名称,则在上述情况下,黑客可以将companyName参数的值作为GET或POST请求传递,其值为companyA或d.company.name = companyB :
"SELECT d.records " +
"FROM Department d " +
"WHERE d.name = 'deptA' +
"AND d.company.name = 'companyA'
OR d.company.name = 'companyB';
通过使用JPA的命名/位置参数功能,可以轻松地减少这种类型的安全风险。 命名参数可帮助我们在查询处理的后续阶段将值绑定到查询。 通过使用命名参数,查询不会每次都针对不同的参数而更改。 因此,查询保持不变,并且提供程序可以轻松地对其进行缓存。
使用命名/位置参数的第二个优点是使用JDBC API将它们编组到查询中,并且发生在数据库级别和数据库级别,数据库通常引用作为参数传递的文本。 因此,在上述情况下,我们可以更改查询以使用命名/位置参数:
方式1:命名参数
public long queryFinancialRecordsOfDept(String deptName, String companyName) {
String query = "SELECT d.records " +
"FROM Department d " +
"WHERE d.name = :deptName +
"AND d.company.name = :compName;
return em.createQuery(query,Long.class)
.setParameter("deptName" , deptName)
.setParameter("compName" , companyName)
.getSingleResult();
}
}
方式2:位置参数
public Long queryFinancialRecordsOfDept(String deptName, String companyName) {
String query = "SELECT d.records " +
"FROM Department d " +
"WHERE d.name = ?1 +
"AND d.company.name = ?2;
return em.createQuery(query,Long.class)
.setParameter(1 , deptName)
.setParameter(2 , companyName)
.getSingleResult();
}
}
指定参数的方式1使用命名变量,可以使用查询对象上的setParameter方法为其提供值。
指定参数的方式2使用数字或索引将查询参数绑定到查询字符串。
注意:我们可以在查询中多次使用相同的命名参数,但只需要使用setParameter函数将值绑定一次即可。
命名查询
命名查询是一种以更具可读性,可维护性和高性能的方式组织静态查询的方法。
JPA中的命名查询是使用@NamedQuery批注定义的。 此注释只能在类级别上应用,查询将在其上进行操作的实体是定义命名查询的好地方。 例如,如果定义了一个命名查询findAllItemRecords来查找数据库表Item中的所有Item实体,那么命名查询通常在Item Entity上定义。 这是一个例子:
@NamedQuery(name="Item.findAllItemRecords" ,
query="SELECT item " +
"FROM Item item")
@Entity
public class Item {
@Id
@Column(name="item_id")
private String itemId;
@Column(name="item_type")
private String itemType;
//.......
}
上面要注意的一件事是,我们在字符串上使用了串联操作。 但这不会像动态查询那样出现性能问题,因为持久性提供程序将在部署时将命名查询从JPQL转换为SQL,并将其缓存以备后用。 这意味着仅在部署时才感觉到使用串联的开销,而不是在应用程序每次使用查询时才感觉到。 像上面那样串联查询的好处是,它使查询更具可读性,因此更易于维护。
命名查询要记住的一件事是,查询的名称范围是整个持久性单元,因此,不可能有两个具有相同名称的命名查询。 您应该对每个命名查询使用限定符。 通常,使用实体名称(如我们在上面的示例中所做的那样),因为前缀是一个不错的选择。
我们可以使用@NamedQueries批注为给定实体定义多个NamedQueries。 让我们看一下指定多个命名查询的示例。
@NamedQueries({
@NamedQuery(name="Item.findAllItemRecords" ,
query="SELECT item " +
"FROM Item item "
"WHERE item.itemId=:itemId),
@NamedQuery(name="Item.findItemByType" ,
query="SELECT item " +
"FROM Item item "
"WHERE item.itemType=:itemType)
})
@Entity
public class Item {
@Id
@Column(name="item_id")
private String itemId;
@Column(name="item_type")
private String itemType;
//.......
}
我们可以在EntityManager上使用createNamedQuery方法在方法中使用命名查询。
public Item findAllItemRecords(String itemId) {
return em.createNamedQuery("Item.findAllItemRecords",
Item.class)
.setParameter("itemId", itemId)
.getSingleResult();
摘要
我们在这篇小博文中讨论了JPA中动态查询和命名查询之间的区别。 在下一篇博客文章中,我们将介绍Criteria API及其使用方式。
这篇博客文章的内容是阅读优秀书籍Pro JPA 2的结果 。 我会推荐给从事JPA相关项目的任何人。
参考: JPA 2 | 动态查询与 JavaWorld博客博客中的JCG合作伙伴 Anuj Kumar 命名的查询 。
翻译自: https://www.javacodegeeks.com/2013/06/jpa-2-dynamic-queries-vs-named-queries.html