尽管目前&nbps;的趋势 ORM框架,使用JPA或Hibernate都没有任何问题。 我必须承认我的用例非常简单。 另外,由于那是几年前的事,我们将这些应用程序托管在我们自己的现场基础架构上,因此,寻求最终性能及其相关的节省并不是目标。 为不熟悉许多技术的初级开发人员赋权。
有趣的是,Hibernate实际上早于JPA,因此该实现具有比规范更多的功能:在JPA 1.0中非常明显,而在2.0中则更少。 然而,无论差距多么小,它都存在。 遗憾的是,因为Hibernate在JPA无法匹配的某些领域提供了非常好的API。 这样的领域之一就是查询,它的关联功能是按示例查询。 这篇文章专门描述这种用例的上下文,以及如何 QBE有助于轻松解决它。
一个简单的搜索表
想象一个Person
实体,它具有几个属性, 例如名字,姓氏,生日等。现在,想象一个允许搜索Person
实体的表单以及每个“ public”属性的搜索字段。
这是对商业应用程序相当普遍的要求。
让我们看看如何使用以下方法实现:
用JPQL查询
回到JPA 1.0, JPQL 是执行查询的唯一选项。 在当前版本(2.1)之前仍然可用,并且大量借鉴了SQL语法。
例如,从PERSON
表中的所有实体中选择所有字段将转换为以下JPQL查询:
SELECT p FROM Person p
添加搜索条件同样与SQL非常相似:
SELECT p FROM Person p WHERE firstName = 'Doe'
使用JPQL实施以前的搜索表单需要:
- 检查每个搜索字段是否已填写
- 如果是,则创建where部分
- 并最终以连接所有地方谓词
评估结果如下:
@WebServlet("/jpql")
classJpqlServlet:HttpServlet(){
overridefundoPost(req:HttpServletRequest,resp:HttpServletResponse){
doAndDispatch(req,resp){firstName,lastName,birthdate,em-> (1)
valselect="SELECT p FROM Person p"
valjpql=if(firstName.isBlank() (2)
&&lastName.isBlank()
&&birthdate==null)select
else{
valwhere="$select WHERE" (3)
varexpression=where
if(firstName.isNotBlank())
expression+=" firstName = '$firstName'" (4)
if(lastName.isNotBlank()){
if(expression!=where) (5)
expression+=" AND" (6)
expression+=" lastName = '$lastName'"
}
if(birthdate!=null){
if(expression!=where)
expression+=" AND"
expression+=" birthdate = '$birthdate'"
}
expression
}
valcq=em.createQuery(jpql)
cq.resultList
}
}
}
- 获取请求参数,公开JPA实体管理器并将人员列表设置为显示页面的请求属性的通用逻辑
- 如果未设置任何条件,则查询字符串只是一个简单的
SELECT
,没有WHERE
子句 - 相反,如果已设置至少一个条件,请添加
WHERE
子句 - 将属性的名称和值添加到
WHERE
子句 - 检查是否设置了先前的条件
- 如果已设置先前的条件,请附加
AND
子句
对于好奇的读者,这是doAndDispatch()
函数的代码。 它还将用于替代实现中。
privatefundoAndDispatch(req:HttpServletRequest,
resp:HttpServletResponse,
f:(String,String,LocalDate?,EntityManager)->List<*>){
funString.toLocaleDate():LocalDate?=if(this.isBlank())null
elseLocalDate.parse(this)
valfirstName=req.getParameter("firstName")
vallastName=req.getParameter("lastName")
valbirthdate=req.getParameter("birthdate")?.toLocaleDate()
valem=Persistence.emf.createEntityManager()
valpersons=f(firstName,lastName,birthdate,em)
req.setAttribute("persons",persons)
req.setAttribute("firstName",firstName)
req.setAttribute("lastName",lastName)
req.setAttribute("birthdate",birthdate)
req.getRequestDispatcher("/WEB-INF/persons.jsp").forward(req,resp)
}
标准API
显然,在此用例中使用JPQL很复杂且容易出错:这是非类型安全API( 例如字符串连接)的问题。 为了解决这个问题,JPA 2.0引入了Criteria API,它提供了一种强类型的API。
上面的SELECT
查询可以替换为以下内容:
valcq=em.criteriaBuilder.createQuery(Person::class.java)
cq.from(Person::class.java)
valtypedQuery=em.createQuery(cq)
typedQuery.resultList
尽管实际上没有WHERE
子句的查询需要更多代码,但在WHERE
,Criteria API会更好:
valcq=em.criteriaBuilder.createQuery(Person::class.java)
valperson=cq.from(Person::class.java)
cq.where(em.criteriaBuilder.equal(person.get<String>("lastName"),"Doe"))
valtypedQuery=em.createQuery(cq)
typedQuery.resultList
关于搜索表单用例,可以看到使用Criteria API的好处。 评估字段是否已填充的逻辑保持不变,但是添加条件变得容易得多:
doAndDispatch(req,resp){firstName,lastName,birthdate,em->
valcq=em.criteriaBuilder.createQuery(Person::class.java)
valperson=cq.from(Person::class.java)
varpredicates=listOf<Predicate>()
if(firstName.isNotBlank())
predicates=predicates+
em.criteriaBuilder.equal(person.get<String>("firstName"),firstName)
if(lastName.isNotBlank())
predicates=predicates+
em.criteriaBuilder.equal(person.get<String>("lastName"),lastName)
if(birthdate!=null)
predicates=predicates+
em.criteriaBuilder.equal(person.get<LocalDate>("birthdate"),birthdate)
cq.where(*predicates.toTypedArray())
valquery=em.createQuery(cq)
query.resultList
}
为了实施更强的键入,可以生成JPA元模型。 由于规范未涵盖元模型的生成,因此请检查您的ORM实现的文档。
超越JPA,按示例查询功能
在Hibernate 4.x中,一个名为Query-By-Example的漂亮功能非常适合搜索用例。 要使用它只是一个问题:
- 创建实体的实例
- 对于每个已填写的字段,填写相应的属性
- 实例化
- 当然执行查询
相应的代码如下所示:
doAndDispatch(req,resp){firstName,lastName,birthdate,em->
valsession=em.delegateasSession (1)
valperson=Person(firstName=if(firstName.isBlank())nullelsefirstName, (2)
lastName=if(lastName.isBlank())nullelselastName,
birthdate=birthdate)
valexample=Example.create(person) (3)
valcriteria=session.createCriteria(Person::class.java).add(example)
criteria.list()
}
-
EntityManager
需要强制转换为专有的Hibernate的Session
,因为只有后者才提供QBE功能 -
null
表示该属性将不属于WHERE
子句。 因此,需要将空字符串设置为null
- 从实体创建示例
请注意,尽管QBE非常强大,但也有一些限制。 如上所示,需要将空字段设置为null
。 因此,由于该示例位于Kotlin中,因此实体类属性必须从不可为null的类型更新为可为null的类型。
@Entity
classPerson(@Id@GeneratedValuevarid:Long?=null,
valfirstName:String?,
vallastName:String?,
valbirthdate:LocalDate?)
或者,可以仅出于QBE的目的创建专用的类。
如果有更多要求,那么总是有可能退回到上面提到的Criteria API。 就我所知,QBE做一件事,但是做得很好。
结论
如本文所见,在实体上执行简单查询时,QBE是一项非常有用的功能。 当我最近发现Hibernate 5.x不推荐使用此功能时,我感到非常惊讶! 根据我的读物,其理由是,由于团队规模不够大,因此实现JPA规范比提供专有功能要好得多,而不管其价值如何。
QBE是否可以用于下一个JPA版本还有待观察。 恕我直言,不添加它-或将其从Hibernate中完全删除将是一个耻辱。