上篇文章我们分享了下Spring-data-jpa的基本使用方法,但在实际使用中可能会有更复杂的用法,现在我们来看看他的更高级玩法,并做了完美的封装,使用起来更加智能简单。Spring-data-jpa为我们提供了JpaSpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的查询,下面就来看看具体如何使用。
首先我们来看看JpaSpecificationExecutor这个类
public interface JpaSpecificationExecutor {
T findOne(Specification var1);
List findAll(Specification var1);
Page findAll(Specification var1, Pageable var2);
List findAll(Specification var1, Sort var2);
long count(Specification var1);
}
可以看出他是个接口,里面提供了基本的使用方法,其中重要的一个参数是Specification
public interface Specification {
Predicate toPredicate(Root var1, CriteriaQuery> var2, CriteriaBuilder var3);
}
发现他也是一个接口,并且只有一个方法,因此这个方法就是我们查询的关键所在,实际他就是向我们提供的构建查询条件的关键接口,我们只要实现他的方法按照JPA 2.0 criteria api写好查询条件就可以了,接下来看看具体如何来怎么使用
由上一篇我么知道使用Spring-data-jpa我们需要继承JpaRepository接口,为了做复杂查询还需继承JpaSpecificationExecutor接口,因此我们可以建一个BaseRepository类来同时继承这两个类
/**
* 基类的数据访问接口(继承了CrudRepository,PagingAndSortingRepository,
* JpaSpecificationExecutor的特性)
*
* Created by mj on 2017/12/17.
*/
@NoRepositoryBean
public abstract interface BaseRepository extends JpaRepository, JpaSpecificationExecutor {
/**
*
* 使用QBL进行查询列表
*
* @author mj 2016年10月26日
* @param query
* @return
*/
public abstract List findAll(BaseQuery query);
/**
*
* 封装分页查询
*
* @author mj 2016年10月26日
* @param query
* @param pageable
* @return
*/
public abstract Page findAll(BaseQuery query, Pageable pageable);
/**
*
* 封装排序查询
*
* @author mj 2016年10月26日
* @param query
* @param sort
* @return
*/
public abstract List findAll(BaseQuery query, Sort sort);
/**
*
* 使用QBL定位记录
*
* @author mj 2016年10月26日
* @param query
* @return
*/
public abstract T findOne(BaseQuery query);
/**
*
* 更新方法
*
* @author mj 2016年10月26日
* @param t
* @param updateFileds
* @param where
* @return
*/
public abstract int update(T t, BaseQuery where, String... updateFileds);
/**
*
* 根据唯一主键更新方法
*
* @author mj 2016年10月26日
* @param t
* @param id
* @param updateFileds
* @return
*/
public abstract int updateById(T t, ID id, String... updateFileds);
}
其中里面的一些方法暂时不用关心是对jpa的一些功能扩展加强后面会讲到,然后我们来构建查询条件,通过以上讲述我们只需创建 一个类来实现Specification
/**
* 自定义Query语言转Specification
*
* @version
* @author mj 2016年10月26日 下午3:46:50
* @param
*
*/
public class QueryToSpecification implements Specification {
private BaseQuery query;
public QueryToSpecification(BaseQuery query) {
super();
this.query = query;
}
/**
* 【请在此输入描述文字】
*
* (non-Javadoc)
* @see Specification#toPredicate(Root, CriteriaQuery, CriteriaBuilder)
*/
@Override
public Predicate toPredicate(Root root, CriteriaQuery cquery, CriteriaBuilder cb) {
return BaseQueryPredicateBuilder.getPredicate(root, cb, cquery,this.query);
}
}
由此可看出我们实现了Specification
/**
* query转换builder类
*
* @version
* @author mj 2016年10月26日 下午3:54:41
*
*/
public class BaseQueryPredicateBuilder {
private static Logger log = LogManager.getLogger(BaseQueryPredicateBuilder.class);
public static Predicate getPredicate2(Root root, CriteriaBuilder cb, BaseQuery query) {
return getPredicate(root, cb, null, query);
}
public static Predicate getPredicate(Root root, CriteriaBuilder cb, CriteriaQuery cquery,
BaseQuery query) {
List predicatesAnd = new ArrayList();
try {
Class> entityClass = queryEntity(query);
if (entityClass == null) {
// 是否返回NULL,待研究
return null;
}
BeanInfo beanInfo = Introspector.getBeanInfo(query.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
Method readMethod = pd.getReadMethod();
if ((pd.getName().indexOf("Page") == 0) || (pd.getName().indexOf("Sort") == 0)) {
continue;
}
if (!pd.getName().equals("class")) {
Object obj = readMethod.invoke(query);
if (obj != null) {
QBindAttrField fieldProp = (QBindAttrField) getBindFieldName(query, pd.getName());
String bindAttrName = fieldProp.fieldName();
if (bindAttrName == null) {
// 查询字段名称默认等于属性名称
bindAttrName = pd.getName();
}
Path> from = root;
Expression expression = from.get(bindAttrName);
switch (fieldProp.where()) {
case equal:
predicatesAnd.add(cb.equal(expression, obj));
break;
case greaterThanOrEqualTo:
predicatesAnd.add(cb.greaterThanOrEqualTo(expression, (Comparable) obj));
break;
case lessThanOrEqualTo:
predicatesAnd.add(cb.lessThanOrEqualTo(expression, (Comparable) obj));
break;
case like:
predicatesAnd.add(cb.like(expression, "%" + (Comparable) obj + "%"));
break;
case greaterThan:
predicatesAnd.add(cb.greaterThan(expression, (Comparable) obj));
break;
case lessThan:
predicatesAnd.add(cb.lessThan(expression, (Comparable) obj));
break;
case notEqual:
predicatesAnd.add(cb.notEqual(expression, (Comparable) obj));
break;
case in:
if (pd.getPropertyType().getName().indexOf("List") > 0) {
List> value = (List>) obj;
if (value.size() == 0) {
// 防止生成LIST时,没有传入值,而查询条件会做全查处理,此处做特殊处理返回空条件
((List>) obj).add(null);
}
if (value.size() > 20) {
Set
这就是我们封装的查询条件逻辑,代码这里就不解释了大家可以自己看看很简单,但需要注意的是这里需要两个自定义注解QBindAttrField,QBindEntity和Where条件枚举类关键字,我们也创建一下,他们的功能就是用来映射后面查询需要的实体类和属性
/**
* 查询绑定属性
*
* @version
* @author mj 2016年10月28日 下午6:20:57
*
*/
@Target({java.lang.annotation.ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface QBindAttrField {
public abstract String fieldName();
public abstract Where where();
}
/**
* 查询绑定实体类
*
* @version
* @author mj 2016年10月26日 下午6:18:03
*
*/
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface QBindEntity {
public abstract Class> entityClass();
}
/**
* 目前系统支持的where查询条件关键字
*
* @version
* @author mj 2016年10月26日 下午6:40:05
*
*/
public enum Where {
in,
like,
equal,
notEqual,
lessThan,
lessThanOrEqualTo,
greaterThan,
greaterThanOrEqualTo
}
接下来我们来实现上面提到的对jpa功能的扩展加强实现里面的方法,创建BaseSimpleJpaRepositoryEx类实现SimpleJpaRepository和BaseRepository接口
/**
* 扩展对JAP功能加强
*
* @version
* @author liuyi 2016年4月16日 下午2:40:16
*
*/
@Transactional(readOnly=false,propagation=Propagation.SUPPORTS)
@NoRepositoryBean
public class BaseSimpleJpaRepositoryEx
extends SimpleJpaRepository implements BaseRepository {
private EntityManager baseEm;
private JpaEntityInformation baseEmInfo;
/**
* BaseSimpleJpaRepositoryEx 构造器
* @param domainClass
* @param entityManager
*/
public BaseSimpleJpaRepositoryEx(Class domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
baseEm = entityManager;
// baseEmInfo = entityInformation;
}
/**
* 自定义查询条件转换实现
*
* (non-Javadoc)
*/
private Specification getConditonByQuery(BaseQuery query) {
return new QueryToSpecification(query);
}
/**
* 封装自定义组合查询列表方法
*
* (non-Javadoc)
*/
@SuppressWarnings("unchecked")
@Override
public List findAll(BaseQuery query) {
if(query.getSort()!=null){
return findAll(getConditonByQuery(query), query.getSort());
}
else if(query.getPage()!=null){
return (List) findAll(getConditonByQuery(query),query.getPage());
}else{
return (List) findAll(getConditonByQuery(query));
}
}
/**
*
* 自定义组合查询分页方法
*
* @author liuyi 2016年4月18日
* @param query
* @param pageable
* @return
*/
@Override
public Page findAll(BaseQuery query,Pageable pageable) {
return findAll(getConditonByQuery(query), pageable);
}
/**
* 查询条件
*
* (non-Javadoc)
*/
@Override
public T findOne(BaseQuery query) {
return findOne(getConditonByQuery(query));
}
@Override
public List findAll() {
return super.findAll();
}
@Override
protected CrudMethodMetadata getRepositoryMethodMetadata() {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getRepositoryMethodMetadata();
}
@Override
protected Class getDomainClass() {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getDomainClass();
}
@Override
public T findOne(ID id) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findOne(id);
}
@Override
protected Map getQueryHints() {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getQueryHints();
}
@Override
public T getOne(ID id) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getOne(id);
}
@Override
public boolean exists(ID id) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.exists(id);
}
@Override
public List findAll(Iterable ids) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(ids);
}
@Override
public List findAll(Sort sort) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(sort);
}
@Override
public Page findAll(Pageable pageable) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(pageable);
}
@Override
public T findOne(Specification spec) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findOne(spec);
}
@Override
public List findAll(Specification spec) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(spec);
}
@Override
public Page findAll(Specification spec, Pageable pageable) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(spec, pageable);
}
@Override
public List findAll(Specification spec, Sort sort) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(spec, sort);
}
@Override
public S findOne(Example example) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findOne(example);
}
@Override
public long count(Example example) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.count(example);
}
@Override
public boolean exists(Example example) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.exists(example);
}
@Override
public List findAll(Example example) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(example);
}
@Override
public List findAll(Example example, Sort sort) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(example, sort);
}
@Override
public Page findAll(Example example, Pageable pageable) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.findAll(example, pageable);
}
@Override
public long count() {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.count();
}
@Override
public long count(Specification spec) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.count(spec);
}
@Override
protected Page readPage(TypedQuery query, Pageable pageable, Specification spec) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.readPage(query, pageable, spec);
}
@Override
protected Page readPage(TypedQuery query, Class domainClass, Pageable pageable,
Specification spec) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.readPage(query, domainClass, pageable, spec);
}
@Override
protected TypedQuery getQuery(Specification spec, Pageable pageable) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getQuery(spec, pageable);
}
@Override
protected TypedQuery getQuery(Specification spec, Class domainClass, Pageable pageable) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getQuery(spec, domainClass, pageable);
}
@Override
protected TypedQuery getQuery(Specification spec, Sort sort) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getQuery(spec, sort);
}
@Override
protected TypedQuery getQuery(Specification spec, Class domainClass, Sort sort) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getQuery(spec, domainClass, sort);
}
@Override
protected TypedQuery getCountQuery(Specification spec) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getCountQuery(spec);
}
@Override
protected TypedQuery getCountQuery(Specification spec, Class domainClass) {
// TODO 这是系统自动生成描述,请在此补完后续代码
return super.getCountQuery(spec, domainClass);
}
/**
* 封装自定义组合查询排序列表方法
*
* (non-Javadoc)
*/
@Override
public List findAll(BaseQuery query, Sort sort) {
return findAll(getConditonByQuery(query), sort);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public List save(Iterable arg0) {
return super.save(arg0);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public S save(S entity) {
return super.save(entity);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public S saveAndFlush(S entity) {
return super.saveAndFlush(entity);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
super.setRepositoryMethodMetadata(crudMethodMetadata);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void delete(ID id) {
super.delete(id);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void delete(T entity) {
super.delete(entity);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void delete(Iterable extends T> entities) {
super.delete(entities);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void deleteInBatch(Iterable entities) {
super.deleteInBatch(entities);
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void deleteAll() {
super.deleteAll();
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void deleteAllInBatch() {
super.deleteAllInBatch();
}
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
@Override
public void flush() {
super.flush();
}
/**
*
* 自定义更新update方法
*
* @author liuyi 2016年7月16日
* @param
* @param where
* @return
*/
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
public int update(T t,BaseQuery where,String... updateFileds){
CriteriaBuilder cb =baseEm.getEntityManagerFactory().getCriteriaBuilder();
CriteriaUpdate update = (CriteriaUpdate) cb.createCriteriaUpdate(t.getClass());
Root root = update.from((Class) t.getClass());
for(String fieldName:updateFileds){
try {
Object o = PropertyUtils.getProperty(t, fieldName);
update.set(fieldName, o);
} catch (Exception e) {
GwsLogger.error("update error:"+e);
}
}
update.where(BaseQueryPredicateBuilder.getPredicate2(root, cb,where));
return baseEm.createQuery(update).executeUpdate();
}
/**
*
* 根据唯一主键更新相关数据
*
* @author liuyi 2016年7月16日
* @param id
* @param
* @return
*/
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
public int updateById(T t,ID id,String... updateFileds){
CriteriaBuilder cb =baseEm.getEntityManagerFactory().getCriteriaBuilder();
CriteriaUpdate update = (CriteriaUpdate) cb.createCriteriaUpdate(t.getClass());
Root root = update.from((Class) t.getClass());
for(String fieldName:updateFileds){
try {
Object o = PropertyUtils.getProperty(t, fieldName);
update.set(fieldName, o);
} catch (Exception e) {
GwsLogger.error("update error:"+e);
}
}
//定位主键信息
Iterable idAttributeNames = baseEmInfo.getIdAttributeNames();
for(String key:idAttributeNames){
if(key!=null&&key!=""){
update.where(cb.equal(root.get(key), id));
break;
}
}
return baseEm.createQuery(update).executeUpdate();
}
}
通过getConditonByQuery(BaseQuery query)方法可以看到获取了我们实现的构造条件实例,从而作为复杂条件查询的Specification
/**
* 自定义jpa工厂类 可确定具体的实现类
* Created by mj on 2017/12/20.
*/
public class BaseRepositoryFactoryBean , T,
I extends Serializable> extends JpaRepositoryFactoryBean {
public BaseRepositoryFactoryBean(Class extends R> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
return new BaseRepositoryFactory(em);
}
//创建一个内部类,该类不用在外部访问
private static class BaseRepositoryFactory
extends JpaRepositoryFactory {
private final EntityManager em;
public BaseRepositoryFactory(EntityManager em) {
super(em);
this.em = em;
}
//设置具体的实现类是BaseRepositoryImpl
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return new BaseSimpleJpaRepositoryEx((Class) information.getDomainType(), em);
}
//设置具体的实现类的class
@Override
protected Class> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseSimpleJpaRepositoryEx.class;
}
}
}
通过代码可以很清楚看到是如何指定的,最后在应用程序的启动类上加上@EnableJpaRepositories指定自动启动扫描这个工厂即可
@EnableJpaRepositories(basePackages = {"com.xiaoma"},
repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class//指定自己的工厂类
)
@SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
System.out.println("========================================xiaomage blog server is started!");
GwsLogger.info("xiaomage blog server is started!");
}
}
至此我们的封装就大功告成,下面就让我们来感受一下效果吧!
首先创建一个实体类Tag
/**
* Tag 实体类
* Created by mj on 2017/12/17.
*/
@Entity
@Table(name = "tag")
public class Tag implements Serializable {
@Id
@GeneratedValue
@Column(name = "tag_id")
private Integer tagId;
@Column(name = "tag_name")
private String tagName;
@Column(name = "create_time")
private Integer createTime;
@Column(name = "update_time")
private Integer updateTime;
此处省略get()/set()方法
}
创建TagRepository接口继承BaseRepository
/**
* Created by mj on 2017/12/17.
*/
public interface TagRepository extends BaseRepository {
}
创建TagQueryFilter查询条件过滤类
/**
* Created by mj on 2017/12/24.
*/
@QBindEntity(entityClass = Tag.class)
public class TagQueryFilter extends BaseQuery {
@QBindAttrField(fieldName = "tagId", where = Where.equal)
private Integer tagId;
@QBindAttrField(fieldName = "tagName", where = Where.equal)
private String tagName;
public Integer getTagId() {
return tagId;
}
public void setTagId(Integer tagId) {
this.tagId = tagId;
}
public String getTagName() {
return tagName;
}
public void setTagName(String tagName) {
this.tagName = tagName;
}
}
创建TagServiceImpl业务实现类实现findTags()方法,TagService此处省略
/**
* Created by mj on 2017/12/24.
*/
@Service
public class TagServiceImpl implements TagService {
@Autowired
private TagRepository tagRepository;
/**
* 分页查询文章所有标签 返回标签list
* @param tag
* @return
*/
@Override
public Page findTags(Tag tag, Integer pageNo, Integer pageSize, String sortField) {
//组合查询语句
TagQueryFilter query = new TagQueryFilter();
if (null != tag){
if (StringUtils.isNotBlank(tag.getTagName())){
query.setTagName(tag.getTagName());
}
}
Sort sort = new Sort(Sort.Direction.DESC,sortField);
Pageable pageable = new PageRequest(pageNo,pageSize,sort);
Page pageList = tagRepository.findAll(query,pageable);
return pageList;
}
}
这样我们就实现了通过tagName作为条件的分页排序查询,如需其他条件只需set其他的属性即可,怎么样很简单吧
创建TagController
/**
* Created by mj on 2017/12/24.
*/
@Controller
@RequestMapping(value = "admin")
public class TagController {
@Autowired
private TagService tagService;
/**
* 分页查询文章所有标签
* @param tag
* @return
*/
@RequestMapping(value = "/findTags")
@ResponseBody
public RetResult findTags(Tag tag, Integer pageNo, Integer pageSize) {
String code = CommConstant.GWSCOD0000;
String message = CommConstant.GWSMSG0000;
GwsLogger.info("博客文章标签开始:code={},message={},tag={}",code,message,tag);
Page tags = null;
try {
if (null == pageNo){
pageNo = 0;
}else {
pageNo = pageNo-1;
}
tags = tagService.findTags(tag,pageNo,pageSize,CommConstant.SORT_FIELD_UTIME);
}catch (Exception e){
code = CommConstant.GWSCOD0001;
message = CommConstant.GWSMSG0001;
GwsLogger.error("博客文章标签异常:code={},message={},e={}",code,message,e);
}
GwsLogger.info("博客文章标签结束,code={},message={},tags={}",code,message,tags);
return RetResult.setRetDate(code,message,tags);
}
}
然后通过请求测试http://127.0.0.1:8081/admin/findTags?tagName=SpringBoot&pageNo=1&pageSize=3即可看到结果
{
"code": "000",
"data": {
"content": [
{
"createTime": 1513934861,
"createTimeStr": "2017-12-22",
"tagId": 2,
"tagName": "SpringBoot",
"updateTime": 1513934861,
"updateTimeStr": "2017-12-22"
}
],
"first": true,
"last": true,
"number": 0,
"numberOfElements": 1,
"size": 3,
"sort": [
{
"ascending": false,
"descending": true,
"direction": "DESC",
"ignoreCase": false,
"nullHandling": "NATIVE",
"property": "updateTime"
}
],
"totalElements": 1,
"totalPages": 1
},
"extraData": null,
"message": "success"
}
好了至此我们已经使用Spring-data-jpa进行简单操作以及复杂操作的封装,如需其他操作方式可以在这基础上对其构建条件进行修改,上面实例是我个人搭建博客的代码,基本可以满足大部分需求,可以直接拿来使用,相信大家也会有更好的玩法可以一起分享交流。