QueryDSL与SpringDataJPA实现多表关联查询

恒宇少年2018-10-29 19:30:10

对于业务逻辑复制的系统来说都存在多表关联查询的情况,查询的返回对象内容也是根据具体业务来处理的,我们本章主要是针对多表关联根据条件查询后返回单表对象,在下一章我们就会针对多表查询返回自定义的对象实体。

本章目标

基于SpringBoot框架平台完成SpringDataJPA与QueryDSL多表关联查询返回单表对象实例,查询时完全采用QueryDSL语法进行编写。

构建项目

我们使用idea工具先来创建一个SpringBoot项目,添加的依赖跟第三章:使用QueryDSL与SpringDataJPA完成Update&Delete一致。为了方便分离文章源码,我们创建完成后把第三章的application.yml配置文件以及pom.xml依赖内容复制到本章项目中(配置内容请参考第三章)。

创建数据表

我们先来根据一个简单的业务逻辑来创建两张一对多关系的表,下面我们先来创建商品类型信息表,代码如下:

  1. -- ----------------------------

  2. -- Table structure for good_types

  3. -- ----------------------------

  4. DROP TABLE IF EXISTS `good_types`;

  5. CREATE TABLE `good_types` (

  6.  `tgt_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',

  7.  `tgt_name` varchar(30) CHARACTER SET utf8 DEFAULT NULL COMMENT '类型名称',

  8.  `tgt_is_show` char(1) DEFAULT NULL COMMENT '是否显示',

  9.  `tgt_order` int(2) DEFAULT NULL COMMENT '类型排序',

  10.  PRIMARY KEY (`tgt_id`)

  11. ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

接下来我们再来创建一个商品基本信息表,表结构如下代码所示:

  1. -- ----------------------------

  2. -- Table structure for good_infos

  3. -- ----------------------------

  4. DROP TABLE IF EXISTS `good_infos`;

  5. CREATE TABLE `good_infos` (

  6.  `tg_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',

  7.  `tg_title` varchar(50) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品标题',

  8.  `tg_price` decimal(8,2) DEFAULT NULL COMMENT '商品单价',

  9.  `tg_unit` varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT '单位',

  10.  `tg_order` varchar(255) DEFAULT NULL COMMENT '排序',

  11.  `tg_type_id` int(11) DEFAULT NULL COMMENT '类型外键编号',

  12.  PRIMARY KEY (`tg_id`),

  13.  KEY `tg_type_id` (`tg_type_id`)

  14. ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

创建实体

我们对应上面两张表的结构创建两个实体并添加对应的SpringDataJPA注解配置,商品类型实体如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter4.bean;

  2. import lombok.Data;

  3. import javax.persistence.*;

  4. import java.io.Serializable;

  5. /**

  6. * ========================

  7. * Created with IntelliJ IDEA.

  8. * User:恒宇少年

  9. * Date:2017/7/9

  10. * Time:15:04

  11. * 码云:http://git.oschina.net/jnyqy

  12. * ========================

  13. */

  14. @Entity

  15. @Table(name = "good_types")

  16. @Data

  17. public class GoodTypeBean

  18.    implements Serializable

  19. {

  20.    //主键

  21.    @Id

  22.    @GeneratedValue

  23.    @Column(name = "tgt_id")

  24.    private Long id;

  25.    //类型名称

  26.    @Column(name = "tgt_name")

  27.    private String name;

  28.    //是否显示

  29.    @Column(name = "tgt_is_show")

  30.    private int isShow;

  31.    //排序

  32.    @Column(name = "tgt_order")

  33.    private int order;

  34. }

商品基本信息实体如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter4.bean;

  2. import lombok.Data;

  3. import javax.persistence.*;

  4. import java.io.Serializable;

  5. /**

  6. * ========================

  7. * Created with IntelliJ IDEA.

  8. * User:恒宇少年

  9. * Date:2017/7/9

  10. * Time:15:08

  11. * 码云:http://git.oschina.net/jnyqy

  12. * ========================

  13. */

  14. @Entity

  15. @Table(name = "good_infos")

  16. @Data

  17. public class GoodInfoBean

  18.    implements Serializable

  19. {

  20.    //主键

  21.    @Id

  22.    @GeneratedValue

  23.    @Column(name = "tg_id")

  24.    private Long id;

  25.    //商品标题

  26.    @Column(name = "tg_title")

  27.    private String title;

  28.    //商品价格

  29.    @Column(name = "tg_price")

  30.    private double price;

  31.    //商品单位

  32.    @Column(name = "tg_unit")

  33.    private String unit;

  34.    //商品排序

  35.    @Column(name = "tg_order")

  36.    private int order;

  37.    //类型外键

  38.    @Column(name = "tg_type_id")

  39.    private Long typeId;

  40. }

我在商品表内并没有使用类型的实体作为表之间的关联而是只用的具体类型编号,有的时候也是根据你的需求来配置的,如果你每个商品读取基本信息时都需要获取商品的类型,那么这里配置@OneToOne还是比较省事,不需要你再操作多余的查询。

构建QueryDSL查询实体

下面我们使用maven compile命令来自动生成QueryDSL的查询实体,我们在执行命令的时候会自动去pom.xml配置文件内查找JPAAnnotationProcessor插件,如果你的实体配置了@Entity注解,那么就会自动生成查询实体并将生成的实体放置到target/generated-sources/java内。 我们找到idea工具的Maven Projects窗口,如下图1所示:

创建控制器

下面我们来创建一个控制器,我们在控制器内直接编写QueryDSL查询代码,这里就不去根据MVC模式进行编程了,在正式环境下还请大家按照MVC模式来编码。 控制器代码如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter4.controller;

  2. import com.querydsl.jpa.impl.JPAQueryFactory;

  3. import com.yuqiyu.querydsl.sample.chapter4.bean.GoodInfoBean;

  4. import com.yuqiyu.querydsl.sample.chapter4.bean.QGoodInfoBean;

  5. import com.yuqiyu.querydsl.sample.chapter4.bean.QGoodTypeBean;

  6. import org.springframework.beans.factory.annotation.Autowired;

  7. import org.springframework.web.bind.annotation.RequestMapping;

  8. import org.springframework.web.bind.annotation.RequestParam;

  9. import org.springframework.web.bind.annotation.RestController;

  10. import javax.annotation.PostConstruct;

  11. import javax.persistence.EntityManager;

  12. import java.util.List;

  13. /**

  14. * ========================

  15. * Created with IntelliJ IDEA.

  16. * User:恒宇少年

  17. * Date:2017/7/9

  18. * Time:15:24

  19. * 码云:http://git.oschina.net/jnyqy

  20. * ========================

  21. */

  22. @RestController

  23. public class GoodController

  24. {

  25.    @Autowired

  26.    private EntityManager entityManager;

  27.    //查询工厂实体

  28.    private JPAQueryFactory queryFactory;

  29.    //实例化控制器完成后执行该方法实例化JPAQueryFactory

  30.    @PostConstruct

  31.    public void initFactory()

  32.    {

  33.        System.out.println("开始实例化JPAQueryFactory");

  34.        queryFactory = new JPAQueryFactory(entityManager);

  35.    }

  36.    @RequestMapping(value = "/selectByType")

  37.    public List<GoodInfoBean> selectByType

  38.            (

  39.                    @RequestParam(value = "typeId") Long typeId //类型编号

  40.            )

  41.    {

  42.        //商品查询实体

  43.        QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;

  44.        //商品类型查询实体

  45.        QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean;

  46.        return

  47.                queryFactory

  48.                .select(_Q_good)

  49.                .from(_Q_good,_Q_good_type)

  50.                .where(

  51.                        //为两个实体关联查询

  52.                        _Q_good.typeId.eq(_Q_good_type.id)

  53.                        .and(

  54.                                //查询指定typeid的商品

  55.                                _Q_good_type.id.eq(typeId)

  56.                        )

  57.                )

  58.                //根据排序字段倒序

  59.                .orderBy(_Q_good.order.desc())

  60.                //执行查询

  61.                .fetch();

  62.    }

  63. }

可以看到上面的代码,我们查询了两张表,仅返回了商品信息内的字段(select(Qgood)),我们在where条件内进行了这两张表的关联,根据传递的类型编号作为关联商品类型主键(相当于left join),最后根据排序字段进行倒序。

运行测试

下面我们来运行项目,控制台日志输出内容如下所示:

  1. .....

  2.  .   ____          _            __ _ _

  3. /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

  4. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

  5. \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

  6.  '  |____| .__|_| |_|_| |_\__, | / / / /

  7. =========|_|==============|___/=/_/_/_/

  8. :: Spring Boot ::        (v1.5.4.RELEASE)

  9. .....

  10. 开始实例化JPAQueryFactory

  11. 2017-07-09 15:40:38.454  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1184ab05: startup date [Sun Jul 09 15:40:36 CST 2017]; root of context hierarchy

  12. 2017-07-09 15:40:38.495  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/selectByType]}" onto public java.util.List<com.yuqiyu.querydsl.sample.chapter4.bean.GoodInfoBean> com.yuqiyu.querydsl.sample.chapter4.controller.GoodController.selectByType(java.lang.Long)

  13. 2017-07-09 15:40:38.498  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)

  14. 2017-07-09 15:40:38.498  INFO 11776 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

  15. 2017-07-09 15:40:38.515  INFO 11776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

  16. 2017-07-09 15:40:38.515  INFO 11776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

  17. 2017-07-09 15:40:38.536  INFO 11776 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

  18. 2017-07-09 15:40:38.721  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup

  19. 2017-07-09 15:40:38.723  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure

  20. 2017-07-09 15:40:38.726  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.alibaba.druid.pool:name=dataSource,type=DruidDataSource]

  21. 2017-07-09 15:40:38.765  INFO 11776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)

  22. 2017-07-09 15:40:38.769  INFO 11776 --- [           main] c.y.q.s.chapter4.Chapter4Application     : Started Chapter4Application in 3.027 seconds (JVM running for 3.683)

可以看到我们在项目启动的时候JPAQueryFactory查询工厂对象就被实例了,接下来我们直接使用JPAQueryFactory实例对象就Ok了。下面我们来访问 : http://127.0.0.1:8080/selectByType?typeId=1 界面输出内容如下图3所示:

  1. Hibernate:

  2.    select

  3.        goodinfobe0_.tg_id as tg_id1_0_,

  4.        goodinfobe0_.tg_order as tg_order2_0_,

  5.        goodinfobe0_.tg_price as tg_price3_0_,

  6.        goodinfobe0_.tg_title as tg_title4_0_,

  7.        goodinfobe0_.tg_type_id as tg_type_5_0_,

  8.        goodinfobe0_.tg_unit as tg_unit6_0_

  9.    from

  10.        good_infos goodinfobe0_ cross

  11.    join

  12.        good_types goodtypebe1_

  13.    where

  14.        goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id

  15.        and goodtypebe1_.tgt_id=?

  16.    order by

  17.        goodinfobe0_.tg_order desc

QueryDSL自动生成的SQL采用了Cross Join 获取两张表的《笛卡尔集》然后根据select内配置的实体进行返回字段,我们使用 where goodinfobe0.tgtypeid=goodtypebe1.tgtid 代替了on goodinfobe0.tgtypeid=goodtypebe1.tgtid实现了相同的效果。

总结

本章的内容比较简单,我们使用QueryDSL完成了两个实体关联查询并返回单实体实例的方法,QueryDSL内也有LeftJoin、InnerJoin等关联查询不过都是基于具体实体类型来完成的,本章就不做解释了,用起来比较繁琐复杂它们遵循的是HQL语法。

本章代码已经上传到码云:

SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter 

SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter

同系列文章

SpringBoot与QueryDSL初整合


QueryDSL与SpringDataJPA实现单表普通条件查询


使用QueryDSL与SpringDataJPA完成Update&Delete

推荐阅读

SpringBoot平台使用Lombok来优雅的编码


在SpringBoot内如何使用ApplicationEvent&Listener完成业务解耦?


SpringBoot自定义专属业务的Starter


SpringBoot使用MapStruct自动映射DTO


SpringBoot架构使用Profile完成打包环境分离


SpringBoot2.0新特性 - 你get到WebMvcConfigurer两种配置方式了吗?


SpringBoot2.0新特性 - 岂止至今最简单redis缓存集成


SpringBoot2.0新特性 - Quartz自动化配置集成


编码规范 - 养成良好的Java编码习惯


基于SpringBoot 设计业务逻辑异常统一处理


基于SpringBoot & Quartz分布式单节点持久化

加入知识星球,恒宇少年带你走以后的技术道路!!!


Copyright © 温县电话机虚拟社区@2017