JPQL版本:
public List<Employee> findEmployees(String name, String deptName, String projectName, String city) { StringBuffer query = new StringBuffer(); query.append("SELECT DISTINCT e "); query.append("FROM Employee e LEFT JOIN e.projects p "); query.append("WHERE "); List<String> criteria = new ArrayList<String>(); if (name != null) { criteria.add("e.name = :name"); } if (deptName != null) { criteria.add("e.dept.name = :dept"); } if (projectName != null) { criteria.add("p.name = :project"); } if (city != null) { criteria.add("e.address.city = :city"); } if (criteria.size() == 0) { throw new RuntimeException("no criteria"); } for (int i = 0; i < criteria.size(); i++) { if (i > 0) { query.append(" AND "); } query.append(criteria.get(i)); } Query q = em.createQuery(query.toString()); if (name != null) { q.setParameter("name", name); } if (deptName != null) { q.setParameter("dept", deptName); } if (projectName != null) { q.setParameter("project", projectName); } if (city != null) { q.setParameter("city", city); } return (List<Employee>)q.getResultList(); }JPQL没有什么好解析的.据说,使用CRITICAL API能避开JPQL字符串分析的系统开销和构建的各种条件选项.
public List<Employee> findEmployees(String name, String deptName, String projectName, String city) { //通过工厂方法创建一个CriteriaBuilder,它是一个核心,通过可以创建查询定义本身,CriteriaQuery实例 //以及查询定义的各种组件(如条件表达式) CriteriaBuilder cb = em.getCriteriaBuilder(); //创建一个条件查询实例,它只是一个空壳,定义查询结果类型 CriteriaQuery<Employee> c = cb.createQuery(Employee.class); //定义查询根,这里就是定义一个from子句. Root<Employee> emp = c.from(Employee.class); //定义一个select子句 c.select(emp); //去重 c.distinct(true); //创建联接 Join<Employee,Project> project = emp.join("projects", JoinType.LEFT); //以下四个if通过Predicate来构造条件表达式,为创建where子句作准备 List<Predicate> criteria = new ArrayList<Predicate>(); if (name != null) { ParameterExpression<String> p = cb.parameter(String.class, "name"); criteria.add(cb.equal(emp.get("name"), p)); } if (deptName != null) { ParameterExpression<String> p = cb.parameter(String.class, "dept"); criteria.add(cb.equal(emp.get("dept").get("name"), p)); } if (projectName != null) { ParameterExpression<String> p = cb.parameter(String.class, "project"); criteria.add(cb.equal(project.get("name"), p)); } if (city != null) { ParameterExpression<String> p = cb.parameter(String.class, "city"); criteria.add(cb.equal(emp.get("address").get("city"), p)); } //创建where子句 if (criteria.size() == 0) { throw new RuntimeException("no criteria"); } else if (criteria.size() == 1) { c.where(criteria.get(0)); } else { c.where(cb.and(criteria.toArray(new Predicate[0]))); } //为条件查询创建一个TypedQuery实例,与JPQL的区别主要在前面 TypedQuery<Employee> q = em.createQuery(c); //为查询占位符赋上参数,设置排序,分页等. if (name != null) { q.setParameter("name", name); } if (deptName != null) { q.setParameter("dept", deptName); } if (project != null) { q.setParameter("project", projectName); } if (city != null) { q.setParameter("city", city); } //执行查询 return q.getResultList(); }
上面的emp.get(),project.get()传入的都是实体字体的字符串类型,如果刚好改了这个字段名,这边忘了改,就会造成运行出错.如果使用强类型进行检查,在开发编译就会报错.这里使用工具自动生成Static Metamodel Classes. 使用了规范化元模型,在开发编译就会进行强类型检查,Google了一下,hibernate提供了工具:https://docs.jboss.org/hibernate/orm/5.0/topical/html/metamodelgen/MetamodelGenerator.html
用法:
1.在pom.xml加入依赖(版本根据你使用的hibernate版本而定)
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>CURRENT-VERSION</version> </dependency>2.如果使用maven进行编译,修改一下maven编译参数
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> <compilerArguments> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </compilerArguments> </configuration> </plugin>3.至于IDE工具配置,看一下上面的链接.
可能会这样的需求,一个关联查询,查询结果可能只取几个属性,并且属性不只是来自于一个实体,这时候可以像下面使用:
public List<Object[]> findAll(Long deptId, String deptName) { CriteriaBuilder cb = em.getCriteriaBuilder(); //创建一个条件查询实例,它只是一个空壳,定义查询结果类型 CriteriaQuery<Object[]> c = cb.createQuery(Object[].class); Root<Employee> emp = c.from(Employee.class); List<Predicate> criteria = new ArrayList<Predicate>(); Root<Department> dept = c.from(Department.class); criteria.add(cb.equal(dept.get(Department_.id),emp.get(Employee_.departmentId))); if (deptId!=null){ criteria.add(cb.equal(emp.get(Employee_.departmentId),deptId)); } if (StringUtils.hasLength(deptName)) { criteria.add(cb.equal(emp.get(Employee_.name),deptName)); } if (criteria.size()>0) { c.where(cb.and(criteria.toArray(new Predicate[criteria.size()]))); } //定义一个select子句 //c.select(cb.array(emp.get(Employee_.name),emp.get(Employee_.departmentId))); c.multiselect(dept.get(Department_.name),emp.get(Employee_.name),emp.get(Employee_.departmentId)); List<Object[]> resultList = em.createQuery(c).getResultList(); return resultList; }创建查询时传入Object[].class类型,定义select子句时,可以使用multiselect也可以像上面使用select.另一种方法是使用createTupleQuery()查询,只是返回结果不一样,定义select子句类似,在我看来,两者没什么特别的区别.这种方式对应JPQL查法的javax.persistence.EntityManager#createQuery(java.lang.String),同样用 List<Object[]>接收结果就可以了 .还有,很遗憾,这种查询在JpaSpecificationExecutor不行.因为org.springframework.data.jpa.domain.Specification#toPredicate的参数CriteriaQuery<?> query,已经固定泛型是?
如果使用spring data jpa,可通过org.springframework.data.jpa.repository.JpaSpecificationExecutor的几个接口方法来实现.
public interface JpaSpecificationExecutor<T> { T findOne(Specification<T> spec); List<T> findAll(Specification<T> spec); Page<T> findAll(Specification<T> spec, Pageable pageable); List<T> findAll(Specification<T> spec, Sort sort); long count(Specification<T> spec); }这几个方法最主要是传入一个org.springframework.data.jpa.domain.Specification实例.它是一个接口,并且只有一个接口方法,将它理解成一个回调.
返回是Predicate类型,如果返回null,在这方法内使用query.where()来应用Predicate为创建where子句作准备也可以.
下面是一个例子:
public Page<User> find(final String departmentId,final String username,Pageable pageable){ return userRepository.findAll(new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { criteriaQuery.distinct(true); List<Predicate> criteria=new ArrayList<Predicate>(); if (!StringUtils.isEmpty(departmentId)){ criteria.add(criteriaBuilder.equal(root.<String>get("departmentId"), departmentId)); } if (!StringUtils.isEmpty(username)){ criteria.add(criteriaBuilder.equal(root.<String>get("username"), username)); } Root<Department> departmentRoot = criteriaQuery.from(Department.class); criteria.add(criteriaBuilder.equal(root.<String>get("departmentId"), departmentRoot.<String>get("id"))); Predicate result=null; if (criteria.size()>0) { result=criteriaBuilder.and(criteria.toArray(new Predicate[0])); } return result; } },pageable); }在这个例子里,实体User的departmentId与实体Department的id相关联,但在实体User并没有使用@ManyToOne注解.一样像原生sql通过where子句进行限制.所以在上面最后添加了一个Predicate.通过userService.find("1","username",null);在mysql生成的sql语句如下:
select distinct user0_.id as id1_1_, user0_.age as age2_1_, user0_.departmentId as departme3_1_, user0_.password as password4_1_, user0_.username as username5_1_ from o_user user0_ cross join o_department department1_ where user0_.departmentId=? and user0_.username=? and user0_.departmentId=department1_.id
我遇到的一个缺陷,有的Predicate能创建参数绑定,有的不能.比如下面两个Predicate
cb.equal(root.<String>get("orderId"), orderId)
cb.equal(root.<Byte>get("status"), ordStatus)
org.hibernate.jpa.criteria.expression.LiteralExpression#render方法如下:
public String render(RenderingContext renderingContext) { if ( ValueHandlerFactory.isNumeric( literal ) ) { return ValueHandlerFactory.determineAppropriateHandler( (Class) literal.getClass() ).render( literal ); } // else... final String parameterName = renderingContext.registerLiteralParameterBinding( getLiteral(), getJavaType() ); return ':' + parameterName; }第二个Predicate,因为Byte是数值类型,就直接返回值了,并没有创建参数绑定,这样的SQL效率会更高?不清楚哈!