栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot

1、Spring Boot产生的背景

要了解 Spring Boot 产生的背景,我们就必须要先了解一下 Spring 的发展史,不仅因为Spring Boot 来源于 Spring 体系,而且 Spring Boot 的诞生和 Spring 框架的发展息息相关。

1.1、Spring 的发展史

2003 年 Rod Johnson 和同伴在此框架的基础上开发了一个全新的框架命名为 Spring,据 Rod Johnson 介绍 Spring 是传统 J2EE 新的开始,随后 Spring 发展进入快车道。

2004 年 03 月,1.0 版发布。

2006 年 10 月,2.0 版发布。

2007 年 11 月,更名为 SpringSource,同时发布了 Spring 2.5。

2009 年 12 月,Spring 3.0 发布。

2013 年 12 月,Pivotal 宣布发布 Spring 框架 4.0。

2017 年 09 月,Spring 5.0 发布。

1.2、Spring Boot 的诞生

Spring 每集成一个开源软件,就需要增加一些基础配置,随着开发项目的逐渐庞大,往往需要集成很多开源软件。

在 2013 年的 SpringOne 2GX 会议上,Pivotal 的 CTO Adrian Colyer 回应了这些批评,并且特别提到该平台将来的目标之一就是实现免 XML 配置的开发体验。Spring Boot 所实现的功能超出了这个任务的描述,开发人员不仅不再需要编写 XML,而且在一些场景中甚至不需要编繁琐的 import 语句。

Spring Boot 并不是要成为 Spring 平台里面众多“Foundation”层项目的替代者。Spring Boot 的目标不在于为已解决的问题域提供新的解决方案,而是为平台带来另一种开发体验,从而简化对这些已有技术的使用。对于已经熟悉 Spring 生态系统的开发人员来说,Spring Boot 是一个很理想的选择;对于采用 Spring 技术的新人来说,Spring Boot 提供一种更简洁的方式来使用这些技术。

1.3、Spring Boot 开发团队

Pivotal 公司:致力于“改变世界构造软件的方式(We are transforming how the world builds software)”,提供云原生应用开发 PaaS 平台及服务,帮助企业客户采用敏捷软件开发方法论,从而提高软件开发人员工作效率、减少运维成本,实现数字化转型、IT 创新,并最终实现业务创新。

Pivotal 公司可谓是大牛云集,公司的开源产品有:Spring 以及 Spring 衍生产品、Web 服务器 Tomcat、缓存中间件 Redis、消息中间件 RabbitMQ、平台即服务的 Cloud Foundry、Greenplum 数据引擎、还有大名鼎鼎的 GemFire(12306 系统解决方案组件之一)

回顾 Pivotal 公司的发展历史,简直就是一场商业并购大片:

1989 年,罗伯·米创立 Pivotal Labs 公司,它的主营业务是帮助客户开发软件,曾给谷歌、Twitter 公司做技术支持;

2003 年,EMC 收购了 VMware;

2009 年,VMware 收购了 Spring 公司;

2012 年,EMC 以现金方式收购了 Pivotal Labs 公司;

2013 年,EMC 和 VMware 分拆出其 Cloud Foundry、Pivotal Labs、Greenplum 等云IV科泰教育计算、大数据资源,GE 投资 1.05 亿美元,成立新公司 Pivotal;

2015 年,EMC 又被 DELL 所收购。

1.4、Spring、Spring Boot 和Spring Cloud 的关系

Spring 最初核心的两大核心功能 Spring IoC 和 Spring Aop 成就了 Spring,Spring 在这两大核心功能上不断地发展,才有了 Spring 事务、Spring MVC 等一系列伟大的产品,最终成就了 Spring 帝国,到了后期 Spring 几乎可以解决企业开发中的所有问题。

Spring Boot 是在强大的 Spring 帝国生态基础上面发展而来,发明 Spring Boot 不是为了取代 Spring,是为了让人们更容易的使用 Spring。

Spring 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装并屏蔽掉复杂的配置和实现原理,最终给开发者提供了一套简单易懂、易部署、易维护的分布式系统开发工具包。

Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发。服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

Spring Cloud 是为了解决微服务架构中服务治理而提供的具备一系列功能的开发框架,并且 Spring Cloud 是完全基于 Spring Boot 而开发,Spring Cloud 利用 Spring Boot 特性整合了开源行业中优秀的组件,整体对外提供了一套在微服务架构中服务治理的解决方案。综上我们可以这样来理解,正是由于 Spring IoC 和 Spring Aop 两个强大的功能才有了Spring,Spring 生态不断的发展才有了 Spring Boot,使用 Spring Boot 让 Spring 更易用更有生命力,Spring Cloud 是基Spring Boot 开发的一套微服务架构下的服务治理方案。

2、为什么要学Spring Boot 2.1、Spring Boot 市场热度

Spring Boot 于 2014 年发布了第一个正式版本,发布之后陆续有一些开源爱好者进行了研究,并迅速喜欢上了这款开源软件,Spring Boot 在初期低调快速发展,直到 2016 年才被真正使用起来。期间很多研究 Spring Boot 的开发者,在网上写了大量文章,推动了 Spring Boot 在行业内的发展。

从 2016 年到 2018 年,是 Spring Boot 在中国发展的黄金时期,使用 Spring Boot 的企业和个人开发者越来越多,我们从 Spring Boot 关键字的百度指数可以看出。

 通过谷歌趋势来看 Spring Boot 在美国的使用情况发现,中国和美国使用 Spring Boot 的整体频率保持一致,看来国内技术人同步全球的技术频率越来越快。

看到社区使用 Spring Boot 的热情,Spring 官方也非常重视 Spring Boot 的后续发展,已经把它作为公司最顶级的项目来推广,放到了官网上第一的位置,后续 Spring Boot 的发展也被看好。

2.2、Spring Boot和微服务架构

微服务架构是在互联网高速发展,技术日新月异的变化以及传统架构无法适应快速变化等多重因素的推动下诞生的产物。互联网时代的产品通常有两类特点:需求变化快和用户群体庞大。在这种情况下,如何从系统架构的角度出发,构建灵活、易扩展的系统,快速应对需求的变化;在用户增加的同时如何保证系统的可伸缩性、高可用性,成为系统架构面临的挑战。

Spring Boot 诞生时,正处于微服务概念在慢慢酝酿中,Spring Boot 的研发融合了微服务架构的理念,实现了在 Java 领域内微服务架构落地的技术支撑。Spring Boot 在开发、测试、部署、运维等方面都做了大量的优化,使用 Spring Boot 开发项目,可以快速响应需求、独立完成开发部署上线。

Spring Boot 的一系列特性有助于实现微服务架构的落地,从目前众多的技术栈对比来看它**是** Java 领域微服务架构最优落地技术,没有之一。

Spring Boot 所集成的技术栈,涵盖了各大互联网公司的主流技术,跟着 Spring Boot 的路线去学习,基本可以了解国内外互联网公司的技术特点。

Spring Boot 和微服务架构都是未来软件开发的大趋势,越早参与,受益越大。

一、Spring Boot 入门 1.1、Spring Boot简介

1.1.1、什么是Spring Boot

Spring Boot 是构建所有基于 Spring 的应用程序的起点。Spring Boot 旨在通过最少的Spring 前期配置使您尽快启动并运行。

该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。Spring Boot 默认配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,Spring Boot 整合了所有的框架。它的核心设计思想是:约定优于配置,**Spring Boot** 所有**开发细节都是依据此思想进行实现的。**

Spring Boot 是一套全新的框架,它来自于 Spring 大家族,因此 Spring 所有具备的功能它都有并且更容易使用;同时还简化了基于 Spring 的应用开发,通过少量的代码就能创建一个独立的、产品级别的 Spring 应用。

1.1.2、Spring Boot 的优势

使用 Spring Boot 开发项目,有以下几方面优势:

Spring Boot 使开发变得简单,提供了丰富的解决方案,快速集成各种解决方案提升开发效率。

Spring Boot 使配置变得简单,提供了丰富的 Starters,集成主流开源产品往往只需要简单的配置即可。

Spring Boot 使部署变得简单,其本身内嵌启动容器,仅仅需要一个命令即可启动项目,结合 Jenkins、Docker 自动化运维非常容易实现。

Spring Boot 使监控变得简单,自带监控组件,使用 Actuator 轻松监控服务各项状态。

从软件发展的角度来讲,越简单的开发模式越流行,简单的开发模式解放出更多生产力,让开发人员可以避免将精力耗费在各种配置、语法所设置的门槛上,从而更专注于业务。这点上,Spring Boot 已尽可能地简化了应用开发的门槛。

1.2、快速上手

1.2.1、依赖环境

使用 Spring Boot 开发项目需要有两个基础环境和一个开发工具,这两个环境是指 Java 编译环境和构建工具环境,一个开发工具是指 IDE 开发工具。

构建工具是一个把源代码生成可执行应用程序的自动化工具,Java 领域中主要有三大构建工具:Ant、Maven 和 Gradle。

Ant(AnotherNeatTool)的核心是由 Java 编写,采用 XML 作为构建脚本,这样就允许你在任何环境下运行构建。Ant 是 Java 领域最早的构建工具,不过因为操作复杂,慢慢的已经被淘汰了。

Maven, Maven 发布于 2004 年,目的是解决程序员使用 Ant 所带来的一些问题,它的好处在于可以将项目过程规范化、自动化、高效化以及强大的可扩性。

Gradle,Gradle 是一个基于 Apache Ant 和 Apache Maven 概念的项目自动化建构工具。它使用一种基于 Groovy 的特定领域语言来声明项目设置,而不是传统的 XML。结合了前两者的优点,在此基础之上做了很多改进,它具有 Ant 的强大和灵活,又有 Maven 的生命周期管理且易于使用。

Spring Boot 官方支持 Maven 和 Gradle 作为项目构建工具。Gradle 虽然有更好的理念,但是相比 Maven 来讲其行业使用率偏低,并且 Spring Boot 官方默认使用 Maven,因此本课程选择使用 Maven 作为 Spring Boot 项目构建工具。

1.2.2、构建项目

①、使用IDEA构建

1、选择 File -> New —> Project… 弹出新建项目的框

2、选择 Spring Initializr,Next 也会出现上述类似的配置界面,IDEA 帮我们做了集成。如果没有 Spring Initializr 选项,可以进入 Settings,选择 Plugins,输入 spring 搜索,找到 Spring Boot 勾选上,再重启就可以了。

3、填写相关内容后,点击 Next 选择使用的版本及依赖的包,再点击 Next,最后确定信息无误点击 Finish。SpringBoot 的版本在 IDEA 工具中,随着时间的推移以及 SpringBoot 本身版本的更新,会随时变化,目前本教材编写时可选 2.1.15 版本,教材中有些地方大家可能会看到版本为 2.1.10,那是上一版本的教材编写时可选的版本,截图由于时间关系未做更新

 上面环境相关的配置说明如下:

第一个环境相关选择创建以 Maven 构建项目,还是以 Gradle 构建项目,这是两种不同的构建方式,其中 Gradle 配置内容更简洁一些,并且包含了 maven 的使用,但我们日常使用 maven 居多。

第二个环境相关选择编程语言,现在支持 Java、Kotlin 和 Groovy。

上面项目相关的配置说明如下:

Group,一般填写公司域名,比如百度公司就会填:com.baidu,我们使用 com.ktjiaoyu。

Artifact,可以理解为项目的名称了,可以根据实际情况来填,本次演示使用默认项目名称 demo。

Spring Boot 版本,本课程统一使用版本 2.1.15。

Dependencies,在这块添加我们项目所依赖的 Spring Boot 组件,可以多选,本次选择 Web 模块即可。

②、项目结构介绍

 如上图所示,Spring Boot 的基础结构共三个文件:

src/main/java 程序开发以及主程序入口

src/main/resources 配置文件

src/test/java 测试程序

另外, Spring Boot 建议的目录结构如下:

root package 结构:com.example.myproject

com.example.myproject 目录下:
- Application.java 建议放到根目录下面,是项目的启动类,主要用于做一些框架配置
- comm(config) 目录建议放置公共类,如全局的配置文件、工具类等
- model(entity) 目录主要是实体类
- repository(mapper) 主要是数据库访问层代码
- service 主要是业务逻辑层代码
-  web 主要是表示层代码

resources 目录下:
- static 目录存放 web 访问的静态资源,如 js、css、图片等
- templates 目录存放页面模板
- application.properties 存放项目的配置信息

test 目录存放单元测试的代码
pom.xml 用于配置项目依赖包,以及其他配置
采用默认配置可以省去很多设置,也可以根据公司的规范进行修改。

③、pom包介绍

pom.xml 文件主要描述了项目包的依赖和项目构建时的配置,在默认的 pom.xml 包中分为四大块。

第一部分为项目的描述信息:

   com.ktjiaoyu
 
   demo
 
   0.0.1-SNAPSHOT
 
   demo
 
   Demo project for Spring Boot

第二部分为项目的依赖配置信息:


        org.springframework.boot
        spring-boot-starter-parent
        2.6.2
         


     
            org.springframework.boot
            spring-boot-starter-test
            test
     
     
            org.springframework.boot
            spring-boot-starter-web
     

parent,标签内配置 Spring Boot 父级版本 spring-boot-starter-parent,Maven 支持

项目的父子结构,引入父级后会默认继承父级的配置;

dependencies,标签内配置项目所需要的依赖包,Spring Boot 体系内的依赖组件不需要填写具体版本号,spring-boot-starter-parent 维护了体系内所有依赖包的版本信息。

本项目的依赖除了我们自己勾选的 spring-boot-starter-web 外,还默认包括了spring-boot-starter-test,spring-boot-starter-test 是 Spring Boot 提供项目测试的工具包,内置了多种测试工具,方便我们在项目中做单元测试、集成测试。

第三部分为构建时需要的公共变量:


        1.8
 

此处默认只指定了项目使用的 JDK 版本,我们通常还会指定使用的字符编码等。

第四部分为构建配置:


        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        

使用 Maven 构建 Spring Boot 项目必须依赖于 spring-boot-maven-plugin 组件,spring-boot-maven-plugin 能够以 Maven 的方式为应用提供 Spring Boot 的支持,即为Spring Boot 应用提供了执行 Maven 操作的可能。spring-boot-maven-plugin 能够将 Spring Boot 应用打包为可执行的 jar 或 war 文件,然后以简单的方式运行 Spring Boot 应用。

以上即为 pom.xml 文件基础内容,几乎所有的 Spring Boot 项目都会用到以上配置信息。

至此一个 Spring Boot 项目搭建好了!

1.2.3、编写代码

本小节我们只写一个 HelloWorld 示例,不考虑数据访问层,但为了演示基本功能,编写一个 DemoService 和 HelloController。

在 com.ktjiaoyu.demo.service 包下创建 DemoService 接口:

package com.ktjiaoyu.demo.service;
​
public interface DemoService {
    public String sayHello();
}

在 com.ktjiaoyu.demo.service.impl 包下创建 DemoServiceImpl 实现类:

package com.ktjiaoyu.demo.service.impl;
​
import com.ktjiaoyu.demo.service.DemoService;
import org.springframework.stereotype.Service;
​
@Service("demoService")
public class DemoServiceImpl implements DemoService {
    @Override
    public String sayHello() {
        return "Hello WorId";
    }
}

在 com.ktjiaoyu.demo.web 包下创建 HelloController 控制器:

package com.ktjiaoyu.demo.web;
​
import com.ktjiaoyu.demo.comm.KtjiaoyuInfo;
import com.ktjiaoyu.demo.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
​
@RestController
public class HelloController {
    @Autowired
    private DemoService demoService;
​
    @RequestMapping("/hello")
    public String hello() {
        String msg = demoService.sayHello();
        return msg;
    }
}

@RestController 的意思是 Controller 里面的方法都以 JSON 格式输出,不需要有其他额外的配置;如果配置为 @Controller,代表输出内容到页面。

@RequestMapping("/hello") 提供路由信息,"/hello" 路径的 HTTP Request 都会被映射到 hello() 方法上进行处理。

1.2.4、运行访问

右键单击项目中的 DemoApplication | run 命令,就可以启动项目了,若出现以下内容表示启动成功:

启动成功后,打开浏览器输入网址:http://localhost:8080/hello,就可以看到以下内容了:

1.2.5、单元测试

Spring Boot 中进行单元测试,首先我们需要使用到 spring-boot-starter-test 依赖,它是Spring Boot 提供项目测试的工具包,内置了多种测试工具,方便我们在项目中做单元测试、集成测试,构建的项目中已经默认添加。

我们大多数情况下都是需要测试项目中某一个服务的准确性,这个时候往往需要 Spring Boot 启动后的上下文环境。为了可以在测试中获取到启动后的上下文环境(Beans),spring-boot-starter-test 提 供 了 两 个 注 解 来 支 持 。 测 试 时 只 需 在 测 试 类 的 上面添加@RunWith(SpringRunner.class) 和

@SpringBootTest 注解即可。

package com.ktjiaoyu.demo;
import com.example.demo.service.DemoService;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
​
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
class DemoApplicationTests {
    @Resource
    private DemoService demoService;
    @Test
    public void testSayHello(){
        String msg=demoService.sayHello();
        System.out.println("msg:"+msg);
    }
}

同时在测试类中注入 DemoService,testSayHello测试方法中调用 DemoService的 sayHello() 方法,执行测试方法后,就会发现在控制台打印出了 Spring Boot 的启动信息,说明在执行测试方法之前,Spring Boot 对容器进行了初始化,输出完启动信息后会打印出以下信息,表示测试成功

 针对 Junit 使用的几条建议:

测试方法上必须使用 @Test 进行修饰

测试方法必须使用 public void 进行修饰,不能带任何的参数

新建一个源代码目录来存放我们的测试代码,即将测试代码和项目业务代码分开

测试类所在的包名应该和被测试类所在的包名保持一致

测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖

测试类使用 Tester 作为类名的后缀(不是必须)

测试方法使用 test 作为方法名的前缀(不是必须)

spring-boot-starter-test 中有对 Web 测试的解决方案:MockMvc,MockMvc 实现了对 Http 请求的模拟,能够直接使用网络的形式,转换到Controller 的调用,这样可以使得测试速度更快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且更方便。

spring-boot-starter-test 对测试的支持是全方位的,

·至此,基于 Spring Boot 的 HelloWorld Demo 项目完成。我们发现使用 Spring Boot 之后,

·仅仅三步即可快速搭建起一个 Web 项目:

IDEA 构建项目

代码编写、测试

启动运行

1.3、Spring Boot 的核心

Spring Boot旨在通过最少的Spring前期配置使您尽快启动并运行。它的核心设计思想是:约定优于配置。

1.3.1、约定优于配置

那什么是约定优于配置呢?

约定优于配置(Convention Over Configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量、获得简单的好处,而又不失灵活性。

Spring Boot 体系将约定优于配置的思想展现得淋漓尽致,小到配置文件、中间件的默认配置,大到内置容器、生态中的各种 Starters(启动器) 无不遵循此设计规则。Spring Boot 鼓励各软件组织方创建自己的 Starter,创建 Starter 的核心组件之一就是 autoconfigure 模块,也是Starter 的核心功能,在启动的时候进行自动装配,属性默认化配置。

可以说正是因为 Spring Boot 简化的配置和众多的 Starters 才让 Spring Boot 变得简单、易用、快速上手,也可以说正是约定优于配置的思想彻底落地才让 Spring Boot 走向辉煌。Spring Boot 约定优于配置的思想让 Spring Boot 项目非常容易上手,让编程变得更简单,其实编程本该很简单,简单才是编程的美。

1.3.2、Starter 启动器

Starter 可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包。例如你想使用 Spring Data JPA 访问数据库,只要加入 spring-boot-starter-data-jpa 启动器依赖就能使用了。

Spring Boot Starter 基于约定优于配置的理念来设计,Spring Boot Starter 中有两个核心组件:自动配置代码和提供自动配置模块及其它有用的依赖。也就意味着当我们项目中引入某个Starter,即拥有了此软件的默认使用能力,除非我们需要特定的配置,一般情况下我仅需要少量的配置或者不配置即可使用组件对应的功能。

Spring Boot 整合了主流的开源软件形成了一系列的 Starter,让我们有了一致的编程体验来集成各种软件,Spring Boot 在集成的时候做了大量的优化,让我们在集成的时候往往只需要很少的配置和代码就可以完成。可以说各种 Starter 就是 Spring Boot 最大的优势之一。

1.3.3、自动配置工作原理

不管是约定优于配置,还是 Starter 启动器,都不断的提到自动配置,那么 Spring Boot 的自动配置是如何实现的呢?

1、@SpringBootApplication 注解启动类。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

这个@SpringBootApplication 注解里面又包括三个注解:

@SpringBootConfiguration:SpringBoot 配置;

@EnableAutoConfiguration:自动配置;

@ComponentScan:组件扫描。

@SpringBootConfiguration 注解里又包含@Configuration 注解,使用@Configuration 注解标注的类表示一个配置类,可以替代以前的 xml 配置文件配置 Bean。该注解意味着我们在启动类中可以添加带有@Bean 注解的方法,进行一些配置,所以我们前面在介绍项目结构时提到启动类可用于做一些框架配置。具体的配置我们在后面再详细探讨。

@ComponentScan 注解表示扫描组件,以前在 SSM 中也使用过组件扫描,启用注解配置。启动类上间接包含该注解,表示会扫描启动类所在包及子包下的注解配置。我们前面的 Demo 项目中,使用注解配置了 DemoServiceImpl,该服务在项目中可被找到,被其他 Bean直接依赖注入,正是基于该注解。

@EnableAutoConfiguration 注解从名字就可以看出,Spring Boot 由该注解开启自动配置。

2、@EnableAutoConfiguration 注解开启自动配置

它主要通过 @import 注解导入了 AutoConfigurationimportSelector 类。

@import(AutoConfigurationimportSelector.class)
public @interface EnableAutoConfiguration

AutoConfigurationimportSelector 类有很多方法,但是要让自动配置生效最重要的方法之一就是 getCandidateConfiguration 方法

protected List getCandidateConfigurations(Annotationmetadata metadata,AnnotationAttributes attributes) {
 	List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
	Assert.notEmpty(configurations, "No auto configuration classes found in meta-INF/spring.factories. If you are using a custom 				packaging, make sure that file is correct.");
	return configurations;
}

getCandidateConfigurations 方法会调用 SpringFactoriesLoader.loadFactoryNames()方法,该方法会查找 spring-boot-autoconfigure.jar 中定义的 meta-INF/spring.factories 文件。

 可以看到,在 spring.factories 文件中定义了在应用运行时所有可能自动配置的类。每一个 xxxAutoConfiguration 类都是容器中的一个组件,并都会加入到 Spring 容器中。加入到容器中之后的作用就是用它们来做自动配置,这就是 Spring Boot 自动配置之源。

1.3.4、Starter 的启动过程

Spring Boot 在启动的时候会干这几件事情:

Spring Boot 在启动时会去依赖的 Starter 包中寻找resources/meta-INF/spring.factories 文件,然后根据文件中配置的 Jar 包去扫描项目所依赖的 Jar 包。

根据 spring.factories 配置加载 AutoConfigure 类

根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context

总结一下,其实就是 Spring Boot 在启动的时候,按照约定去读取 Spring Boot Starter 的配置信息,再根据配置信息对资源进行初始化,并注入到 Spring 容器中。这样 Spring Boot 启动完毕后,就已经准备好了一切资源,使用过程中直接注入对应 Bean 资源即可。

1.3.5、Spring Boot 的配置

Spring Boot 对于开发人员最大的好处在于可以对 Spring 应用进行自动配置,而且自动配置功能是没有侵入性的,只是作为一种基本的默认实现。开发人员可以通过定义其他配置 来替代自动配置所提供的功能。这给予了开发人员很大的灵活性,既可以快速的创建一个可以立即运行的原型应用,又可以不断的修改和调整以适应应用开发在不同阶段的需要。Spring Boot 使得这样的切换变得很简单。接下来我们对 Spring Boot 项目中可能会使用到的配置做一个简单的入门。

①、全局配置配置

Spring Boot 全局配置文件:1、application.properties。2、application.yml 配置文件。两个文件的作用:可以覆盖 Spring Boot 自动配置的默认值。

1、application.properties

首先,位置可以出现在以下四个地方(优先级依次降低)

1、项目根目录下的 config 文件夹下

2、项目根目录下

3、classPath 下的 config 文件夹下

4、classPath 下

我们默认在 resources 目录下放置 application.properties。

其次,properties 文件的语法就是 key=value,非常简单,我们以前也多次使用到。

再次,application.propertie 文件主要用来配置数据库连接,日志等等相关配置,也可以覆盖 Spring Boot 自动配置的默认值。

例如,针对今天的 Demo,我们可以对服务器进行一些配置:

 IDEA 在编辑该文件进行配置时,会有相应的代码提示,非常方便。其他常用配置我们将会在后面陆续接触到,具体配置项大家也可以百度自行查找。目前我们的配置将服务器端口号修改为 8080,并且指定项目上下文路径为/demo。配置后访问结果:

另外,application.propertie 文件还可以做一些自定义配置,自定义配置的数据可通过@ConfigurationProperties 和@Value 等注解获取,这个我们在后面讲自定义配置文件时再一起操作。

2、application.yml

YAML 简介:

yml 是 YAML(YAML Ain't Markup Language)语言的文件,以数据为中心,比 json、xml 等更适合做配置文件

yml 和 xml 相比,少了一些结构化的代码,使数据更直接,一目了然。

yml 和 json 呢?没有谁好谁坏,合适才是最好的。yml 的语法比 json 优雅,注释更标准,适合做配置文件。json 作为一种机器交换格式比 yml 强,更适合做 api 调用的数据交换。

YAML 语法:

以空格的缩进程度来控制层级关系。空格的个数并不重要,只要左边空格对齐则视为同一个层级。注意不能用 tab 代替空格。且大小写敏感。支持字面值,对象,数组三种数据结构,也支持复合结构。

字面值:字符串,布尔类型,数值,日期。字符串默认不加引号,单引号会转义特殊字符。日期格式支持 yyyy/MM/dd HH:mm:ss

对象:由键值对组成,形如 key:(**空格)value** 的数据组成。冒号后面的空格是必须要有的,每组键值对占用一行,且缩进的程度要一致,也可以使用行内写法:{k1: v1, ....kn: vn}

数组:由形如 -(**空格)value** 的数据组成。短横线后面的空格是必须要有的,每组数据占用一行,且缩进的程度要一致,也可以使用行内写法: [1,2,...n]

复合结构:上面三种数据结构任意组合

application.yml

我们在 resources 目录下创建 application.yml 配置文件,将刚刚 propertie 文件中的配置信息再配置一次,并修改为不同的值。如下:

 启动项目发现,应用的还是 application.propertie 文件中配置的值,说明两份文件存在相同配置时,优先使用 application.propertie 文件中的配置。将 application.propertie文件中的配置注释,再启动项目,这次应用 application.yml 文件的配置,可以使用http://localhost:8080/test/hello 进行访问了。

注意事项:

1、字符串可以不加引号,若加双引号则输出特殊字符,若不加或加单引号则转义特殊字符;

2、数组类型,短横线后面要有空格;对象类型,冒号后面要有空格;

3、YAML 是以空格缩进的程度来控制层级关系,但不能用 tab 键代替空格,大小写敏感;

4、如何让一个程序员崩溃?在 yml 文件中加几个空格!(>皿<)

②、自定义配置文件

1、@Value 获取自定义配置

在 application.properties 自定义配置:

com.ktjiaoyu.name=czkt
com.ktjiaoyu.email=czkt@ktjiaoyu.com

在 HelloController.java 中通过 @Value 注解获取数据:

@RestController
public class HelloController {
    @Value("${com.ktjiaoyu.name}")
    private String name;
    @Value("${com.ktjiaoyu.email}")
    private String email;
    @Resource
    private DemoService demoService;
​
    @RequestMapping("/hello")
    public String hello() {
        String msg = demoService.sayHello();
        msg+=" "+name+" "+email;
        return msg;
    }
}

访问输出:

 2、@ConfigurationProperties 注解获取并封装自定义配置

使用@ConfigurationProperties 注解,需要在 pom.xml 中添加依赖:


        org.springframework.boot
        spring-boot-configuration-processor
        true

在 com.ktjiaoyu.demo.comm 包下创建 KtjiaoyuInfo.java 类,并添加注解,prefix 指定获取的配置信息的前缀:

@ConfigurationProperties(prefix = "com.ktjiaoyu")
public class KtjiaoyuInfo implements Serializable {
    private String name;
    private String email;
    //省略 setter/getter
}

在启动类上使用@EnableConfigurationProperties(KtjiaoyuInfo.class)

修改 HelloController.java,注入 KtjiaoyuInfo 获取数据:

@RestController
public class HelloController {
    @Resource
    private KtjiaoyuInfo ktjiaoyuInfo;
    @Autowired
    private DemoService demoService;

    @RequestMapping("/hello")
    public String hello() {
        String msg = demoService.sayHello();
        msg += " " + ktjiaoyuInfo.getName() + " " + ktjiaoyuInfo.getEmail();
        return msg;
    }
}

启动访问发现效果一样,能正确获取到数据

3、使用自定义配置文件配置信息

注释 application.properties 文件中的自定义配置,在 resources 目录下创建自定义配置文件 demo.properties,将相应的自定义配置信息放入 demo.properties

在KtjiaoyuInfo.java类上,添加 @PropertySource("classpath:demo.properties") 注解,加载自定义配置文件,并添加 @Configuration 注解,该注解我们下一小节再详细讲解。

将启动类上的 @EnableConfigurationProperties(KtjiaoyuInfo.class) 注解删除。

启动访问发现效果和前面一样,说明自定义配置文件配置信息成功,但是目前自定义配置文件暂不支持 yml 格式的配置。

③、@Configuration 配置

在前面的学习中,我们几次提到了@Configuration 注解,其实@Configuration 注解并不属于Spring Boot 中新增加的,而是在 Spring3.0 中首次出现。

@Configuration 注解作用往小了说,因为它里面又包括@Component,可以简单的用来配置一个类,例如我们上一小节,在 KtjiaoyuInfo.java 类上使用@Configuration 注解,就是将KtjiaoyuInfo 配置为了一个 Bean,另外,该类上的@PropertySource 注解也是 Spring 中的一个注解。

@Configuration 注解作用往大了说可以替代 Spring 原来的 XML 配置文件,相当于 XML 配置文件中的标签;如果说@Configuration 注解相当于 XML 配置文件中的标签,那么标签由谁表示呢?答案是@Bean 注解。

@Configuration+@Bean 两 个 注 解 , 可 以 用 来 代 替 标 签 。 我 们 将@Configuration 注解使用在类上面,相当于 XML 配置文件中的,然后在该类中定义一些方法,这些方法需要有相应的返回类型,在这些方法上使用@Bean 注解,则表示该方法可产生一个 Bean 对象,对应标签的功能。

④、导入 XML 配置文件

在使用 Spring Boot 的时候一般是极少需要添加配置文件的(application.properties 除外),但是在实际应用中也会存在不得不添加配置文件的情况,例如集成其他框架或者需要配置一些中间件等,在这种情况下,我们就需要引入我们自定义的 xml 配置文件了。

使用方式非常简单,定义一个配置类,例如 XxxConfig.java,在该类上使用我们刚刚掌握的@Configuration ,将其配置为一个配置类,然后再添加 @importResource(locations= {"classpath:xxx.xml"})注解即可,xxx.xml 表示我们需要导入的 Spring XML 配置文件。

二、数据访问:JPA 2.1、相关概念

2.1.1、JPA由来

ORM 框架能够将 Java 对象映射到关系型数据库中,能够直接持久化复杂的 Java 对象。ORM 框架的出现,可以让开发者从数据库编程中解脱出来,把更多的精力放在业务模型与业务逻辑上。目前比较流行的 ORM 框架有 MyBatis、Hibernate、Toplink、JDO 等。

在 JPA 规范之前,由于没有官方的标准,使得各 ORM 框架之间的 API 差别很大,使用了某种 ORM 框架的系统会严重受制于该 ORM 的标准。基于此,Sun 引入新的 JPA ORM,主要的原因有:其一,简化现有 Java EE 和 Java SE 应用开发工作;其二,Sun 希望整合 ORM 技术,实现统一的 API 调用接口。

2.1.2、JPA是什么

JPA(Java Persistence API)是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供了一种对象 / 关系映射工具来管理 Java 应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合 ORM 技术,结束现在 Hibernate、Toplink、JDO 等 ORM 框架各自为营的局面。

值得注意的是,JPA 是在充分吸收了现有的 Hibernate、Toplink、JDO 等 ORM 框架的基础上发展而来的,具有易于使用、伸缩性强等优点。从目前的开发社区的反应上看,JPA 受到了极大的支持和赞扬,其中就包括了 Spring 与 EJB 3.0 的开发团队。

注意:JPA 是一套规范,不是一套产品,那么像Hibernate、Toplink、JDO它们是一套产品,如果说这些产品实现了这个JPA规范,那么我们就可以称他们为JPA 的实现产品。

2.1.3、Spring Data JPA

Spring Data 是 Spring 的一个子项目,用于简化数据库访问,包括 NoSQL 非关系型数据库,另外还包括对关系型数据库的访问支持。Spring Data 使我们可以快速简单地使用普通的数据访问技术及新的数据访问技术,Spring Data 会让数据的访问变得更加方便。

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,可以让开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增、删、改、查等在内的常用功能,且易于扩展,学习并使用 Spring Data JPA 可以极大提高开发效率。Spring Data JPA 其实就是 Spring 基于 Hibernate 之上构建的 JPA 使用解决方案,方便在 Spring Boot 项目中使用 JPA 技术。

Spring Data JPA让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它实现。

2.2、快速上手

1、数据库脚本

本课程贯穿项目为 CRM 客户关系管理系统,为了方便演示,使用 crm 数据库中的sys_user 表,脚本如下:

CREATE TABLE `sys_user` (
 `usr_id` bigint(20) NOT NULL AUTO_INCREMENT,
 `usr_name` varchar(50) DEFAULT NULL,
 `usr_password` varchar(50) DEFAULT NULL,
 `usr_role_id` bigint(20) DEFAULT NULL,
 `usr_flag` int(11) DEFAULT NULL,
 PRIMARY KEY (`usr_id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

2、创建项目

打开 IntelliJ IDEA,在菜单栏选择 File 菜单-->New-->Project...-->Spring Initializr,Project SDK 即选择 JDK 的版本,Choose Initializer Service URL 选择 Default:http://start.spring.io。

在下图页面中填写项目相关的信息(注意包名命名为 com.ktjiaoyu.crm,方便在后面的贯穿项目 CRM 中重用),接下来选择项目需要的依赖(Spring Data JPA、MySQL Driver)和SpringBoot 的版本(本教程选择 2.1.15)

 项目 pom.xml 文件如下:


    
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            mysql
            mysql-connector-java
            5.1.18
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

注意需要将 MySQL 驱动包的版本降低为 5.x 版本(我使用的是 5.1.18 版本),否则在 IDEA中运行会报错。

3、application.properties 添加配置信息

#配置数据源相关信息
#MySQL5.x 版本的驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/crm?useUnicode=true&character
Encoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
#配置 JPA(Hibernate)相关信息
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建、更新、验证数据库表结构,有四个值。

create:每次加载 Hibernate 时都会删除上一次生成的表,然后根据 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。

create-drop:每次加载 Hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。

update:最常用的属性,第一次加载 Hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 Hibernate 时根据 model 类自动更新表结构,即使表结构改变了,但表中的行仍然存在,不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。

validate :每次加载 Hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。本次课因为我们已经有数据库了,且属于快速上手,暂时不使用该参数。hibernate.dialect 参数:主要是指定数据库方言(数据库类型)

show-sql 参数:是否在日志中打印出自动生成的 SQL,方便调试的时候查看

format_sql 参数:是否格式化 SQL

4、编写实体类:User.java

package com.ktjiaoyu.crm.entity;
​
import javax.persistence.*;
import java.io.Serializable;
​
@Entity
@Table(name = "sys_user")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "usr_id")
    private Long usrId;
    @Column(name = "usr_name")
    private String usrName;
    @Column(name = "usr_password")
    private String usrPassword;
    @Column(name = "usr_role_id")
    private Long usrRoleId;
    @Column(name = "usr_flag")
    private Integer usrFlag;
    //省略构造方法和 Setter/Getter
}

JPA 注解说明如下:

@Entity(name="EntityName") 必须,用来标注一个数据库表对应的实体,数据库中创建的表名默认和类名一致。其中,name 为可选,对应数据库中一个表,使用此注解标记 Pojo 是一个 JPA 实体。

@Table(name="",catalog="",schema="") 可选,用来标注一个数据库标对应的实体,数据库中创建的表名默认和类名一致。通常和 @Entity 配合使用,只能标注在实体的 class 定义处,表示实体对应的数据库表的信息。

@Id 必须,@Id 定义了映射到数据库表的主键的属性,一个实体只能有一个属性被映射为主键。

@GeneratedValue(strategy=GenerationType,generator="") 可选,strategy: 表示主键生成策略,有 AUTO、IDENTITY、SEQUENCE 和 TABLE 4 种,分别表示让 ORM 框 架自动选择合适的策略,ID 自增长,序列产生主键,表产生主键;generator: 表示主键生成器的名称。

@Column(name = "usr_id", nullable = false, length=32) 可选,@Column 描述了数据库表中该字段的详细定义,这对于根据 JPA 注解生成数据库表结构的工具。

name: 表示数据库表中该字段的名称,默认情形属性名称一致;nullable: 表示该字段是否允许为 null,默认为 true;unique: 表示该字段是否是唯一标识,默认为false;length: 表示该字段的大小,仅对 String 类型的字段有效。

@Transient 可选,@Transient 表示该属性并非一个到数据库表的字段的映射,ORM 框架将忽略该属性。

@Enumerated 可选,使用枚举的时候,我们希望数据库中存储的是枚举对应的String 类型,而不是枚举的索引值,需要在属性上面添加@Enumerated(EnumType.STRING) 注解。

5、编写 Repository

在 com.ktjiaoyu.crm.repository 包下创建 UserRepository.java:

public interface UserRepository extends JpaRepository, JpaSpecificationExecutor {
}

在 IDEA 中打开类 UserRepository,在这个类的大括号内的区域右键单击,选择Diagrams | Show Diagram 选项,即可打开类图,如下:

通 过 上 图 我 们 发 现 JpaRepository 继 承 PagingAndSortingRepository 和QueryByExampleExecutor, PagingAndSortingRepository 类主要负责排序和分页内容,QueryByExampleExecutor 提供了很多示例的查询方法。

因此,继承 JpaRepository 的 Repository 会自动拥有示例查询方法和排序、分页功能。继 续 往 上 查 看 我 们 发 现 PagingAndSortingRepository 又继承了 CrudRepository 。CrudRepository 的源码如下:

@NoRepositoryBean
public interface CrudRepository extends Repository {
     S save(S entity);
     Iterable saveAll(Iterable entities);
    Optional findById(ID id);
    boolean existsById(ID id);
    Iterable findAll();
    Iterable findAllById(Iterable ids);
    long count();
    void deleteById(ID id);
    void delete(T entity);
    void deleteAllById(Iterable ids);
    void deleteAll(Iterable entities);
    void deleteAll();
}

从 CrudRepository 的源码可以看出 CrudRepository 内置了我们最常用的增、删、改、查的方法,方便我们去使用,因为 JpaRepository 继承了 PagingAndSortingRepository,PagingAndSortingRepository 继承了 CrudRepository,所以继承 JpaRepository 的类也默认拥有了上述方法。

因此使用 JPA 操作数据库时,只需要构建的 Repository 继承了 JpaRepository,就会拥有了很多常用的数据库操作方法。

6、编写测试

创建好 UserRepository 之后,当业务代码中需要使用时直接将此接口注入到对应的类中,在 Spring Boot 启动时,会自动根据注解内容创建实现类并注入到目标类中。

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTester {
    @Autowired
    private UserRepository userRepository;

    @Test
    public void testInsert() {//测试新增
        User user = new User();
        user.setUsrName("JPA");
        user.setUsrPassword("123456");
        user.setUsrRoleId(9L);
        user.setUsrFlag(1);
        userRepository.save(user);
    }
    
    @Test
    public void testGet() {//测试按主键查询
        //User user = userRepository.getOne(2L);//不是使用该方法
        User user = userRepository.findById(2L).get();
        System.out.println("usrName:" + user.getUsrName());
    }
}
执行结果:

 至此,Spring Boot 集成 Spring Data JPA 快速上手完成。

2.3、核心功能

2.3.1、基本操作

我们可以将 Spring Data JPA 基本操作分为两种,一种是 Spring Data JPA 默认实现的,另一种是需要根据查询的情况来自行构建。

①、预生成方法

构建的 Repository 继承了 JpaRepository,JpaRepository 接口源码如下:

@NoRepositoryBean
public interface JpaRepository extends PagingAndSortingRepository, QueryByExampleExecutor {
    List findAll();
    List findAll(Sort var1);
    List findAllById(Iterable var1);
     List saveAll(Iterable var1);
    void flush();
     S saveAndFlush(S var1);
    void deleteInBatch(Iterable var1);
    void deleteAllInBatch();
    T getOne(ID var1);
     List findAll(Example var1);
     List findAll(Example var1, Sort var2);
}

同时通过前面的了解,我们知道 JpaRepository 又继承了 PagingAndSortingRepository 和QueryByExampleExecutor,PagingAndSortingRepository 又继承了 CrudRepository。

预生成方法就是我们上面看到的那些方法,因为继承了 JpaRepository 而拥有了父接口的这些内容。所有父接口拥有的方法都可以直接调用,根据方法名也可以看出它的含义。

②、自定义查询

Spring Data JPA 还可以根据接口方法名来实现数据库操作,主要的语法是 findByXX、readByXX、queryByXX、countByXX、getByXX 后面跟属性名称,利用这个功能仅需要在定义的Repository 中添加对应的方法名即可,使用时 Spring Boot 会自动帮我们实现

根据用户名查询用户:

public List findByUsrName(String usrName);

修改、删除、统计也是类似语法:

public Long countByUsrName(String usrName);

基本上 SQL 体系中的关键词都可以使用,如 Like 、IgnoreCase、OrderBy:

public List findByUsrNameLike(String usrName);

编写测试方法测试以上方法,并观察执行结果:

 @Test
 public void testFindByUsrNameLike() {//测试按用户名模糊查询
     List list = userRepository.findByUsrNameLike("%t%");//查询参数必须带% 号
     if (list != null) {
         for (User user : list) {
             System.out.println("usrName:" + user.getUsrName());
         }
     }
 }

 可以根据查询的条件不断地添加和拼接,Spring Boot 都可以正确解析和执行,其他使用示例可以参考下表。

2.3.2、复杂操作

前面小节介绍了 Spring Data JPA 的使用方式和基本操作,常用的增、删、改、查需求 Spring Data JPA 已经实现了。但对于复杂的数据库场景,动态生成方法不能满足,对此 Spring Data JPA 提供了其他的解决方案,这就是这节的主要内容。

①、自定义QL查询

使用 Spring Data 大部分的 查询 都可以根据方法名定义的方式来实现,但是由于某些原因必须使用自定义的 QL 来查询,Spring Data 也可以完美支持。

在 Repository 的查询方法上面使用 @Query 注解,在注解内写 HQL 来查询内容。

@Query(" select u from  User u where u.usrRoleId=?1")
public List findByRoleId1(Long roleId);

注意:HQL 面向实体类和属性,语句中不能写表名和字段名。

当然如果感觉使用原生 SQL 更习惯,它也是支持的,需要再添加一个参数 nativeQuery = true,这时写的是 SQL,面向数据库表和字段名。

 @Query(value = "select * from sys_user where usr_role_id=?1", nativeQuery = true)
 public List findByRoleId2(Long roleId);

两种写法执行时运行的 SQL 语句区别如下,但是结果是相同的。

@Query 上面的 1 代表的是方法参数里面的顺序,如果有多个参数也可以按照这个方式添加 1、2、3....。除了按照这种方式传参外,还可以使用 @Param 来支持命名参数,提高代码可读性和可维护性。

 @Query("select u from User u where u.usrRoleId=:roleId")
 public List findByRoleId3(@Param("roleId") Long roleId);

如涉及到删除和修改需要加上 @Modifying,也可以根据需要添加 @Transactional 对事务的支持、操作超时设置等。

@Transactional(timeout = 10)
@Modifying
@Query("update User u set u.usrName=?1 where u.usrId=?2") 
public int modifyNameById(String usrName, Long usrId);

②、命名QL查询

除了使用 @Query 注解外,还可以预先定义好一些查询,并为其命名,然后在 Repository 中添加相同命名的方法。

定义命名的 Query:

@Entity
@Table(name = "sys_user")
@NamedQueries(@NamedQuery(name = "User.findUsersByName", query = "select u from User u where u.usrName = ?1"))
public class User implements Serializable {
}

通过 @NamedQueries 注解可以定义多个命名 Query,@NamedQuery 的 name 属性定义了 Query 的名称,注意加上 Entity 名称 . 作为前缀,query 属性定义查询语句。

UserRepository 中定义对应的方法:

public interface UserRepository extends JpaRepository{
    //此方法对应 User 实体中的命名查询
    //如果希望根据接口方法名来实现数据库操作,则应命名为:findUsersByUsrName
    public List findByUsrName(String usrName);
}

③、分页查询

Spring Data JPA 已经帮我们内置了分页功能,在查询的方法中,需要传入参数 Pageable,当查询中有多个参数的时候 Pageable 建议作为最后一个参数传入。

@Query("select u from User u where u.usrRoleId=?1")
public Page findPageByUsrRoleId(Long roleId, Pageable pageable);

Pageable 是 Spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则,Page 是 Spring 封装的分页对象,封装了总页数、分页数据等。返回对象除使用 Page 外,还可以使用 Slice 作为返回值。

Page 和 Slice 的区别如下。

Page 接口继承自 Slice 接口,而 Slice 继承自 Iterable 接口。

Page 接口扩展了 Slice 接口,添加了获取总页数和元素总数量的方法,因此,返回 Page 接口时,必须执行两条 SQL,一条复杂查询分页数据,另一条负责统计数据数量。

返回 Slice 结果时,查询的 SQL 只会有查询分页数据这一条,不统计数据数量。

用途不一样:Slice 不需要知道总页数、总数据量,只需要知道是否有下一页、上一页,是否是首页、尾页等,比如前端滑动加载一页可用;而 Page 知道总页数、总数据量,可以用于展示具体的页数信息,比如后台分页查询。

@Test
public void testFindPageByUsrRoleId() {//测试按角色查询用户并分页
        int page = 0, size = 2;//分页要素,页数从 0 开始
        //控制分页数据的排序,可以选择升序和降序
        Sort sort = Sort.by(Sort.Direction.DESC, "usrId");
        //控制分页的辅助类,可以设置页码、每页的数据条数、排序等
        Pageable pageable = PageRequest.of(page, size, sort);
        Page userPage = userRepository.findPageByUsrRoleId(2L, pageable);
        System.out.println("总记录数:" + userPage.getTotalElements());
        System.out.println("总页数:" + userPage.getTotalPages());
        System.out.println("当前页数:" + (userPage.getNumber() + 1));
        System.out.println("每页记录数:" + userPage.getSize());
        System.out.println("当前页记录数:" + userPage.getNumberOfElements());
        for (User user : userPage.getContent()) {//获得查询记录
            System.out.println("usrId:" + user.getUsrId());
        }
}

执行结果:

 ④、复杂查询

我们可以通过 AND 或者 OR 等连接词来不断拼接属性来构建多条件查询,但如果参数大于 6 个时,方法名就会变得非常的长,并且还不能解决动态多条件查询的场景。到这里就需要给大家介绍另外一个利器 JpaSpecificationExecutor 了。

JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封装,可以用于动态生成Query 来满足我们业务中的各种复杂场景。 Spring Data JPA 为 我 们提 供 了JpaSpecificationExecutor 接口,只要简单实现 toPredicate 方法就可以实现复杂的查询。

我们来看一下 JpaSpecificationExecutor 的源码:

public interface JpaSpecificationExecutor {
    //根据 Specification 条件查询单个对象,注意的是,如果条件能查出来多个会报错
    Optional findOne(@Nullable Specification spec);
    //根据 Specification 条件查询 List 结果
    List findAll(@Nullable Specification spec);
    //根据 Specification 条件,分页查询
    Page findAll(@Nullable Specification spec, Pageable pageable);
    //根据 Specification 条件,带排序的查询结果
    List findAll(@Nullable Specification spec, Sort sort);
    //根据 Specification 条件,查询数量
    long count(@Nullable Specification spec);
}

JpaSpecificationExecutor 的源码很简单,根据 Specification 的查询条件返回 List、Page 或 者 count 数据。前面提到的 toPredicate 方法在 Specification 接口中。

@Nullable
Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);

在使用 toPredicate 方法构建复杂查询场景之前,我们需要了解几个概念:

Root root,代表了可以查询和操作的实体对象的根,通过 get("属性名") 来获取对应的值。

CriteriaQuery query,代表一个 specific 的顶层查询对象,它包含着查询的各个部分,比如 select 、from、where、group by、order by 等。

CriteriaBuilder cb,来构建 CriteriaQuery 的构建器对象,其实就相当于条件或者是条件组合,并以 Predicate 的形式返回。

具体案例:

修改 UserRepository 继承 JpaRepository和 JpaSpecificationExecutor

public interface UserRepository extends JpaRepository, JpaSpecificationExecutor {
	//省略相关代码
}

定义 UserService 接口及实现类,在其中定义一个实现动态查询的复杂分页查询来演示JpaSpecificationExecutor 的具体使用。

public interface UserService {
    public Page findPageByMap(Map param, Pageable pageable);
}
@Service("userService")
public class UserServiceImpl implements UserService {
​
    @Resource
    private UserRepository userRepository;
    @Override
    public Page findPageByMap(Map param, Pageable pageable) {
        return userRepository.findAll(new Specification() {
​
            @Override
            public Specification and(Specification other) {
                return null;
            }
​
            @Override
            public Specification or(Specification other) {
                return null;
            }
​
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
                List predicates = new ArrayList<>();
                if(param.get("usrName")!=null) { //动态SQL
                    predicates.add(criteriaBuilder.like(root.get("usrName"),"%"+param.get("usrName")+"%"));
                }
                if(param.get("roleId")!=null){
                    predicates.add(criteriaBuilder.equal(root.get("usrRoleId"),param.get("roleId")));
                }
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        },pageable);
    }
}
  @Test
    public void testFindPageByMap(){
        int page=0,size=2;//分页要素
        //控制分页数据的排序,可以选择升序和降序
        Sort sort = Sort.by(Sort.Direction.DESC, "usrId");
        //控制分页的辅助类,可以设置页码,每页的数据条数、排序等
        Pageable pageable=PageRequest.of(page,size,sort);
        Map param=new HashMap();
        param.put("usrRoleId",2L);
        Page userPage = userService.findPageByMap(param, pageable);
        System.out.println("总记录数:" + userPage.getTotalElements());
        System.out.println("总页数:" + userPage.getTotalPages());
        System.out.println("当前页数:" + (userPage.getNumber() + 1));
        System.out.println("每页记录数:" + userPage.getSize());
        System.out.println("当前页记录数:" + userPage.getNumberOfElements());
        for (User user : userPage.getContent()) {//获得查询记录
            System.out.println("usrId:" + user.getUsrId());
        }
    }

执行结果:

通过本章节学习 Spring Data JPA 也可以看出 Spring Boot 的设计思想,80%的需求通过默认、简单的方式实现,满足大部分使用场景,对于另外20% 复杂的场景,提供另外的技术手段来解决。Spring Data JPA 中根据方法名动态实现 SQL,组件环境自动配置等细节,都是将 Spring Boot 约定优于配置的思想体现的淋淋尽致。

三、数据访问:JPA关联&MyBatis 3.1、JPA多表查询

多表查询在 Spring Data JPA 中有两种实现方式,第一种是创建一个结果集的接口来接收多表联接查询后的结果,第二种是利用 JPA 的关联映射来实现。

3.1.1、数据库表及关系

CRM 数据库中除 sys_user(用户)表外,还包括 sys_role(角色)表。

sys_role(角色)表脚本:

CREATE TABLE `sys_role` (
 `role_id` bigint(20) NOT NULL AUTO_INCREMENT,
 `role_name` varchar(50) DEFAULT NULL,
 `role_desc` varchar(50) DEFAULT NULL,
 `role_flag` int(11) DEFAULT NULL,
 PRIMARY KEY (`role_id`),
 UNIQUE KEY `role_id` (`role_id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

sys_user(用户)表与 sys_role(角色)表之间存在着主外键关系,sys_user(用户)表中的usr_role_id 外键字段对应 sys_role(角色)表中的主键 role_id 字段。

3.1.2、多表连接查询

1、在 com.ktjiaoyu.crm.entity 包下创建 Role 实体类:

@Entity
@Table(name = "sys_role")
public class Role implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;
    @Column(name = "role_desc")
    private String roleDesc;
    @Column(name = "role_flag")
    private Integer roleFlag;
}

2、在 com.ktjiaoyu.crm.vo 包下创建 UserInfo 接口,里面提供所需数据的 getter 方法,其中第 3 章数据访问:JPA 关联&MyBatis包括用户数据和角色名称(roleName)。

public interface UserInfo {
    public Long getUsrId() ;
    public String getUsrName();
    public String getUsrPassword();
    public Long getUsrRoleId() ;
    public Integer getUsrFlag();
    //角色名称
    public String getRoleName();
}

在运行中 Spring 会给接口(UserInfo)自动生产一个代理类来接收返回的结果,代码中使用 getXX 的形式来获取。

3、在 UserRepository 中添加查询方法,返回类型设置为 UserInfo:

@Query("select u.usrId as usrId,u.usrName as usrName,u.usrPassword as usrPassword,u.usrRoleId as usrRoleId,u.usrFlag as usrFlag, " +
            " r.roleName as roleName from User u,Role r where u.usrRoleId=r.roleId and u.usrId=?1")
public UserInfo getUserInfo(Long usrId);

注意:此处为 HQL,必须面向实体和属性编写;获取到的属性必须指定别名。

4、测试验证:

    @Test
    public void testGetUserInfo(){  //测试多表联接查询
        UserInfo userInfo=userRepository.getUserInfo(2L);
        System.out.println("userName:"+userInfo.getUsrName());
        System.out.println("roleName:"+userInfo.getRoleName());
    }

3.1.3、关系映射

在软件开发中,类与类之间最普遍的关系就是关联关系,而且关联是有方向的。以角色(Role)和用户(User)为例,一个角色下有多个用户,而一个用户只能属于一个角色。第 3 章数据访问:JPA 关联&MyBatis

从User到Role的关联就是多对一关联,这就意味着每个User对象只会引用一个Role对象,因此在 User 类中应该定义一个 Role 类型的属性,来引用所关联的 Role 对象。

从 Role 到 User 是一对多关联,这意味着每个 Role 对象会引用一组 User 对象,因此在 Role类中应该定义一个集合类型的属性,来引用所有关联的 User 对象。

如果仅有从 User 到 Role 的关联,或者仅有从 Role 到 User 的关联,就称为单向关联。如果同时包含两种关联,就称为双向关联。

数据之间一对多或者多对一的关系,通常涉及两张表,“多”方表通过外键引用“一”方表的主键来实现一对多的关联。

本小节结合具体的例子来介绍如何映射以下关联关系:

以 User 和 Role 为例,介绍如何映射多对一单向关联映射

以 Role 和 User 为例,介绍如何映射一对多双向关联映射

①、单向多对一关联

首先咱们以 User 和 Role 为例,介绍通过使用外键来建立多对一的单向关联关系。建议大家新建一个 Spring Boot 项目,按前面小节准备好初始环境及 User 和 Role 实体。

1、修改 User 实体,添加关联 Role 对象:

@Entity
@Table(name = "sys_user")
public class User implements Serializable {
  @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "usr_id")
    private Long usrId;
    @Column(name = "usr_name")
    private String usrName;
    @Column(name = "usr_password")
    private String usrPassword;
    
    @ManyToOne(targetEntity = Role.class)
    @JoinColumn(name = "usr_role_id")
    private Role role;
    @Column(name = "usr_flag")
    private Integer usrFlag;
    //省略其他代码
}

修改过后的 User 实体类中,将原 usrRoleId 属性注释了,新增了 Role role 属性,用来表示多对一关联,另外在该属性上,我们添加了@ManyToOne 和@JoinColumn 两个注解。

@ManyToOne 注解映射多对一关联关系,targetEntity 属性表示关联实体类型,可省略;

@JoinColumn 注解映射关联的外键字段,如不指定,则生成一张新表维护两个对象之间的关系。

2、创建 UserRepository

public interface UserRepository extends JpaRepository {
}

3、测试基本的 CRUD

@Test
    public void testGet() { //测试按注解查询用户,关联获得角色数据
        User user = userRepository.findById(4L).get();
        System.out.println("usrName:" + user.getUsrName());
        System.out.println("roleName:"+user.getRole().getRoleName());
    }

执行结果:

 从输出的 SQL 语句可以发现,执行查询 User,关联查询了 Role 数据,并将 Role 相关数据自动封装在 User 对象的 role 属性中。

其他增删改方法的测试请自行完成。

②、双向一对多关联

在前面的示例中,咱们已经建立了 User 类到 Role 类的单向多对一关联,下面咱们再继续通过示例完成 Role 类到 User 类的双向一对多关联。

1、修改 Role 实体类,添加关联的 User 对象集合:

@Entity
@Table(name = "sys_role")
public class Role implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;
    @Column(name = "role_desc")
    private String roleDesc;
    @Column(name = "role_flag")
    private Integer roleFlag;
​
    @oneToMany(targetEntity = User.class,fetch = FetchType.LAZY,cascade = CascadeType.PERSIST,mappedBy = "role")
    private Set users=new HashSet();
}

修改过后的 Role 实体类中,新增了 Set users 属性(使用 Set 集合可避免重复对象的问题),用来表示一对多关联,在该属性上,我们添加了@oneToMany 注解,映射一对多关联关系。

targetEntity 属性表示关联的实体类型

fetch 属性表示加载策略,FetchType 取值有 LAZY 及 EAGER,LAZY 表示延迟加载,EAGER 表示立即加载。@ManyToOne 注解也包含该属性,且默认值为 EAGER,表示立即加载,所以查询 User 时通过左外关联立即获取 Role 数据;@oneToMany 注解该属性默认值为 LAZY。

cascade 属性表示级联操作,CascadeType 取值有 PERSIST、REMOVE、ALL……等。PERSIST 表示级联持久化(保存)操作,REMOVE 级联删除,ALL 级联所有操作。@ManyToOne 注解也包含该属性,但一般不在多的一方进行级联操作。

mappedBy 属性用来设置对象之间的关系维护方(关系维护通过操作外键完成)。如不指定 mappedBy 属性,则对象均由自己维护关系(外键),操作一方对象时,会额外产生更新外键的 SQL 语句。所以一般在@oneToMany 注解中指定 mappedBy 属性,且属性值为多方对象中关联的一方属性名,并且此时一方实体中不能添加@ JoinColumn 注解

2、创建 RoleRepository

public interface RoleRepository extends JpaRepository {
}

3、测试查询

    
    @Test
    public void testGet(){
        Role role=roleRepository.findById(1L).get();
        System.out.println("roleName:"+role.getRoleName());
        System.out.println("users.size:"+role.getUsers().size());
    }

测试结果:

 观察输出,可以发现查询了 Role 对象,输出了 roleName,但是随后输出关联 User 集合数量时报错了,错误原因是 no Session,因为我们在实体类中配置映射时使用的是延迟加载,要解决该问题,我们将在后续课程中,在 Web 环境中通过 AOP 的方式解决,此处大家可以将加载策略设置为立即加载解决。

4、测试级联操作:级联新增

    
    @Test
    public void testAdd(){  //测试级联新增
        Role role=new Role("测试角色","演示级联新增角色和用户",1);
        User user1=new User(role,"测试角色1","123456",1);
        User user2=new User(role,"测试角色2","123456",1);
        //将 User 添加到 Role 的 users 集合中,进行级联新增
        role.getUsers().add(user1);
        role.getUsers().add(user2);
        roleRepository.save(role);//新增角色的同时新增关联的用户
    }

测试结果:

运行测试会报错,提示死循环,原因是 Lombok 插件造成的,只能遗憾的将实体类改回手动生成 setter/getter 方法,再次运行,结果如下:

 发现生成三条插入语句,数据库中不但新增了 Role 对象,还新增了 User 对象。

5、测试级联操作:级联删除

将级联属性的值改为 REMOVE,再来测试级联删除,将刚刚创建的 Role 对象及关联的User 对象一次删除。

    @Test
    public void testDelete(){   //测试级联删除
        //先使用 getOne 方法获取到 Role 的引用,然后调用 delete 方法删除
        Role role=roleRepository.findById(10L).get();
        roleRepository.delete(role);
    }

发现先查询了 Role 对象,再查询 User 对象,然后会将相关的对象全部删除。

至此,JPA 关联映射相关操作就给大家介绍到这里。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号