JPA Specification 的写法



公司里需要用Specification的pattern写一个比较通用的查询,之前对于JPA的了解仅限于继承JPARepository接口,就能实现基本的查询,又或者加上@Query标签@Modify标签基本能实现所有的增删改查了。


之前看资料的时候也有看到一些对于Specification的描述和实践,那个时候一眼看过去,哎呀妈呀,整啥玩意呢,这么复杂看到。果然人对于未知的事物总是有抵触心理的,不到万不得已不会去了解他。


这次我就来好好了解他。


----------------------------------------------------------------------------------------------邪恶的分割线---------------------------------------------


了解了他其实还是很简单的呢。


要实现的功能是把表里面某个field符合prefix的都找出来,并且prefix-XXXX里面XXXXX必须是数字,不能是字母,里面最大的一条取出来。


一说到是数字,不能是字母,大家一定第一个反应就是用正则表达式吧。


话不多说,附上代码先。


@SuppressWarnings("unchecked")
    @Transactional
    public  T getMaxStringFieldValue(final String field, final String prefix, final Class doClass){
        
        Repository rep = (Repository) metadata.getRepository(doClass);
        
        Specification s = new Specification(){

            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {

                //first layer subquery begin
		//创建子查询并声明返回类型是String类型
                Subquery firstLayerSq = query.subquery(String.class);
		//声明子查询的对象
                Root rootFirstLayerSq = ((Root) firstLayerSq.from(doClass));
		//根据域名获得该域的数据集(不知道用这个词对不对)
                Path stringFieldFirstLayerSq = rootFirstLayerSq.get(field);
		//通过CriteriaBuilder的length函数(对应SQL的length函数)获得该数据集的所有数据的字符串长度
                Expression lenExpFirstLayerSq = cb.length(stringFieldFirstLayerSq);
                
                //second layer subquery begin
		//子查询里再开一个子查询,就跟SQL语句一样
                Subquery secondLayerSq = firstLayerSq.subquery(Integer.class);
                Root rootSecondLayerSq = (Root) secondLayerSq.from(doClass);
                Path stringFieldSecondLayerSq = rootSecondLayerSq.get(field);
		//声明所有字符串长度中最长的长度是多少
                Expression maxLenExpSecondLayerSq = cb.max(cb.length(stringFieldSecondLayerSq));
                //构造like的关键字
                StringBuilder sb = new StringBuilder(prefix);
                sb.append("-%");
                String pattern = sb.toString();
                //redesign...
		//这里是个关键点,因为Specification本身不支持Regular Expression,还好这个项目用的是Hibernate,可以自定义DiaLect并注册函数
		//这里我注册了regexp函数
                Expression fitRegPredicate = cb.function("regexp", Boolean.class, cb.substring(stringFieldSecondLayerSq, prefix.length()+2));
                //这里也是一个关键点,之前弄了半天怎么都是错,就是没有加cb.isTrue方法,CriteriaBuilder.function最终转化成功需要CriteriaBuilder的一些
		//判断结果的函数来执行,这里跟我先前写好的sql语句有点出入,这也是我为什么搞了半天搞不出来的原因
		//这里通过CriteriaBuilder的and函数把两个判断条件链接起来,一个是like根据prefix筛选,一个是根据XXX部分一定是数字来筛选
                Predicate fitPrefixStringFieldPredicate = cb.and(cb.like(stringFieldSecondLayerSq, pattern),cb.isTrue(fitRegPredicate));
                //这里把需要的结果注入到select方法里,把判断条件注入到where里面
                secondLayerSq.select(maxLenExpSecondLayerSq).where(fitPrefixStringFieldPredicate);
              //second layer subquery end
		//把第一个子查询的field长度与最长长度比较
                Predicate stringFieldLenPredicate = cb.equal(lenExpFirstLayerSq, secondLayerSq);
		//这里又用一个greatest函数,来比较字符串的大小,这里有个坑啊!cb.greatest函数对应sql的max函数,
		//而不是sql的greatest...亲测(数据库是PostgreSQL)
                Expression maxStringFieldExpFirstLayerSq = cb.greatest(stringFieldFirstLayerSq);
                
                firstLayerSq.select(maxStringFieldExpFirstLayerSq).where(stringFieldLenPredicate);
                
                Predicate equalsFieldValuePredicate = cb.equal(root.get(field), firstLayerSq);
                //first layer subquery end
                return cb.and(equalsFieldValuePredicate);
                
            }
            
        };
        
        T t = rep.findOne(s);
        
        return t;
        
    }




下面的是自定义的DiaLect


@Component
public class CustomPostgreSQLDialect extends PostgreSQL9Dialect{
    
    public CustomPostgreSQLDialect(){
        
        super();
        registerFunction("regexp", new RegularFunction());
        
    }
}

public class RegularFunction implements SQLFunction{

    @Override
    public boolean hasArguments() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean hasParenthesesIfNoArguments() {
        // TODO Auto-generated method stub
        return true;
    }

    @Override
    public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException {
        // TODO Auto-generated method stub
        return StandardBasicTypes.BOOLEAN;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public String render(Type firstArgumentType, List arguments, SessionFactoryImplementor factory)
            throws QueryException {
        // TODO Auto-generated method stub
        
        if(arguments.size() != 1){
            throw new QueryException(new IllegalArgumentException(
                    "regular_function should be one arg"));
        }
        
        return arguments.get(0)+" ~ '^[0-9]+$' ";

    }

}


最后记得改一下datasource的property


附上SQL语句:

select * from table where name = (select max(name) from table where length(name) = (select max(length(name)) from table where name like 'prefix-%' and substr(name,10) !~* '[a-z]'))


写完这篇文章,突然发现我的功能有问题,有个Bug。。。。。。。。。。。。。。。。。。。。。。


对于Specification的使用是没有问题的,有些理解可能还不对,希望以后能再纠正。


你可能感兴趣的:(JPA Specification 的写法)