JPA的CRITICAL API

如果问我,CRITICAL API和JPQL相比,有什么优点,我只能答,可以拿出来装...哈哈!了解它,我觉得一个主要目的,可以看明白别人写的代码.CRITICAL API是一个构建查询的编程API.下面以构建动态查询为例.

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字符串分析的系统开销和构建的各种条件选项.


CRITICAL API版本:
    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 toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
这个方法的作用,通过Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb三者,为一个引用的实例创建一个where子句.

返回是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效率会更高?不清楚哈!



你可能感兴趣的:(JPA的CRITICAL API)