Hibernate的示例查询

尽管目前&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
    }
  }
}
  1. 获取请求参数,公开JPA实体管理器并将人员列表设置为显示页面的请求属性的通用逻辑
  2. 如果未设置任何条件,则查询字符串只是一个简单的SELECT ,没有WHERE子句
  3. 相反,如果已设置至少一个条件,请添加WHERE子句
  4. 将属性的名称和值添加到WHERE子句
  5. 检查是否设置了先前的条件
  6. 如果已设置先前的条件,请附加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的漂亮功能非常适合搜索用例。 要使用它只是一个问题:

  1. 创建实体的实例
  2. 对于每个已填写的字段,填写相应的属性
  3. 实例化
  4. 当然执行查询

相应的代码如下所示:

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()
}
  1. EntityManager需要强制转换为专有的Hibernate的Session ,因为只有后者才提供QBE功能
  2. null表示该属性将不属于WHERE子句。 因此,需要将空字符串设置为null
  3. 从实体创建示例

请注意,尽管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中完全删除将是一个耻辱。

可以在Github上以Maven格式找到此文章的完整源代码。

翻译自: https://blog.frankel.ch/hibernate-query-by-example/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值