欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java for Web学习笔记(一二零):搜索(2)JPA的动态条件搜索(中)

程序员文章站 2022-04-22 13:38:22
...

通用的检索

定义可以获得Predicate[]的SearchCriteria

SearchCriteria: 单个条件Criterion的集合

SearchCriteria是单个条件Criterion的集合List<Criterion>

/*代码很简单,也很有趣。由于List<T>是一个接口,必须有具体的实现,例子采用了ArrayList<T>,但我们希望只开放List接口的方法。*/
public interface SearchCriteria extends List<Criterion>{
    public static class Builder {
        public static SearchCriteria create(){
            return new SearchCriteriaImpl();
        }

        private static class SearchCriteriaImpl extends ArrayList<Criterion> implements SearchCriteria{
            private static final long serialVersionUID = 1L;
        }
    }
}

单个条件Criterion

public class Criterion {
    //【1】单个条件有三个有要素:
    // 1.1)对象的那个属性(对应数据库表格哪个列)
    private final String propertyName;
    // 1.2)是什么操作,等于,大于还是什么。这个操作在Enum Operator中定义,具体见后面
    private final Operator operator;
    // 1.3)和什么对比,也就是等于大于什么。因为该对象属性的类型不知道,所以采用了Object
    private final Object compareTo;
    
    public Criterion(String propertyName, Operator operator, Object compareTo) {
        this.propertyName = propertyName;
        this.operator = operator;
        this.compareTo = compareTo;
    }
    
    public String getPropertyName() {
        return propertyName;
    }
	
    public Operator getOperator() {
        return operator;
    }
	
    public Object getCompareTo() {
        return compareTo;
    }	

    //【2】具体定义操作,包括EQ,NEQ,LT等等。比较特别地,我们也同时给出了这些操作如何转换为JPA中的Predicate。
    // 这种在enum中带有方法的处理,我们以往很少见到。
    public static enum Operator{
        EQ{ //【3.1】这是判断相等的操作。注意:我们这里不是比较object的地址,而是判断值是否一样,因此需要确保对象是Compatable的。我们通过一个静态方法getComparable()来实现,如果非compatable的,直接抛出异常。
            @Override
            public Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b) {
                return b.equal(r.get(c.propertyName), getComparable(c));
            }			
        }, NEQ {  ...// 如上类似 b.notEqual(r.get(c.getPropertyName()), getComparable(c));  
        }, LT {//【3.2】原则上lessThan和equal没有什么区别,但是在输入参数类型上有要求。如果我们给出一个通用的Compatable<?>,出现报错,修订为下面的方式。
            @Override
            @SuppressWarnings({ "rawtypes", "unchecked" })
            public Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b) {
                return b.lessThan(r.<Comparable>get(c.getPropertyName()), getComparable(c));
            }
        }, LTE {  ...// 如上类似 b.lessThanOrEqualTo(r.<Comparable>get(c.getPropertyName()), getComparable(c)) 
        }, GT {   ...// 如上类似 b.greaterThan(r.<Comparable>get(c.getPropertyName()), getComparable(c)) 
        }, GTE {  ...// 如上类似 b.greaterThanOrEqualTo(r.<Comparable>get(c.getPropertyName()), getComparable(c)) 
        }, LIKE { //【3.3】很显然LIKE只能是String,例如"%123"这类,使用getString()来获取,如果不是String,直接抛出异常
           @Override
           public Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b) {
               return b.like(r.get(c.getPropertyName()), getString(c));
           }
        }, NOT_LIKE { ...// 如上类似 b.notLike(r.get(c.getPropertyName()), getString(c)) 
        }, IN { //【3.4】IN的情况是一个Collection,进行类似的处理,只是直接写进来
           @Override
           public Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b) {
               Object o = c.getCompareTo();
               if (o == null)
                   return r.get(c.getPropertyName()).in();
               if (o instanceof Collection)
                   return r.get(c.getPropertyName()).in((Collection<?>) o);
               throw new IllegalArgumentException(c.getPropertyName());
           }
        }, NOT_IN { 
            @Override
            public Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b) {
                Object o = c.getCompareTo();
                if (o == null)
                    return b.not(r.get(c.getPropertyName()).in());
                if (o instanceof Collection)
                    return b.not(r.get(c.getPropertyName()).in((Collection<?>) o));
                throw new IllegalArgumentException(c.getPropertyName());
            }
        }, NULL {
            @Override
            public Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b) {
                return r.get(c.getPropertyName()).isNull();
            }
        }, NOT_NULL {  ...// 如上类似 r.get(c.getPropertyName()).isNotNull()
        };
        
        //【3】注意这种写法,为Enum里面增加方法	
        public abstract Predicate toPredicate(Criterion c, Root<?> r, CriteriaBuilder b);
		
        @SuppressWarnings("rawtypes")
        private static Comparable getComparable(Criterion c){
            Object o = c.getCompareTo();
            if(o != null && !(o instanceof Comparable<?>))
                throw new IllegalArgumentException(c.getPropertyName());
            return (Comparable<?>) o;
        }
		
        private static String getString(Criterion c){
            if(!(c.getCompareTo() instanceof String))
                throw new IllegalArgumentException(c.getPropertyName());
            return (String)c.getCompareTo();
        }
    }
}

提供复合条件

AbstractSearchableJpaRepository是我们提供的通用检索仓库,在后面介绍。

class AbstractSearchableJpaRepository{
    ....
    private static Predicate[] toPredicates(SearchCriteria criteria, Root<?> root,CriteriaBuilder builder){
        Predicate[] predicates = new Predicate[criteria.size()];
        int i =0;
        for(Criterion c : criteria)
            predicates[i++] = c.getOperator().toPredicate(c, root, builder);
        return predicates;
    }
}

通用仓库

AbstractDomainClassAwareRepository :提供T的类型获取

这种方式,在之前我们写通用的CRUD时已经学过,复习一下
abstract class AbstractDomainClassAwareRepository<T> {
	protected final Class<T> domainClass;
	
	@SuppressWarnings("unchecked")
	protected AbstractDomainClassAwareRepository(){
		Type genericSuperclass = this.getClass().getGenericSuperclass();
		while(!(genericSuperclass instanceof ParameterizedType)){
			if(!(genericSuperclass instanceof Class))
				throw new IllegalStateException("Unable to determine type " +
						"arguments because generic superclass neither " +
						"parameterized type nor class.");
			if(genericSuperclass == AbstractDomainClassAwareRepository.class)
				throw new IllegalStateException("Unable to determine type " +
						"arguments because no parameterized generic superclass " +
						"found.");
			genericSuperclass = ((Class<T>)genericSuperclass).getGenericSuperclass();
		}
		ParameterizedType type = (ParameterizedType)genericSuperclass;
		Type[] arguments = type.getActualTypeArguments();
		this.domainClass = (Class<T>)arguments[0];
	}
}

SearchableRepository:通用多条件检索接口定义

public interface SearchableRepository<T> {
	Page<T>	search(SearchCriteria criteria, Pageable pageable);
}

AbstractSearchableJpaRepository:通用多条件检索实现

abstract class AbstractSearchableJpaRepository<T> extends AbstractDomainClassAwareRepository<T>
	implements SearchableRepository<T>{
	
	@PersistenceContext protected EntityManager entityManager;

	@Override
	public Page<T> search(SearchCriteria criteria, Pageable pageable) {
		CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
		//根据Page<T>的实现PageImpl<>,需要有总数和响应范围的List,所有需要查询两次
		// 查询了总个数
		CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class);
		Root<T> countRoot = countCriteria.from(this.domainClass);
		long total = this.entityManager.createQuery(
				countCriteria.select(builder.count(countRoot))
				.where(toPredicates(criteria, countRoot, builder))
						).getSingleResult();
		// 查询相关页的list
		CriteriaQuery<T> pageCriteria = builder.createQuery(this.domainClass);
		Root<T> pageRoot = pageCriteria.from(this.domainClass);
		List<T> list = this.entityManager.createQuery(
				pageCriteria.select(pageRoot)
				.where(toPredicates(criteria, pageRoot, builder))
				//QueryUtils.toOrders: turns a Spring Data Sort into JPA ordering instructions
				.orderBy(QueryUtils.toOrders(pageable.getSort(), pageRoot, builder))
				     ).setFirstResult(pageable.getOffset())
				      .setMaxResults(pageable.getPageSize())
				      .getResultList();		
		
		return new PageImpl<>(new ArrayList<>(list), pageable, total);
	}

	private static Predicate[] toPredicates(SearchCriteria criteria, Root<?> root,CriteriaBuilder builder){ .... ... }
}
相关链接:我的Professional Java for Web Applications相关文章