资讯专栏INFORMATION COLUMN

Spring Data JPA 查询

icattlecoder / 2818人阅读

摘要:根据区域部门学科类别查询出符合条件的考评人员。继承接口我们的考评员仓库原继承,为了实现可以复杂查询,继承接口。实现规格接口为了规范,我们建了一个的查询规格的包,将所有生成查询规格的类放入该包中。

SpringData

Spring项目中,我们使用JPA进行查询,只需简单地继承SpringData提供的接口即可实现强大的数据查询功能。

之前的强检器具统计管理用的仅仅是单表查询,理解不深,这次开发的考评员综合查询涉及到了多个实体间的查询,值得学习,特此记录。

以下是ER图。

管理部门选择本部门或其管辖的部门中的某人员作为该部门的考评人员。根据区域、部门、学科类别查询出符合条件的考评人员。

概述

复杂查询,我们就需要SpringData提供的JpaSpecificationExecutor接口,这是该接口的源代码。

public interface JpaSpecificationExecutor {

    /**
     * Returns a single entity matching the given {@link Specification}.
     * 
     * @param spec
     * @return
     */
    T findOne(Specification spec);

    /**
     * Returns all entities matching the given {@link Specification}.
     * 
     * @param spec
     * @return
     */
    List findAll(Specification spec);

    /**
     * Returns a {@link Page} of entities matching the given {@link Specification}.
     * 
     * @param spec
     * @param pageable
     * @return
     */
    Page findAll(Specification spec, Pageable pageable);

    /**
     * Returns all entities matching the given {@link Specification} and {@link Sort}.
     * 
     * @param spec
     * @param sort
     * @return
     */
    List findAll(Specification spec, Sort sort);

    /**
     * Returns the number of instances that the given {@link Specification} will return.
     * 
     * @param spec the {@link Specification} to count instances for
     * @return the number of instances
     */
    long count(Specification spec);
}

该接口中声明的方法都有一个共同的参数:Specification,翻译过来就是规范的意思。

这是Specification接口源代码。

public interface Specification {

    /**
     * Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
     * {@link Root} and {@link CriteriaQuery}.
     * 
     * @param root
     * @param query
     * @return a {@link Predicate}, must not be {@literal null}.
     */
    Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
}

Specification接口中有一个toPredicate方法,看不懂代码我们可以看注释。

Creates a WHERE clause for a query of the referenced entity in form of a {Predicate}

用谓词的形式为引用的实体创建一个WHERE语句。

到这里,我们应该明白了这个查询的原理:我们先生成我们需要的谓语,然后用toPredicate将谓语生成为where语句,最后再使用我们Specification类型的where语句作为参数用JpaSpecificationExecutor接口中定义的方法进行查询。

继承JpaSpecificationExecutor接口
public interface ExaminerRepository extends CrudRepository, JpaSpecificationExecutor {
}

我们的考评员仓库原继承CrudRepository,为了实现可以复杂查询,继承JpaSpecificationExecutor接口。

实现规格接口

为了规范,我们建了一个spec的查询规格的包,将所有生成查询规格的类放入该包中。

/**
 * @author zhangxishuo on 2018/5/26
 * 考评人员查询规格
 */

public class ExaminerSpecs {

    // 日志
    private static final Logger logger = Logger.getLogger(ExaminerSpecs.class);

    public static Specification base(Department userDepartment, final Map map) {
        return new Specification() {

            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                return null;
            }
        };
    }
}

我们在这个类中写了一个base方法,用于根据传入的参数生成一个查询规格。

return语句中,我们new了一个接口,然后在new这个接口的同时对这个接口进行实现,实现接口中的toPredicate方法。

toPredicate中的三个参数意义。Root:查询的模型;CriteriaQuery:标准查询,用于查询的;CriteriaBuilder:标准构建,用户构建查询语句的。

大家不要去纠结是直接new接口然后在花括号中实现还是写一个类实现接口然后再去new

这里说一下我自己的理解。其实这主要是看实际需要,如果这个接口的实现类需要被好多个其他类调用,为了代码的复用,我们就会写一个类去实现接口,就像我们后台MVC架构的业务逻辑层,因为同一个方法可能会被好多个控制器进行调用,所以我们建一个Service接口,再建立一个Service接口实现类,然后其他类需要时进行Autowire

如果我们的方法不需要复用,那Android代码就是我们最好的例子(Android中大量使用了new接口然后在new时去实现的写法)。

public class MyActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.content_layout_id);

        final Button button = findViewById(R.id.button_id);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // Code here executes on main thread after user presses button
            }
        });
    }
}

这是Android官方文档中对按钮添加点击事件的示例代码,我们看到这里的View.OnClickListener()就是在new的时候再去实现的。其实这也不难理解,打开我们每天用的APP,几乎每个按钮,每个视图的功能都是不一样的,所以不需要进行代码复用。

构建谓语

谓语是什么呢?按我的理解,一个谓语就是一个条件。

logger.debug("构建谓语,人员的部门的区域的id等于map中区域的id");
Predicate districtPredicate = criteriaBuilder
        .equal(root.join("qualifier")
                    .join("department")
                    .join("district")
                    .get("id")
                    .as(Long.class),
                ((District) map.get("district"))
                    .getId());

我们使用criteriaBuilderequal方法构建了一个等于的条件(或谓语),该条件要求考评员(root)的人员的部门的区域的id转化为Long类型的结果与传入的区域的id相等。

类似的写法,我们可以构建出部门和学科的谓语。

连接谓语

谓语创建完了,我们还需要将这些谓语进行连接。

这里我们需要用到and条件,即区域符合且部门符合且学科类别符合。

为了方便构建,这里参考之前的项目代码构建了一个用and拼接谓语的方法。

private Predicate predicate = null;
private CriteriaBuilder criteriaBuilder;

// 设置and谓语.注意,这里只能设置and关系的谓语,如果谓语为OR,则需要手动设置
private void andPredicate(Predicate predicate) {
    // 如果传入的谓语不为空
    if (null != predicate) {
        if (null == this.predicate) {
            // 如果该方法之前没有谓语,则直接赋值
            this.predicate = predicate;
        } else {
            // 如果之前有谓语。则使用criteriaBuilder的与将已有谓语和新谓语用and连接
            this.predicate = this.criteriaBuilder.and(this.predicate, predicate);
        }
    }
}

这几行代码就体现了一名优秀软件工程师的素养,优秀的人在写代码之前就能遇见哪些功能是重复的,不需实现之后再将相同代码分离重构。

logger.debug("用and连接该谓语");
this.andPredicate(districtPredicate);
CriteriaQuery

如果我们只是单纯的查询的话,没有什么特殊要求的话,那我们直接就可以把我们的谓语返回。

return this.predicate;

没错,直接将谓语返回,debug的时候发现实际查询时会获取我们的谓语,并执行query.where语句。

如果需要复杂功能的话,可以使用CriteriaQuery

// 把Predicate应用到CriteriaQuery中去,可以实现更丰富的查询功能,可以分组,排序等
criteriaQuery.where(this.predicate);

return criteriaQuery.getRestriction();
查询

基础工作完成了,我们终于可以使用我们的谓语进行查询啦。

/**
 * 查询符合条件的考评员信息
 * @param district   区域
 * @param department 部门
 * @param discipline 学科类别
 * @return 符合条件的考评员信息
 */
@Override
public List getAllExaminerInfo(District district, Department department, Discipline discipline) {
    logger.debug("新建Map并设置属性");
    Map map = new HashMap<>();
    map.put("department", department);
    map.put("district", district);
    map.put("discipline", discipline);

    logger.debug("调用根据Map的查询方法");
    return this.getExaminerInfoByMap(map);
}

/**
 * 根据Map查询考评员的信息
 * @param map 考评员查询Map
 * @return 考评员列表
 */
private List getExaminerInfoByMap(Map map) {
    logger.debug("获取当前登录用户");
    User currentLoginUser = userService.getCurrentLoginUser();

    logger.debug("获取查询谓语");
    Specification specification = ExaminerSpecs.base(currentLoginUser.getDepartment(), map);

    logger.debug("查询并返回");
    return (List) examinerRepository.findAll(specification);
}

怎么样,谓语拼接完之后是不是很简单呢?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69615.html

相关文章

  • Spring Boot 的简单教程(四)数据库连接之Spring Data JPA的使用

    摘要:以前都是用进行数据库的开发,最近学习之后发现显得更友好,所以我们就一起来了解一下的原理吧。简单介绍持久性是的一个规范。它用于在对象和关系数据库之间保存数据。充当面向对象的领域模型和关系数据库系统之间的桥梁。是标识出主键是指定主键的自增方式。 以前都是用Mybatis进行数据库的开发,最近学习Spring Boot之后发现JPA显得更友好,所以我们就一起来了解一下JPA的原理吧。 Spr...

    yuxue 评论0 收藏0
  • Spring Boot [组件学习-Spring Data JPA]

    摘要:与的关系是什么是官方提出的持久化规范。它为开发人员提供了一种对象关联映射工具来管理应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合技术,结束现在,,等框架各自为营的局面。定义了在对数据库中的对象处理查询和事务运行时的的。 导读: 在上篇文章中对Spring MVC常用的一些注解做了简要的说明,在这篇文章中主要对Spring Data JPA 做一个简要的说明,并附有一...

    andong777 评论0 收藏0
  • 我就是不看好jpa

    摘要:要是紧急排查个问题,妈蛋虽然有很多好处,比如和底层的无关。你的公司如果有,是不允许你乱用的。 知乎看到问题《SpringBoot开发使用Mybatis还是Spring Data JPA??》,顺手一答,讨论激烈。我实在搞不懂spring data jpa为啥选了hibernate作为它的实现,是Gavin King的裙带关系么?DAO层搞来搞去,从jdbc到hibernate,从top...

    NusterCache 评论0 收藏0
  • 一起来学SpringBoot | 第六篇:整合SpringDataJpa

    摘要:忽略该字段的映射省略创建数据访问层接口,需要继承,第一个泛型参数是实体对象的名称,第二个是主键类型。 SpringBoot 是为了简化 Spring 应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程 上一篇介绍了Spring JdbcTempl...

    Dionysus_go 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<