公司里需要用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]+$' ";
}
}
附上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的使用是没有问题的,有些理解可能还不对,希望以后能再纠正。