第四章:使用QueryDSL与SpringDataJPA实现多表关联查询

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

本章目标

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

构建项目

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

创建数据表

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

-- ----------------------------
-- Table structure for good_types
-- ----------------------------
DROP TABLE IF EXISTS `good_types`;
CREATE TABLE `good_types` (
  `tgt_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `tgt_name` varchar(30) CHARACTER SET utf8 DEFAULT NULL COMMENT '类型名称',
  `tgt_is_show` char(1) DEFAULT NULL COMMENT '是否显示',
  `tgt_order` int(2) DEFAULT NULL COMMENT '类型排序',
  PRIMARY KEY (`tgt_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

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

-- ----------------------------
-- Table structure for good_infos
-- ----------------------------
DROP TABLE IF EXISTS `good_infos`;
CREATE TABLE `good_infos` (
  `tg_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `tg_title` varchar(50) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品标题',
  `tg_price` decimal(8,2) DEFAULT NULL COMMENT '商品单价',
  `tg_unit` varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT '单位',
  `tg_order` varchar(255) DEFAULT NULL COMMENT '排序',
  `tg_type_id` int(11) DEFAULT NULL COMMENT '类型外键编号',
  PRIMARY KEY (`tg_id`),
  KEY `tg_type_id` (`tg_type_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

创建实体

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

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

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/9
 * Time:15:04
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "good_types")
@Data
public class GoodTypeBean
    implements Serializable
{
    //主键
    @Id
    @GeneratedValue
    @Column(name = "tgt_id")
    private Long id;
    //类型名称
    @Column(name = "tgt_name")
    private String name;
    //是否显示
    @Column(name = "tgt_is_show")
    private int isShow;
    //排序
    @Column(name = "tgt_order")
    private int order;
}

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

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

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/9
 * Time:15:08
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "good_infos")
@Data
public class GoodInfoBean
    implements Serializable
{
    //主键
    @Id
    @GeneratedValue
    @Column(name = "tg_id")
    private Long id;
    //商品标题
    @Column(name = "tg_title")
    private String title;
    //商品价格
    @Column(name = "tg_price")
    private double price;
    //商品单位
    @Column(name = "tg_unit")
    private String unit;
    //商品排序
    @Column(name = "tg_order")
    private int order;
    //类型外键
    @Column(name = "tg_type_id")
    private Long typeId;
}

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

构建QueryDSL查询实体

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

图1

我们双击对应的命令就可以执行构建项目了,构建完成的查询实体如下图2所示:

图2

如上图2所示,QueryDSL在生成时会完全根据实体的包来对应创建。

创建控制器

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

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

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.yuqiyu.querydsl.sample.chapter4.bean.GoodInfoBean;
import com.yuqiyu.querydsl.sample.chapter4.bean.QGoodInfoBean;
import com.yuqiyu.querydsl.sample.chapter4.bean.QGoodTypeBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.util.List;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/9
 * Time:15:24
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class GoodController
{

    @Autowired
    private EntityManager entityManager;

    //查询工厂实体
    private JPAQueryFactory queryFactory;

    //实例化控制器完成后执行该方法实例化JPAQueryFactory
    @PostConstruct
    public void initFactory()
    {
        System.out.println("开始实例化JPAQueryFactory");
        queryFactory = new JPAQueryFactory(entityManager);
    }

    @RequestMapping(value = "/selectByType")
    public List<GoodInfoBean> selectByType
            (
                    @RequestParam(value = "typeId") Long typeId //类型编号
            )
    {
        //商品查询实体
        QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
        //商品类型查询实体
        QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean;
        return
                queryFactory
                .select(_Q_good)
                .from(_Q_good,_Q_good_type)
                .where(
                        //为两个实体关联查询
                        _Q_good.typeId.eq(_Q_good_type.id)
                        .and(
                                //查询指定typeid的商品
                                _Q_good_type.id.eq(typeId)
                        )
                )
                //根据排序字段倒序
                .orderBy(_Q_good.order.desc())
                //执行查询
                .fetch();
    }
}

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

运行测试

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

.....

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.4.RELEASE)

.....
开始实例化JPAQueryFactory
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
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)
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)
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)
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]
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]
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]
2017-07-09 15:40:38.721  INFO 11776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
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
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]
2017-07-09 15:40:38.765  INFO 11776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
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所示:

图3

下面我们来看下控制台输出的SQL,如下所示:

Hibernate: 
    select
        goodinfobe0_.tg_id as tg_id1_0_,
        goodinfobe0_.tg_order as tg_order2_0_,
        goodinfobe0_.tg_price as tg_price3_0_,
        goodinfobe0_.tg_title as tg_title4_0_,
        goodinfobe0_.tg_type_id as tg_type_5_0_,
        goodinfobe0_.tg_unit as tg_unit6_0_ 
    from
        good_infos goodinfobe0_ cross 
    join
        good_types goodtypebe1_ 
    where
        goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id 
        and goodtypebe1_.tgt_id=? 
    order by
        goodinfobe0_.tg_order desc

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

总结

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

本章代码已经上传到码云: SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter SpringBoot相关系列文章请访问:目录:SpringBoot学习目录 QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录 SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录 感谢阅读!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏kangvcar

Python操作MongoDB - 极简教程

1766
来自专栏GreenLeaves

SQL学习之Insert的特殊用法(插入检索出的数据,表之间的数据复制)

1、插入检索出的数据 select * from dbo.Customers_1 ? 现在有个需求,需要将这张Customers_1表的数据合并到Custome...

1818
来自专栏张善友的专栏

Ibatisnet Quick Start

准备工作 1. 下载ibatis软件包(http://ibatis.apache.org/dotnetdownloads.html)。 2. 创建测试数据库,并...

1708
来自专栏更流畅、简洁的软件开发方式

【自然框架】QuickPagerSQL——专门生成分页用的SQL的类库

   分享一个生成分页用SQL的函数库   一般一提到分页,大家就会想到存储过程,而大多数情况都是在存储过程里面拼接SQL,我觉得与其在存储过程里面拼接,还不如...

2345
来自专栏乐沙弥的世界

SQL*Plus 执行脚本时传递参数(@script_name var1,var2)

      在使用sqlplus执行sql脚本时,经常碰到向脚本传递参数的情形。类似于shell脚本的参数传递,我们同样可以向sql脚本传递参数,其方法是脚本后...

1521
来自专栏c#开发者

MVC 5 Scaffolder + EntityFramework+UnitOfWork Pattern 代码生成工具集成Visual Studio 2013

MVC 5 Scaffolder + EntityFramework+UnitOfWork Pattern 代码生成工具 经过一个多星期的努力总算完成了单表,多...

37213
来自专栏维C果糖

详述 SQL 中的 distinct 和 row_number() over() 的区别及用法

1 前言 在咱们编写 SQL 语句操作数据库中的数据的时候,有可能会遇到一些不太爽的问题,例如对于同一字段拥有相同名称的记录,我们只需要显示一条,但实际上数据库...

1757
来自专栏CSDN技术头条

【干货】从配置到实例:MyBatis 基础入门

本质而言,ORM(Object-Relation Mapping),是一种编程技术,能够实现面向对象编程语言与关系型数据库之间的数据转换(映射)。

1042
来自专栏SpringBoot 核心技术

第六章:使用QueryDSL的聚合函数

1202
来自专栏GreenLeaves

Oracle pl/sql编程值控制结构

以下测试案例均来自于scott方案,运行前请确保该方案解锁。 1、条件分支状语 pl/sql中提供了三种条件分支状语,分别是: if   then if   t...

1899

扫码关注云+社区