近来在做web开发,以spring boot作为web服务器,从0开始一些简单的学习。

下面是一些基本的spring boot概念,在学习时逐步记录下来。


SpringBoot入门

存在意义

  • Spring boot是spring全家桶的默认简装版,简化我们使用spring全家桶的存在。
    • 基本IoC,AOP使用的是spring framework
    • 安全用spring security
    • 数据访问使用 spring data,支持关系型非关系型数据库
    • 分布式部署可用spring cloud
  • Spring boot也是桥接spring cloud的分布式解决方案的桥梁。
  • Spring boot目的可以快速构建一个微服务,然后通过spring cloud进行协调,再通过spring cloud data flow进行connect连接这些微服务。这就是spring官方的指导方案。

优点

  • 使用非常方便,可以快速搭建spring项目
  • 嵌入servlet容器,不用打war包,直接打成jar包独立运行
  • starters启动器,都可以帮我们做好自动依赖和版本控制
  • 不需要XML配置,也不进行代码生成,使用时很方便
  • 有生产环境的运行时监控
  • 支持热启动
  • 天然集成云计算

缺点

  • 入门容易,精通难,集成度高,不容易了解底层
  • 会自动绑定一堆依赖,会有额外的一些冗余

前提基本知识

  • Spring框架的使用经验
  • 使用Maven进行项目构建和依赖管理经验
  • 熟练使用eclipse/IDEA

HelloWorld

  • 设置好maven依赖,直接使用springInitializr的 start.spring.io 框架创建即可

  • 创建MainApplication.java 和 HelloController.java

  @SpringBootApplication
  public class Demo1Application {
      public static void main(String[] args) {
          SpringApplication.run(Demo1Application.class, args);
      }
  }
  // RestController == Controller + ResponseBody
  @RestController
  public class HelloController {
      @RequestMapping("/hello")
      public String hello() {
          return "Hello Spring Boot";
      }
  }
  • 运行,然后本地浏览器 localhost:8080/hello测试

  • 创建可执行jar包,在pom.xml中加入

      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
              </plugin>
          </plugins>
      </build>
  • 使用 maven 的lifecycle进行package打包,会在target目录下获得一个可执行 java 包,然后使用
  $java -jar xxoo.1.0.jar

版本依赖

  • 默认我们pom中会有下面的父项目依赖,spring-boot-starter-parent,打开它可以看到它依赖于 spring-boot-dependencies, 再打开dependencies可以看到我们内部依赖的全部版本号。
  • 所以我们大部分情况下,加入新依赖,是无需额外制定依赖版本的。
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  • 所以我们大部分情况下,加入新依赖,是无需额外制定依赖版本的。

依赖库

  • 上述默认情况,我们pom中会依赖 spring-boot-starter-web,我们打开它,可以看到内部依赖了更多的库,包括tomcat, json等等。
  • 这个 spring-boot-starter-web 我们称为启动器。spring boot有更多的默认启动器,包括:
  • 我们也可以开发定制自己的starter,这些starter的目的就是便于依赖管理,统一配置,使用更简单。

程序入口详解

我们看到了,spring boot的程序入口有个重要注解 @SpringBootApplication,这个注解跟进去看的话会发现它做了如下几件事:

  • 首先它是一个配置类,本身包含了 @configuration 注解(而@configurarion配置类本身也是一个@component 组件类)
  • 其次,它是一个@EnableAutoConfiguration 开启了自动配置功能类,最终实现的是,把@SpringBootApplication这个注解的入口类,所在的包内全部组件进行自动扫描并导入。
    • 假设 com.fk.app.java是这个入口类的话,那么 com.fk 包下全部组件会被自动扫描导入。
  • 然后,它自动 @import 了spring-boot-autoconfigure的包,将这些包自动导入容器中。
    • 目录在:ExternalLibraries -> Maven:org-springframework.boot:spring-boot-autoconfigure -> META-INF -> spring.factories 的 org.springframework.boot.autoconfigure.EnableAutoConfiguration中。

核心目录说明

- src
    - main
        - java
            - com.xxoo.xxproject 源代码
        - resources
            - static 一些静态资源文件,例如css, js, html, image等,包括vue工作路径一般在这里
            - template 因为jar包内嵌tomcat,所以默认不支持jsp,但spring boot支持模板引擎,例如freemarker, thymeleaf
            - application.peoperties 这是spring boot的配置文件,可以覆盖修改默认设置。(例如添加一行 server.port=8081 ,那么启动后tomcat就会监听8081)
    - test 测试目录,其包结构通常和src类一致。

SpringBoot配置

SpringBoot配置

默认支持类型

两种全局配置文件类型

  • application.properties
  • application.yaml 或 application.yml

配置文件的作用

  • 对sprint boot的一些默认配置值进行修改

存放位置

  • src/main/resources目录下
  • 类路径/config 下

绑定容器对象

@ImportResource(“classpath:xxx.xml”)

它的注解中应该是xml格式文件,且该格式文件进行管理对象绑定时,一般如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd" >
        
    <context:property-placeholder location="classpath:/config.properties"/>
</beans>

它是spring支持的方式,因为sping boot 并不能自动识别spring的xml配置,于是就需要importresource来特殊指定xml配置文件名。

它一般用在启动类上,进行启动配置指定。

@PropertySource(“classpath:xxx.properties”)

这个直接后面一般是 .properties或者 .yml文件,用来给java bean注入绑定对象。

它一般放置在java bean的类名上。

它是spring boot推荐的方式,更简单。

@Bean

它是一种更简单的给容器注入bean对象的方式。

它也是sprint boot 推荐的方式,即,全注解的方式来进行配置,而不是使用xml配置的方式。所以,如下面代码所示,专门创建一个 @configuration 配置类,然后使用 @bean来进行注入,是最佳方式。而不是xml 的

@Configuration
public class MyAppConfig {
    //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
    // 之后,这个 HelloService组件 就被容器管理了,getbean("helloService001")就可以获得bean了
    @Bean
    public HelloService helloService0011(){
        System.out.println("配置类@Bean给容器中添加组件");
        return new HelloService();
    }
}

配置绑定

将配置从yaml文件中进行获取,则使用 @ConfigurationProperties 进行配置绑定。例如

@Component      // 注意,如要使用配置绑定功能,必须是容器中的组件
@ConfigurationProperties(prefix="personXXOO")  // 将会从 personXXOO 节点进行读取填充
public class Person{
    private String name;
    private Integer age;
    private List<Object> lists;
    private Dog dog;
}
public class Dog{
    private String name;
    private Integer age;
}

配置文件

personXXOO:
   name: 张三
   age: 20
   lists:
       - item1
       - item2
   dog:
       name: 小白
       age: 1

注意:使用该方式,需要pom导入一个配置文件处理器。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

这里也可以使用 application.properties 进行配置,例如下面也是可以的

personXXOO.name=张三
personXXOO.age=20
personXXOO.lists=item1,item2
personXXOO.dog.name=小白
personXXOO.dog.age=1

配置补充

配置中可以使用的特殊符号

例如:

personXXOO.name=张三${random.uuid}                # 这个是随机
personXXOO.age=${random.int[0, 100]}             # 这个也是随机
personXXOO.lists=item1,item2
personXXOO.dog.name=${personXXOO.name}的狗        # 这个是值引用
personXXOO.dog.age=${personXXOO.age:20}          # 这个是 : 后面加 默认值。

Properties配置编码问题

上面的properties里面可能有编码乱码问题。因为intellijIdea默认properties是ASC2编码,我们需要在setting->editor->file encodings-> 下方,开启 transparent native-to-ascii conversion.

配置绑定的其他方式

之前我们提过使用

@ConfigurationProperties(prefix= "xxxoo")

的方式为一个类的值进行配置绑定。

我们还可以使用传统的XML方式进行配置绑定,如下

<bean class="Person">
    <property name="age" value=12></property>
</bean>

我们还可以使用value注解来绑定值,如下

@Component
public class Person{
  @Value("${personXXOO.name}")  // 这个等同于XML中的value赋值,也就是从配置文件中找到personXXOO.name并将其值赋给 name 变量。注意,有 $ 号
  private String name;
  @Value("true")    // 也可以直接赋值,不从配置文件读取
  private Boolean isBoss;
  @Value(#{11*2})   // 支持springEL语法,可以用 # 进行运算
  private Integer age;
}
@ConfigurationProperties @Value
用法 可以一次就批量注入整个配置文件的属性 需要一个个的指定
灵活性 支持松散语法绑定,配置中的 last-name 变量名等同于 lastName 变量名 不支持松散语法绑定
SpEL 不支持 #{11*2} 这种方式 支持
JSR303校验 支持 ,支持Validated。即标注变量格式 @Email,要求变量格式必须是邮箱,会自行进行校验 不支持
配置文件 两边都支持yml, yaml, properties 支持yml, yaml, properties
复杂类型 可以支持,支持map,list等 不支持map,list等
使用情况 如果对一个bean进行全部配置,则使用这个省事 特殊使用个别配置的时候,尽量使用这个

多环境配置

  • 存在目的

  • 多环境(例如 开发/运营 环境)之间,快速的进行参数配置的切换

  • 方式

    1. 多profile文件的方式。注意文件名的格式必须为application-xxoo.properties的格式。例如 application-dev.properties, application-prod.propertiese

    2. 使用yaml多文档的方式。例如

     ---
     server:
       port: 8080
     spring:
       profiles:
         active: dev
     ---
     server:
       port: 8081
     spring:
       profiles: dev
    
  • 激活指定环境的方式

    1. 使用配置文件方式

      1. properties文件中都默认会读取标准的application.properties内的配置。除非指定了spring.profiles.active = xxoo

      2. yaml文件中默认读取第一段不做指定的配准配置。除非指定了

      spring: profiles: active: dev

    2. 使用命令行 –spring.profiles.active=xxoo

    命令行配置,优先于配置文件,将会导致配置文件中的设定无效化。

    1. 【不推荐】使用jvm参数 –spring.profiles.active=xxoo

多配置的调用顺序

  • 首先命令行为最高优先
  • 【不推荐】其次是修改JNI环境,Java系统环境,操作环境变量等
  • 加载jar包外部的 properties, yaml文件
  • 加载jar包内部(src目录内)的properties, yaml文件
  • 加载@configuration注解类上的 @propertySource 属性值

SpringBoot日志

可选框架

日志抽象层 日志的实际实现
slf4J,JCL,Jboss-logging (推荐slf4j) log4j, JUL, jog4j2, logback (推荐logback)

springboot使用的默认也是 slf4j 和 logback

JUL是java.util.logging的简写

配置和使用slf4J

基本准则

  1. 因为slf4j是抽象层,所以我们应该调用slf4j这种抽象层,而不是调用实现层函数。

  2. 配置文件,使用实现层本身的配置方式,而是不使用slf4j的。

    1. 例如logback的配置文件 logback-spring.xml/logback.xml/logback-spring.groovy/logback.groovy
    2. log4j2的配置文件 log4j2-spring.xml/log4j2.xml
    3. JUL的配置文件 logging.properties

这些都是不变的。

  1. 我们可以使用springboot的配置application.properties/application.yaml进行log配置;也可以使用上面描述的logback的配置文件进行log配置,这样可以更加灵活的指定每个jar库自己的log格式。

  2. logback自己的配置文件比spring boot的配置文件优先度更高。

  3. logback自己的配置文件分为两种:

    1. logback.xml 这个被日志框架slf4j直接识别,不被spring boot识别

    2. logback-spring.xml 这个更强大,被spring boot识别,然后通知slf4j。所以可以指定不同的环境使用不同的格式,例如

      <springProfile name="dev">
          <pattern>%d{yyyy-MM-dd} [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
      </springProfile>
      <springProfile name="!dev">
          <pattern>%d{yyyy-MM-dd} %-5level %logger{20} - %msg%n</pattern>
      </springProfile>
    

    上面的配置,则表示在 Dev 环境模式下,使用第一套格式输出,否则使用第二套。

    (spring更改模式是 –spring.profiles.active=xxoo, 上文有说明)

使用代码

import org.slf4j.Logger
import org.slf4j.LoggerFactory
    
public class HelloFK{
    public static void main(String[] args){
        Logger logger = LoggerFactory.getLogger(getClass());
        logger.trace("HelloWorld");
        logger.debug("HelloWorld");
        logger.info("HelloWorld");  // 默认使用 info 级别日志
        logger.warn("HelloWorld");
        logger.error("HelloWorld");
    }
}

logback配置

- 【输出日志级别】loggging.level.com.freeknight=trace
- 【控制台格式】logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
- 【文件格式】logging.pattern.file=
- 【输出文件路径】logging.path=/mydir/log
  • - 不指定路径,则在当前项目下生成 mytest.log - 也可指定绝对路径和相对路径 - 当设置了logging.file,那么logging.path将无效化 - 推荐使用logging.path

结构图

  • [标准模式] APP->slf4j-api.jar-> logback-core.jar+logback-classic.jar

  • [底层Log4j模式] APP->slf4j-api.jar-> slf4j-log412.jar->log4j.jar

  • [底层JUL模式] APP->slf4j-api.jar-> slf4j-jdk14.jar->JVM runtime

其中 slf4j-api是抽象API层。 slf4j-jdk14.jar/slf4j-log412.jar 是适配adaptation层,最后的log4j.jar,logback. jar是实现层

配置文件,使用实现层本身的配置方式,而是不使用slf4j的。

遗留问题

springboot使用的是 slf4j 和 logback

ring底层使用的是commons-logging

Hibernate底层使用的是 jboss-logging

所以结构图如下

app -> commons-logging api
    -> log4j api
    -> JUL api
    -> slf4j-api.jar -> logback-classic.jar+logback-core.jar

不统一的话,首先难以管理,其次还容易出现库版本冲突

进行统一

我们想统一使用 slf4j 和 logback 的话,需要做如下操作:

  • 去除原本的api库,例如 commons-logging api, log4j api, JUL api 等
    • 这一步可以通过 intelljIdea的 diagram->Show dependencies,从依赖图中进行删除
  • 添加中间转换库
    • 使用 jcl-over-slf4j.jar 替换commons-logging api
    • 使用 log4j-over-slf4j.jar 替换log4j api
    • 使用 jul-to-slf4j.jar 替换JUL api
    • 这一步可以通过pom.xml手动添加依赖
  • 正常导入slf4j的实现日志类,例如我们用的logback,则正常导入 logback-classic.jar+logback-core.jar

结构图如下

app -> jcl-over-slf4j.jar -> slf4j-api.jar -> logback-classic.jar+logback-core.jar
    -> log4j-over-slf4j.jar -> slf4j-api.jar -> logback-classic.jar+logback-core.jar
    -> jul-to-slf4j.jar -> slf4j-api.jar -> logback-classic.jar+logback-core.jar
    -> slf4j-api.jar -> logback-classic.jar+logback-core.jar

spring boot的log机制

  • 默认pom.xml中加载spring-boot-starter中,加载了 spring-boot-starter-logging依赖

  • spring-boot-starter-logging 里面自动依赖了jcl-over-slf4j.jar, log4j-over-slf4j.jar, jul-to-slf4j.jar,这三个库的底层依赖都是 slf4j-api

  • 这个 slf4j-api 的实现是依赖 logback-classic.jar, loggback-classic的底层依赖是 logback-core.jar

  • 当我们引入第三方库的时候,记得要移除其自身的log包,例如,我们引入 spring 的话,则需要做如下配置

  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring.core</artifactId>
      <exclusions>
          <exclusion> <!-- 这里我们在移除spring自带的commmons-logging -->
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging</artifactId>
          </exclusion>
      </exclusions>
  </dependency>

结论

  1. springboot底层是slf4j+logback

  2. springboot自身已经帮我们添加了中间转换包,无需额外再pom.xml添加

  3. 但第三方包内自带的log实现库,需要我们在添加第三方包的时候,手动移除那些不需要的log实现库

SpringBoot的web开发

静态资源映射规则

  1. 直接在static目录里加入自己的css或js,其合法目录在

    1. src/main/resources/META-INF/resources/
    2. src/main/resources/resources/
    3. src/main/resources/static/
    4. src/main/resources/public/
    5. src/main/resources/
  2. 使用webjars将第三方资源使用jar包的方式引入项目,例如引入jquery, vue等

    1. pom.xml中加入需要的js依赖 (下面的配置可以直接从 www.webjars.org 官网获取)
        </dependencies> 
              <!-- 引用jquery -->
              <dependency>
                  <groupId>org.webjars</groupId>
                  <artifactId>jquery</artifactId>
                  <version>3.1.1</version>
              </dependency>
              <dependency>
                  <groupId>org.webjars.npm</groupId>
                  <artifactId>vue</artifactId>
                  <version>2.6.11</version>
              </dependency>
          </dependencies>
    
    1. 然后在资源文件夹下新建index.html,合法的html存放的资源文件夹包括:

      1. src/main/resources/META-INF/resources/
      2. src/main/resources/resources/
      3. src/main/resources/static/
      4. src/main/resources/public/
      5. src/main/resources/

    默认使用的首页是 index.html ,默认使用的网页图标识 favicon.ico

    1. 如果是spring mvc,则需要实现 webMvcConfigurer接口来定义这些静态文件的访问入口,如下
      @Configuration
      public class WebConfig implements WebMvcConfigurer {
          @Override
          public void addResourceHandlers(ResourceHandlerRegistry registry) {
              registry
                  .addResourceHandler("/webjars/**")
                  .addResourceLocations("classpath:/META-INF/resources/webjars/");
          }
      }
    

    而spring boot项目则无需额外处理,底层自动做了 classpath:/META-INF/resources/webjars/ 到 /webjars/** 的映射。

    1. 我们创建自己的html时,里面的引用的css,js文件路径则是
      <script src="/webjars/jquery/3.1.1/jquery.min.js"></script> 
      <script src="/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js"></script> 
      <title>WebJars Demo</title> 
      <link rel="stylesheet" href="/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css" /> 
    
    • 如果我们想省略版本号,动态获取最新版本,则可以在pom.xml中增加配置

      <dependency>  
          <groupId>org.webjars</groupId>  
          <artifactId>webjars-locator</artifactId>  
          <version>0.31</version>  
      </dependency> 
      
    • webjars-locator会自动更新wabjars版本号,我们就可以省略wabjars的更新和路径了,我们可以设置html的加载如下 (下面都去除了版本号)

      <script src="/webjars/jquery/jquery.min.js"></script> 
      <script src="/webjars/bootstrap/js/bootstrap.min.js"></script> 
      <title>WebJars Demo</title> 
      <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.min.css" />
      
    1. 访问本地 localhost:8080 即可看到效果

SpringBoot的数据访问

这里一般推荐使用docker创建mysql容器,用navicat查看mysql的更变会比较方便。

基本概念

  • 数据源,数据库,连接池, 数据库驱动 分别是什么。
    • 用户程序 访问【数据源】
    • 【数据源】可以通过jdbc创建一个连接;也可以创建多个连接,然后存放到【连接池】,用的时候再从【连接池】中获取连接
    • 【连接池】负责维护这些jdbc连接
    • jdbc连接通过不同的【数据库驱动】访问不同类型的【数据库】
  • JDBC是什么
    • JDBC是一套规范的API接口,用来方便用户使用同一套接口访问多种不同的数据库。
    • 在不同的数据库驱动对应关系下,可以访问不同类型的数据库,并对其进行创建连接,销毁连接,sql执行等操作。
  • Druid是什么
    • Druid是一个连接池 + 一个内置JDBC组件库 + 一些额外监控支持功能包。
  • MyBatis是什么
    • MyBatis是一个JDBC的封装库,使我们更方便使用JDBC。
    • 它没有实现JPA接口
  • JPA是什么
    • JPA是一个ORM思想的接口框架,存在于用户应用程序和JDBC之间的一个中间层。它让用户开发时使用ORM面对对象的开发机制,它自己来进行对JDBC的映射。
  • Hibernate是什么
    • 它是一种ORM框架,实现了JPA的接口。它也是spring data 默认实现JPA层的库。

sprint data

SpringBoot默认采用SpringData的方式统一访问nosql和sql。

SpringData项目的目的是为了简化构建基于spring框架应用,所创建的一种简化数据访问的技术。

SpringData项目特点包括:

  • 默认支持很多数据访问方式,包括jpa,jdbc, keyvalue, mangoDB, redis, solr, neo4j, elasticsearch, cassandra等。
    • 使用任何一个数据访问,只需要导入对应的starters. 一般命名为:
    • 特殊的有原生的jdbc,第三方的mybatis,其starter为
spring-boot-starter-data-jpa
spring-boot-starter-data-redis
spring-boot-starter-data-mangodb
spring-boot-starter-jdbc    【原生】
mybatis-sprint-boot-starter 【第三方】
  • 它为我们对数据库访问提供了统一的CRUD(增删改查),排序和分页的功能接口。
    • Repository 统一接口
    • RevisionRepository 基于乐观锁机制,继承Repository
    • CrudRepository 基本的CRUD操作,继承Repository
      • PagingAndSortingRepository 基本CRUD以及分页操作,继承CRUDRepository
      • JPARepository,继承PagingAndSortingRepository
  • 它为我们进行数据访问提供了统一的模板类,例如
    • RedisTemplate
    • MangoTemplate

jdbc

使用整合流程
  1. 自动导入jdbc的starter,以及mysql驱动依赖

  2. 进行数据源的配置编写

   spring:
     datasource:
       username: root
       password: 123456
       url: jdbc::mysql://192.168.12.22:3306/myDB
       driver-class-name: com.mysql.jdbc.Driver
  1. 代码进行连接访问测试
   @RunWith(SpringRunner.class)
   @SpringBootTest
   public class MyTest{
       @Autowired
       DataSource dataSource;
       
       @Test
       public void TestContextLoader(){
           System.out.println(dataSource.getClass());
           Connection connection = dataSource.getConnnection();
           System.out.println(connection);
           connection.close();
       }
   }
   
   /* 将打印出
   class org.apache.tomcat.jdbc.pool.DataSource     # 默认数据源是tomcat的
   com.mysql.jdbc.JDBC4Connection@ba56f951          # 证明连接成功
   */
  1. spring boot默认支持三种数据源,也可以自己指定数据源
   org.apache.tomcat.jdbc.pool.Datasource
   HikariDataSource
   BasicDataSource
  1. 默认sql语句执行机制

    • 默认在程序启动时会先执行以下sql语句,用来创建表结构
   schema.sql
   schema-all.sql
   以及
   schema-*.sql  // 其中的 * 必须是当前环境名,例如 dev, prod 等
  • 然后会执行如下sql语句,用来初始化表数据
   data.sql
   data-all.sql
   以及
   data-*.sql // 其中的 * 必须是当前环境名,例如 dev, prod 等
  • 如果不想遵循上述规则,可以在配置中进行指定

     spring:
       datasource:
         username: root
         password: 123456
         url: jdbc::mysql://192.168.12.22:3306/myDB
         driver-class-name: com.mysql.jdbc.Driver
         schema:  # 程序每次启动时,都会运行这两个sql用来建表
           - classpath: sql/mySchema1.sql
           - classpath: sql/mySchema2.sql
    
  1. 自动配置了jdbcTemplate,可用来操作数据库。
   // 设立我们写个controller用来做测试。
   @Controller
   public class HelloController{
       @Autowired
       JdbcTemplate jdbcTemplate;
       
       @ResponseBody
       @GetMapping("/query")
       public Map<String, Object> map(){
           // 主要就是这里使用jdbcTemplate进行访问
           List<String, Object> list = jdbcTemplate.quertForList("select * FROM myTable");
           return list.get(0);
       }
   }
   
   /* 访问 localhost:8080/query/ 将打印出数据库中第一行的数据
   {
     id: 1,
     colume2: myContext...
   }
   */
使用druid数据源

​ 实际开发中,我们很少直接使用 jdbc 默认的数据源org.apache.tomcat.jdbc.pool.Datasource,通常会考虑使用 druid数据源,因为它有更多的监控和扩展功能。

​ 想使用druid数据源,需要做如下操作:

  • 引入druid

    • 访问 maven 仓库: mvnrepository.com 查找druid,找到引入方式

    • 在pom.xml引入库:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.8</version>
    </dependency>
    
    • 修改applation.properties/yaml ,指定数据源
    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc::mysql://192.168.12.22:3306/myDB
        driver-class-name: com.mysql.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
  • 添加并使用jdbc的各种配置

    • 先编写配置
    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc::mysql://192.168.12.22:3306/myDB
        driver-class-name: com.mysql.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        initialSize: 5
        minIdle: 5
        maxAction: 20
        maxWait: 60000
        ....等等
    
    • 此时上述配置无效,必须我们手动为其指定配置类
    @Configration
    public class DruidConfig{
        @ConfigrationProperties(prefix="spring.datasource") // 这里指定的从application.peoperties文件中读取 spring.datasource 组下的配置
        @Bean
        public DataSource druid(){
            return new DruidDataSource();
        }
    }
    
  • 开启druid的监控

    • 配置一个管理后台的servlet,并初始化一些值,放到DruidConfig类中
    @Bean
    public ServletRegisterationBean statViewServlet(){
        ServletRegisterationBean bean = new ServletRegisterationBean(new StatViewServlet,  "/druid/");
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin"); // loginUsername这些是在druid内定义的
        initParams.put("loginPassword", "123456");
        bean.setInitParameters(initParams);
        return bean;
    }
    
    • 配置一个监控的filter,并初始化一些值,放到DruidConfig类中
    @Bean
    public FilterRegisterationBean webStatFilter(){
        FilterRegisterationBean bean = new FilterRegisterationBean();
        bean.setFilter (new WebStatFilter());
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/");
        bean.setInitParameters(initParams);
        bean.setUrlPatterns(Arrays.asList("/*"));   // 拦截全部请求
        return bean;
    }
    
    • 上面两部配置完毕后,就可以通过访问 localhost:8080/duird/ 访问到监控页面。

Mybatis

基本概念

它是对JDBC的进一步封装,但MyBatis并不是标准的ORM实现,它主要是帮我们减少JDBC的一些操作。

例如一般JDBC操作有如下四步:

//获取连接
connection = DriverManager.getConnection(url, username, password);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setAutoCommit(false);

//准备sql
String sql = "select * from book where book_id = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, 4);

//执行sql
ResultSet resultSet = statement.executeQuery();
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
while (resultSet.next()) {
    for (int i = 1; i <= columnCount; i++) {
        System.out.println(i + " label:" + rsmd.getColumnLabel(i) + " type:" + rsmd.getColumnType(i));
    }
}

//提交关闭
connection.commit();
connection.close();

使用MyBatis的话,则可以仅仅将注意力集中在SQL语句部分,对于前后的获取连接关闭连接都可以不用关心了。

使用整合流程
  • 导入MyBatis的starter,以及JDBC以及mysql驱动。

  • 修改配置,修改数据源,指定数据源配置等(上面jdbc的那些步骤)

  • 在数据库中创建表(可以使用 schema.sql)

  • 创建javaBean

    • 假设我们在数据库中创建了两个表
    • Employee表,5个字段
    • Department表,2个字段
    • 那么我们在java中创建两个对应的bean(下面代码省略了getter/setter)
      private Integer id;
      private String lastName;
      private Integer gender;
      private String email;
      private Integer dId;
  }
  public class Department{
      private Integer id;
      private String departmentName;
  }
使用MyBatis操作数据库

​ 有两种方式可用:注解法 ,和配置法。

  • 注解法

    • 使用MyBatis对数据库进行增删改查
    // DepartmentMapper.java
        
    @Mapper // 指定这是一个操作数据库的mapper
    public interface DepartmentMapper{
        @Select("select * from department where id=#{id}")
        public Department getDepartmentById(Integar id);
        @Delete("delete from department where id=#{id}")
        public int deleteDepartmentById(Integar id);
        // 因为id自增,所以声明id为自增组件,而SQL插入时,也仅仅插入 Department 的 departmentName变量
        @Options(useGeneratedKeys = true, keyProperty = 'id')
        @Insert("insert into department(departmentName) values(#{departmentName})")
        public int insertDepartment(Department department);
        @Update("update department set departmentName=#{departmentName} where id=#{id}")
        public int updateDepartment(Department department);
    }
    

    上面使用注解进行增删改查,我们可以写个controller进行测试

    @RestController
    public class DepartmentController{
        @Autowired
        DepartmentMapper departmentMapper;
            
        @GetMapping("/department/{id}")
        public Department getDepartment(@PathVariable("id") Integer id){
            return departmentMapper.getDepartmentById(id);
        }
            
        @GetMapping("/department")
        public Department insertDepartment(Department department){
            departmentMapper.insertDepartment(department);
            return department;
        }
    }
        
    /* 
    访问 localhost:8080/department?departmentName=TEST 则会发现页面返回
    {
      id: 1,
      departmentName: TEST
    }
    并且数据库也的确多了一条数据。
        
    访问 localhost:8080/department/1 则会发现显示与上面相同,的确进行了数据库的查询。
    */
    
    • 批量注解法

    • 当我们有大量的数据库表的时候,会有大量的bean ,于是也将需要大量的mapper对象进数据库操作。我们需要简化创建Mapper注解的方法

    • 此时我们可以在 springBootApplication 上做@mapperScan标识,MapperScan所指定的包目录内,所有类都视为mapper,无需每个类指定Mapper

      @MapperScan(value="com.freeeknight.myTest.mapper")
      @SpringBootApplication
      public class MyTestApplication{
          public static void main(String[] args){
              SpringApplication.run(MyTestApplication.class);
          }
      }
    
  • 配置法

    • 和上面一样,先将类扫描装配到容器中,但不用注解实现SQL语句
    // DepartmentMapper.java
        
    @Mapper // 指定这是一个操作数据库的mapper,如果有了MapperScan,则这里可以不用指定mapper
    public interface DepartmentMapper{
        public Department getDepartmentById(Integar id);
        public int deleteDepartmentById(Integar id);
    }
    
    • 在resources/mybatis/中创建一个 mybatis-config.xml 全局配置文件
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!--  这里做一些全局设置,可以推荐看看下面 -->
            <!-- https://mybatis.org/mybatis-3/zh/ -->
        </settings>
    </configuration>
    
    • 在resources/mybatis/mapper中创建一个DepartmentMapper.xml 的sql映射文件
    <mapper namespace="com.freeknight.myTest.mapper.DepartmentMapper">
        <!-- 对应上面的getDepartmentById函数参数,返回值,以及SQL语句 -->
        <select id="getDepartmentById" parameterType="int" resultType="com.freeknight.myTest.bean.Department">
          SELECT * FROM department WHERE ID = #{id}
        </select>
            
        <insert id="deleteDepartmentById">
          insert into department(department) values(#{departmentName})
        </insert>
    </mapper>
    
    • 最后在项目配置中,指定mybatis的配置文件位置
    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc::mysql://192.168.12.22:3306/myDB
        driver-class-name: com.mysql.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        initialSize: 5
    mybatis:
      config-location: classpath: mybatis/mybatis-config.xml
      mapper-locations: classpath: mybatis/mapper/*.xml  # 直接指定文件夹
    
  • 注解法和配置法可以混用。不过,个人推荐注解法。

JPA

JPA是一个ORM思想的接口框架,存在于用户应用程序和JDBC之间的一个中间层。它让用户开发时使用ORM面对对象的开发机制,它自己来进行对JDBC的映射。

JPA与SpringData
Application-> SpringData(CRUDRepository)-> SprintDataJPA(JPARepository)-> JPA-> JDBC-> DB
配置使用JPA
  • 首先进行配置数据源和JPA的基本设置
  spring:
    datasource:
      username: root
      password: 123456
      url: jdbc::mysql://192.168.12.22:3306/myDB
      driver-class-name: com.mysql.jdbc.Driver
    jpa: # jpa的配置在这里
      hibernate:
        hbm2ddl-auto: update # 更新或创建数据表结构
        #validate       加载hibernate时,验证创建数据库表结构
      #create       每次加载hibernate,重新创建数据库表结构,这会导致数据库表数据丢失。
      #create-drop  加载hibernate时创建,退出是删除表结构
      #update       加载hibernate自动更新数据库结构
      show-sql: true # 每次增删改查时显示sql
  • 编写一个实体类(bean)和数据表进行映射,并且配置好映射关系
  // entity\User.java
  
  // 使用JPA注解配置映射关系
  @Entity   // 告诉JPA这是一个和数据表进行映射的实体类
  @Table(name="tbl_user")   // 指定映射的表
  public class User{
      @Id // 表示这是一个主键
      @GeneratedValue(strategy = GenerationType.INDENTITY) // 这是自增主键,指定其自增策略
      private Integer Id;
      @Column(name="last_name", length=50)  // 数据库列属性
      private String lastName;
      @Column // 省略name,则默认列名就是属性名
      private String email;
      // 下面省去了getter,setter函数
  }
  • 操作本表,编写一个DAO进行该类操作
  // Repository\UserRepository.java
  
  // 这里继承 JpaRepository 进行数据库操作,如果使用 mybatis 对数据库操作,则继承 Repository 并做mapper即可
  // JpaRepository<C, T> 模板类的第一个参数是需要被操作的类名,第二个参数是被操作类中主键的类型,上面是Interger Id, 是整形。
  public interface UserRepository extends JpaRepository<User, Integer>{
      
  }
  • 写一个controller进行增删改查的测试
  // controller\UserController.java
  
  @RestController
  public class UserController{
      @Autowired // 注入UserRepository可以直接使用
      UserRepository userRepository;
      
      @GetMapping("user/{id}")
      public User getUserById(Integer Id){
          return userRepository.findOne(id); // findOne这些函数都是被JpaRepository直接提供的
      }
  }
  • 上面我们看到JPA和myBatis都可以对数据库进行增删改查,什么时候用什么呢?

    • 如果查询结构复杂,你喜欢sql语句,或者要用存储过程,则使用MyBatis
    • 如果能设计好数据库的ORM结构,简单使用sql,用JPA就好
    • 两者可以同时使用。

SpringBoot原理

总结:SpringBoot的常见各层

Enitity层

  • 实体类,和数据库中的属性值保持一致,实现getter, setter方法(可能实现 tostring 方法)

  • 样例代码

  @Entity
  public class User{
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
      @Column(name = "head_image_url")
      private String headImageUrl;
      // getter/setter
      public void setId(Long id) {
          this.id = id;
      }
      public Long getId() {
          return id;
      }
      //...
  }

Mapper层/DAO层

  • 持久层,数据库操作层,负责对数据持久化操作,它是负责对数据库进行增删改查操作的

    • 可以用JPA或者mybatis实现
  • 样例代码

  @Mapper
  @Repository
  public interface UserMapper {
      @Select("select * from tbl_user where id=#{id}")
      public User getUserById(Integar id);
  }

Service层

  • 业务层。主要封装自己的业务逻辑,提供给contoller层使用。 部分情况会将具体的实现放置在 ServiceImpl 中。

  • 样例代码

  public interface UserService extends Service<User> {
      @Autowired // 注入UserRepository可以直接使用
      UserMapper userMapper;
  
      public User getUserById(Integer Id){
          return userMapper.getUserById(id);
      } 
  }

Controller层/web层

  • 控制层。主要负责接收前端消息,并调用 Service 层进行业务处理

  • 样例代码

  @RestController
  @RequestMapping("/user")
  public class UserController {
      @Resource
      private UserService userService;
  
      @PostMapping("/add")
      public Result add(User user) {
          userService.addUser(user);
          return ResultGenerator.genSuccessResult();
      }
  }

View层

  • 主要是一些 jsp, html文件
  • 因为我不打算使用spring mvc,所以不说了。

总结:JAVA对象

PO/DO

  • Persistent object 持久化对象。也被称为data对象(简写为DO),Entity对象。
  • 可以简单认为一个PO对应的是数据库中一个表结构。
  • PO 中不应该有任何数据库的操作。

VO

  • View object 表现层对象。
  • 它是将前端某个页面中,所需要展示的所有数据封装起来的一个结构。
  • 它是面对前端界面的,和后端存储未必有关系。

DTO/TO

  • Data transfer object 数据传输对象。也被称为TO
  • 它就是在数据进行传输时,组装出来的一个数据流格式。

(DAO)

  • 这不是一个java对象
  • Data access object 数据访问对象,它是进行数据库访问的一个对象,它通常配合PO一起使用,它对PO进行数据库操作,它处于业务层和数据库之间。

BO

  • Business object业务对象。
  • 这个是一个业务定义,和业务相关,也可以称为是一个逻辑对象

POJO

  • plain ordinary java object,普通的java对象
  • 这就是说,这个对象,即不是po(数据库对象),也不是vo(显示层对象),也不是dto(传输层对象),也不是BO(业务对象),这个对象非常难以定义其特殊用途的时候,就称为POJO
  • 经常Bean可以视为一个pojo

变换关系

  • POJO持久化后,成为PO,通过DAO进行操作

  • POJO进行传输过程中,成了DTO

  • POJO在表现层(网页)展示的时候,成为了VO

例子

例如数据库两个表分别有100,88个字段,那么这两个PO都分别有100,88个属性。

假设这俩表内的业务相同,都要在一起进行一些逻辑操作,那么这个POJO中有这俩个表的全部数据,则POJO有188个属性。

然后假设其中30个属性要给前端显示,那么后端给前端的消息中,就有一个拥有30个属性的DTO传输对象。

前端收到30个DTO的传输对象时,假如分为三个页面进行展示,那么应该会有3个分别属性为10条的VO对象。

总结:Spring注解大全

声明bean

  • @Component 基本的组件
    • 定义给pojo,没有特殊作用的组件
    • 其他几个注解的基类
  • @Service 业务层使用
    • 针对BO业务对象
  • @Repository 属于访问层使用
    • 针对 DAO 层
  • @Controller 在控制层使用
    • 针对表现层,负责http接口
  • 【@Configuration】 声明配置类
    • 这个注解本身目的不是做bean,但底层实现了Component,也确实创建了bean
  • 【@SpringBootApplication】 主类标识
    • 这个注解本身目的不是做bean,但底层实现了Component,也确实创建了bean

注入bean

  • @Autowired 自动依赖注入。spring提供的支持

    • 使用spring时,应该优先使用这个

    • 和inject可以互换,两者实现机理相同

    • 可以增加required属性

    • 可以配合@Qualifier显式指定装配的对象

    @Autowired(required=false) // 默认Autowired的required是为true。当该值为false时,则表示不强制进行依赖,此时customTest将会为 null.
    @Qualifier(value = "customerTest1") // 如果我们有多个该类对象,则在xml中创建多个对象,然后此处指定绑定哪个对象
    private CustomerTest customerTest;
    

    例如上面对应的xml

    <beans xmlns="http://www.springframework.org/schema/beans"">
        <bean id="customerTest1" class="com.example.demo1.Testclass.customerTest">
            <property name="name" value="name1"></property>
            <property name="age" value="2"></property>
        </bean>
        <bean id="customerTest2" class="com.example.demo1.Testclass.customerTest">
            <property name="age" value="3"></property>
            <property name="name" value="name2"></property>
        </bean>
    </beans>
    
    • 可以配合@Primary显式指定装配对象
    @Primary
    @Bean(name = "customerTest1")
    private CustomerTest customerTest;
    
  • @Inject 自动依赖注入。jsr330规范

    • 不使用spring时,可以使用这个

    • 和Autowired可以互换,两者实现机理相同

    • 可以配合@Qualifier显式指定装配的对象

    • 可以配合@Name注解

    @Inject
    @Named("myBean") // 如果没有该项,则要求变量名必须和配置一致
    private MyBean myBean;
    
  • @Resource 自动依赖注入。jsr250规范

    • 应当避免使用

    • 可以配合@Qualifier显式指定装配的对象

    • 一般会配合一个name属性

    @Resource(name = "userMapper")
    private UserMapper userMapper;
    

配置类

  • @Configuration 声明当前类为配置类。

    • 一个配置类,相当于xml形式的spring配置

    • 该注解作用于 类。

    • 该注解内部包含了 @component 注解,所以本注释也就声明了这个配置类是一个 @bean

    • 可以搭配 @ConfigurationProperties 标注,来指定配置文件路径

    @Configuration
    @ConfigurationProperties(prefix = "redis.config")
    public class RedisConfiguration {
        private int maxTotal;
        private int maxIdle;
    }
    

    配置文件 redis.config

    redis.config.maxTotal=5000
    redis.config.maxIdle=10
    
    • @ConfigurationProperties 标注还可以进一步配合 @EnableConfigurationProperties 标注。

    • 配置类还可以加 @AutoConfigureAfter@AutoConfigureBefore 可以指定配置的先后依赖关系

    @AutoConfigureAfter(DataSourceAutoConfiguration.class) // 指定这个配置类加载必须在DataSourceAutoConfiguration类配置之后。
    public class MybatisAutoConfiguration {
    }
    
  • @SpringBootConfiguration 和Configuration一样,只是spring boot专有的

  • @Bean 声明一个函数,表示该函数将返回一个bean对象。

    • 相当于xml配置中的一个 标签的bean初始化

    • 该注解作用于 函数/方法。

    • 通常存在在 @configuration 配置类中

    • 不允许存在于@component 组件类中

    • 通常配合使用@scope注解,来指定创建bean的方式

    • Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),

    • Protetype (每次调用新建一个bean)

    • Request (web项目中,给每个http request新建一个bean),

    • Session (web项目中,给每个http session新建一个bean)

    • GlobalSession(给每一个 global http session新建一个Bean实例)

    @Configuration
    public class MyConfiguration {
        @Bean
        @Scope("prototype")
        public Encryptor encryptor() {
            // ...
        }
    }
    
    • 可以配合name属性指定别名
    @Bean(name = { "dataSource", "subsystemA-dataSource"})
    public DataSource dataSource() {
        // ...
    }
    
    • 可以配合initMethod,destroyMethod属性指定bean初始化函数和释放函数
    @Bean(initMethod = "init")
    public Foo foo() {
       return new Foo();
    }
    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
       return new Bar();
    }
    
  • @ComponentScan 用于对@Component的包扫描

    • 一般作用在主入口类上
    • 相当于xml中的,全部bean记录
  • @EnableAutoConfiguration 开启自动配置注解

    • 开启本注解,则会自动将 “spring-boot-autoconfigure.jar/META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration的value值列表” 中所有 xxxAutoConfiguration 的类进行配置加载。
    • 我们一般不会手动使用该注解。
  • @WishlyConfiguration 相当于@Configuration与@ComponentScan的组合注解,可以替代这两个注解

  • @Import 导入指定的配置类

  @Configuration
  @Import({ CustomerConfig.class, SchedulerConfig.class })
  public class AppConfig {
  }
  • @ImportResource 加载xml文件,在xml 文件中再指定配置文件
  @Configuration
  @ImportResource("classpath:cons-injec.xml") //导入xml配置项
  public class SoundSystemConfig {
  }

配置文件 cons-injec.xml

  <?xml version="1.0" encoding="UTF-8"?>
  <beans xmlns="http://www.springframework.org/schema/beans ">
         <bean id="compactDisc" class="com.jiaobuchong.soundsystem.BlankDisc">
                <constructor-arg> <!--这里就是配置项,在配置一个bean的构造函数->
                       <list>
                              <value>Sgt. Pepper's Lonely Hearts Club Band</value>
                              <value>With a Little Help from My Friends</value>
                       </list>
                </constructor-arg>
         </bean>
  </beans>
  • @Value注解进行赋值
  @Value("Michael Jackson") // 简易赋值
  String name;
  @Value("#{domeClass.name}") // 赋值其他bean的属性
  String name;
  @Value("${book.name}") // 注入配置中的属性(假设test.properties配置文件中有如下一行 book.name=《三体》)
  String bookName;

环境配置注释

  • @Profile
  @Component
  @Profile("dev")
  public class DevDatasourceConfig{
  }

配合application.properties配置中的下列项共同作用

  spring.profiles.active=dev

定时任务注释

  • @EnableScheduling 在配置类上使用,开启计划任务的支持
    • 作用于 类
  • @Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型
    • 作用于 方法/函数

异步操作注释

  • @EnableAsync 配置类中,通过此注解开启对异步任务的支持
    • 作用于 类
  • @Async 在实际执行的bean方法使用该注解来申明其是一个异步任务
    • 作用于 方法/函数

功能开启类注释

  • Enable* 这是一系列的注解,用来开启对某功能的支持,例如
    • @EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
    • @EnableAsync 开启异步方法的支持
    • @EnableScheduling 开启计划任务的支持

测试相关类注释

  • @RunWith 运行器,在Spring中通常用于对JUnit的支持
  @RunWith(SpringJUnit4ClassRunner.class)
  • @ContextConfiguration 用来加载配置,和 import有些类似,但它仅作用于 RunWith 测试环境下,和import不可互换
  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes={TestConfig.class})
  public class TestConfig{
      // ...
  }

入口类

  • @SpringBootApplication 用在主类上,标识这是一个spring boot应用
    • 这是个最重要的核心类
    • 实际上这个注解是 @SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解的合集。

条件类

  • @Conditional 它是一个系列,上文有提及,主要配合 @bean @configration使用,当符合指定条件下,@bean, @configration才有效。
  @Component
  @ConditionalOnBean(name="redisTemplate") // 当有这个bean,这个类才会算为bean被IOC容器管理
  public class RedisOperBean {
    private final RedisTemplate redisTemplate;
    // ...
  }
  
  @Configuration
  @ConditionalOnClass({ Feign.class }) // 当这个类存在,该配置类才有效
  public class OAuth2FeignAutoConfiguration {
  }

TODO List:

SpringBoot与缓存:redis

SpringBoot与消息:RebbitMQ

SpringBoot与检索:ES

SpringBoot与任务:邮件任务, 定时任务, 异步任务

SpringBoot与安全:SpringSecurity

SpringBoot与分布式:ZooKeeper, dubbo, springCloud

SpringBoot与开发热部署

SpringBoot与监控管理

附注:YAML语法

基本语法

  1. 空格 为缩进,控制层级关系(tab不允许使用)。空格空多少,无所谓,只要空格数量相同,则视为同一级。

  2. K: V 表示一个键值对。注意:K:后面必须加 空格,然后再加 V。

  3. #号是注释

  4. 属性和值都是大小写敏感的。

例如

   server:
       port: 8080
       path: /hello

值的语法

  1. 字符串通常不需要加引号。

  2. 双引号 包装的字符串,会做自动转义工作; 单引号包装的字符串则会视为字符串。默认不加引号,会视为加了单引号。例如

   name: "zhangsan \nnihao"  # 输出就是    zhangsan 换行nihao
   name: 'zhangsan \nnihao'  # 输出就是    zhangsan\nnihao
   name: zhangsan \nnihao    # 输出就是    zhangsan\nnihao
  1. 布尔类型用小写 true, false 表示

  2. 空值用 null 或 ~ 表示

  3. 日期使用 iso-8601标准 : yyyy/MM/dd HH:mm:ss

  4. 文本块可以用 | 符号表示,比较烦,不细说了

  5. 引用。& 表示锚点, * 表示引用锚点数据。例如

   name: &a freeknight
   anothername: *a
   names:
       - demacia
       - *a
   # 输出anothername: freeknight
   # 输出names: [demacia,freeknight]
   defaults: &defaultconfig
     adapter:  postgres
     host:     localhost
   
   development:
     database: myapp_development
     <<: *defaultconfig
     
   # 等同于
   defaults:
     adapter:  postgres
     host:     localhost
   
   development:
     database: myapp_development
     adapter:  postgres
     host:     localhost
  1. 对象
   person:
       name: freeknight
       age: 30
       
   # 或者单行写法
   person: {name: freeknight,age: 30}
  1. 数组
   myArrays:
       - 12
       - 22
       
   # 或者单行写法
   myArrays: [12,22]
  1. 允许双感叹号的类型强转

    name: !!str true
    # 这里的name是 string, 不是布尔型。
    

附注:深入自动配置原理

流程分析

  • 启动时,spring boot通过main入口的 @SpringBootApplication 注解进行解释。

  • 查看@SpringBootApplication注解,我们会发现@EnableAutoConfiguration字段,它进行配置自动加载。

  • 查看@EnableAutoConfiguration注解,我们会发现它进行了 @import({AutoConfigurationImportSelector.class}) ,用它向容器中导入组件

  • 我们看 AutoConfigurationImportSelector类中的 getAutoConfigurationEntry()函数会发现,

  List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  • 继续跟踪 getCandidateConfigrations 函数,我们会发现:
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  • 继续跟踪 loadFactoryNames 函数,我们会发现
  Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  while(urls.hasMoreElements()) {
                      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  }
  • 从上面可知,spring boot通过扫描所有jar包内的 META-INF/SPRING.FACTORIES 文件,来进行组件的加载的同时,加载其中的 @propertySource 的值。

  • 打开项目中 external libraries中的Mavens:org.springframework.bootLspring-boot-autoconfigure:2.x.x包,找到其中的META-INF/spring.factories文件,我们看到

  # Auto Configure
  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
  org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
  org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
  .....一大堆类....\
  org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
  org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
  • 我们拿其中一个类 HttpEncodingAutoConfiguration 为例进行查看
  // 说明这是一个配置类,可以给容器中添加组件
  @Configuration(
      proxyBeanMethods = false
  )
  // 启用指定类的 configration properties功能,即将配置文件中的值,和httpProperties这个类的变量进行了绑定。下面我们会看 HttpProperties 类内容
  @EnableConfigurationProperties({HttpProperties.class})
  
  // 这个注解意思是说,当符合指定的condition(本例中意思是,必须是 web 项目)。这个 HttpEncodingAutoConfiguration 类才会生效,否则本类无效,
  @ConditionalOnWebApplication(
      type = Type.SERVLET
  )
  // 判断当前项目中是否有 CharacterEncodingFilter 这个类(该类是spring mvc中解决乱码的标准过滤器类)。如果有的话,则当前的这个 HttpEncodingAutoConfiguration 类生效,否则本类无效,
  @ConditionalOnClass({CharacterEncodingFilter.class})
  
  // 继续判断当前项目是否有 “spring.http.encoding.value” 这个配置的属性,有的话,则读取;如果不存在,这个 spring.http.encoding.value 依然认为是 enabled(因为matchIsMissing)。
  @ConditionalOnProperty(
      prefix = "spring.http.encoding",
      value = {"enabled"},
      matchIfMissing = true
  )
  public class HttpEncodingAutoConfiguration {
      // 这个properties中的值,已经被  @EnableConfigurationProperties({HttpProperties.class}) 这个注解和 HttpProperties 类绑定了。
      private final Encoding properties;
      // 这个Bean说明,该配置类如果有效,则会创建bean。并且,这个bean中的属性,会从 properties进行读取
      @Bean
      @ConditionalOnMissingBean
      public CharacterEncodingFilter characterEncodingFilter() {
          CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
          filter.setEncoding(this.properties.getCharset().name());
          return filter;
      }
      
      // ....其他代码
  }

我们打开httpProperties类,可见该类中有大量需要进行配置的变量。

  // 这个注解说明,本类中的值,是从配置文件中进行读取的
  @ConfigurationProperties(
      prefix = "spring.http"
  )
  public class HttpProperties {
          public static final Charset DEFAULT_CHARSET;
          private Charset charset;
          private Boolean force;
          private Boolean forceRequest;
          private Boolean forceResponse;
          private Map<Locale, Charset> mapping;
  
          public Encoding() {
              this.charset = DEFAULT_CHARSET;
          }
          public Charset getCharset() {
              return this.charset;
          }
          public void setCharset(Charset charset) {
              this.charset = charset;
          }
          public boolean isForce() {
              return Boolean.TRUE.equals(this.force);
          }
      // ....其他代码
  }

上面的属性类中的变量,也就是我们在配置文件application.properties里面可以配置项名。例如:

  spring.http.charset=utf-8
  spring.http.force=true
  spring.http.forceRequest=true
  spring.http.forceResponse=true

于是,上面我就知道了。spring boot里面是如何创建大量bean(看 spring.factories 文件),bean对象所在类如何绑定属性类(HttpProperties),以及这些属性类是如何读取配置的全部流程。

实战意义

  1. 我们知道了spring boot启动时会创建大量默认组件,并加载大量的自动配置类。
  2. 我们做任何事情,应该
    1. 先去检查是否有spring boot默认写好的自动配置类
    2. 然后,我们看看这个自动配置类内有哪些组件
    3. 然后,我们看这些组件,从properties里面获取了哪些属性
    4. 最后,我们在配置文件中如何配置这些属性

配置判断

上面我们的例子中有使用 @ConditionalOnWebApplication 等condition注解,只有这些注解判断成功后,那个配置类才会真正生效,否则,配置类内的自动配置和自动添加组件将会无效。

那么我们还包括哪些配置条件注解呢?

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean;
@ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

补充:

  1. 配置判断注解,不仅仅可以作用于配置类,还可以作用于函数
  2. 我们还可以通过重写@conditional 注解类中的match()函数,来实现自定义的配置条件判断
  3. 我们可以在 application.properties / yaml 中添加 debug = true 的属性,让控制台在启动spring boot时打印自动配置报告,可以看出自动加载了哪些配置,哪些配置因为什么原因而没有加载。

附注:Docker

核心概念

  • DockerHost(主机):安装了docker程序的机器。
    • Docker是安装到操作系统上的一个软体。
    • Docker安装后,将会运行守护进程和其容器。
  • DockerImages(镜像):将一组软件打包后的一个docker模板,用来快速创建容器。
    • 一个镜像可以启动多个容器
    • 容器内的端口是独立的,不会冲突。例如开启了三个mysql容器都监听3306,是没有问题的。
  • DockerContanier(容器):将镜像启动后的一组独立运行的一个或一组应用实例。
  • DockerClient(客户端):用来连接docker主机进行操作,通过命令行或UI方式和docker守护进程通信。
  • DockerRegistry(仓库):用来保存打包好的docker镜像的地方。
    • 推荐的公共仓库是 hub.docker.com

简要用流程

  1. 安装docker
  2. 去docker镜像仓库找到对应软件的镜像
  3. 使用docker client运行这个镜像,该镜像就会生成一个容器
  4. 关闭容器,就是关闭软件

常用操作

进入命令行或powershell

镜像操作

  • docker version:查看docker版本

  • docker search 关键字:搜索指定镜像。等同于直接访问hub.docker.com进行搜索。

  PS C:\user\freeknight> docker search redis
  NAME            DESCRIPTION                          STARS  OFFICIAL AUTOMATED
  redis           Redis is an open source key-value…   8191     [OK]
  bitnami/redis   Bitnami Redis Docker Image           145                 [OK]
  ...
  • docker pull 镜像名:tag 其中后面的tag可选,表示镜像的版本,默认是 latest
  PS C:\user\freeknight> docker pull bitnami/redis:5.51
  ...
  868de938a9c1b30: pull complte
  ...
  Digest: sha256: 868de938a9c1b30868de938a9c1b30868de938a9c1b30868de938a9c1b30
  • docker images 查看本地的所有镜像
  PS C:\user\freeknight> docker images
  RESPOSITORY   TAG         IMAGE ID        CREATED     SIZE
  redis       latest    5d4d51c2ea08    3 days ago  25.2MB
  • docker rmi image-id 删除本地指定镜像,传入上面的IMAGE ID
  PS C:\user\freeknight> docker rmi 5d4d51c2ea08
  deleted: sha256: 868de938a9c1b30868de938a9c1b30868de938a9c1b30868de938a9c1b30

容器操作

  • docker run –name container-name -d image-name : 参数container-name为自定义容器名,image-name为镜像模板名,-d表示后台运行。运行一个镜像,这将产生一个新容器。
    • -p 8888:8080: -p 是 进行端口映射,从主机端口8888(映射到)容器内部端口8080。例如我们容器内启动了一个tomcat,它监听8080端口,但我们在开发机上访问本机8080是无效的,必须将端口进行映射出来。于是我们将容器中的8080映射到主机的8888端口,然后我们在开发机访问8888端口即可。
    • -e PASSWORD=root : -e 是进行启动时的参数传入
    • -v /config/:/etc/config : -v 是文件配置传入,将本地的/config/文件夹内的内容,传递给容器内的/etc/config/内,通常用作配置文件的传递。
  • docker ps: 查看当前有哪些容器被运行状态
  • docker stop container-id 或者 docker stop contanier-name : 停止运行中的容器
  • docker ps -a 查看所有容器(包括运行中的和被停止的)
  • docker start container-id 或者 docker start contanier-name : 启动一个停止中的容器
  • docker rm container-id : 删除一个容器(该容器必须是被停止状态)
  • docker logs container-id或container-name: 查看一个容器日志

docker错误

虚拟机中启动提示

虚拟机管理服务无法启动虚拟机“DockerDesktopVM”,因为一个 Hyper-V 组件尚未运行

​ 解决方法:

  1. 设置virtualbox, 设置->系统->处理器-> 开启嵌套VT-x
  2. 进入windows虚拟机,设置->程序与应用->启用或关闭windows功能->开启container和hyper-V功能
  3. 重启电脑,进入系统,任务管理器->性能->查看【虚拟机】项的值是否是 【enable/是】。
  4. 右键点击docker desktop图标,switch to windows containers
  5. 应该OK了

下载镜像时提示

no matching manifest for windows/amd64

​ 解决方法:

1. 打开docker desktop的setting界面
2. 选择Docker engine,设置 "experimental": true (默认该值为falses)
3. apply and restart

附注:MyBatis

基本概念

与JDBC和Hibernate关系
  • JDBC

    • 是一个对多种数据库的统一API接口,底层接各种数据库驱动,统一方式调用。
    • 它使用jdbc template进行数据库增删改查
    • 它需要自己处理如下操作
    • 编写sql
    • 预编译
    • 设置参数
    • 执行sql
    • 封装结果
  • Hibernate

    • 它是一个全自动,全映射的ORM框架,是对JDBC的封装。
    • 其目的是去SQL化,需要使用SQL语句,就可以通过对象操作进行数据库的增删改查
    • 将JDBC的上述五步操作全部封装。
    • 缺点:
    • 因为封装了SQL,我们无法设置SQL,导致无法进行优化。
    • 因为是全映射的,所以当我们想取出某一条字段数据时,是无法达成的。它一次query就是整个object的全部属性(即数据库中整条数据全部列)的获取。
    • 为解决上述问题,可以学习一个特殊的语法 HQL,但这东西太复杂,学习成本反而增加了。
  • MyBatis

    • 它是一个半自动框架。

    • 类似Hibernate,它也做了封装,但它将后面的四步完全自动化,即下面四步不用管了

    • 预编译

    • 设置参数

    • 执行sql

    • 封装结果

    但第一步“编写sql”这一步开放给用户,通过配置文件或注解的方式给了用户,这样的好处就是: 可以优化SQL,可定制SQL。解决了hibernate的问题。

基本使用MyBatis

  • 下载

https://github.com/mybatis/mybatis-3

  • 添加依赖
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>x.x.x</version>
  </dependency>
  • 通过全局配置xml文件创建mybatis的sqlSessionFactory。这个sqlSessionFactory就是一个sql连接池,mybatis负责维护。
  <!-- mybatis-config.xml -->
  <?xml version="1.0" encoding="UTF-8" ?>
  <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
  <configuration>
    <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
          <property name="driver" value="com.mysql.jdbc.Driver"/>
          <property name="url" value="jdbc:mysql://loclhost:3306/myTestDB"/>
          <property name="username" value="root"/>
          <property name="password" value="123456"/>
        </dataSource>
      </environment>
    </environments>
    <mappers>
      <!-- sql mapper配置文件 -->
      <mapper resource="mybatis/EmployeeMapper.xml"/>
    </mappers>
  </configuration>
  • 上面的全局配置文件,也可以使用代码的方式实现。下面的代码等同于上述上面的xml.

    DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
    TransactionFactory transactionFactory = new JdbcTransactionFactory();
    Environment environment = new Environment("development", transactionFactory, dataSource);
    Configuration configuration = new Configuration(environment);
    configuration.addMapper(EmployeeMapper.class);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
    
  • 创建初始类,用来创建sqlSessionFactory

  • 然后从sqlSessionFactory中获取一个sqlsession实例,一个sqlseesion实例就是一个和数据库的一次会话。

  • 然后进行sql执行增删改查

  • 最后关闭这个session会话

  public class MyBatisTest{
      @Test
      public void Test1(){
          String resource = "mybatis/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          
          try(SqlSession openSession = sqlSessionFactory.openSession()){
          // 前面这个参数是xml中 命名空间+id,后面是参数 id 的值
            Employee employee = session.selectOne("com.freeknight.mybatis.EmployeeMapper.selectEmployee", 1);
          }finally{
            // 最后,关闭session
              openSession.close();
          }
      }
  }
  • 创建一个 mapper 的xml配置文件,内含sql语句
  <!-- EmployeeMapper.xml -->
  <?xml version="1.0" encoding="UTF-8" ?>
  <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  <!-- 命名空间只是拿来限制作用域 -->
  <mapper namespace="com.freeknight.mybatis.EmployeeMapper">
    <!-- id 是这个语句的唯一标识,resulttype是返回值类型 -->
    <select id="selectEmployee" resultType="com.freeknight.mybatis.Employee">
      select * from tbl_employee where id = #{id}
    </select>
  </mapper>
  • 上面的xml文件也可以使用编码标注方式实现,编码标注方式也是spring boot推荐的方式。下面文件等同于上面的xml配置文件。

    package com.freeknight.mybatis;
    public interface EmployeeMapper {
      @Select("select * from tbl_employee where id = #{id}")
      Employee selectEmployee(int id);
    }
    
  • 使用编码标注的方式的话,那么session进行 sql调用时可以使用一个更安全简单的方式

    @Test
    public void Test2(){
        // 这里无需重新new了,直接获取即可
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            
        try(SqlSession openSession = sqlSessionFactory.openSession()){
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee employee = mapper.selectEmployee(101);
            /* 上面两行等同于之前的
            Employee employee = session.selectOne("com.freeknight.mybatis.EmployeeMapper.selectEmployee", 1);
            */
        }finally{
            openSession.close();
        }
    }
    
  • 样例po entity类

  public class Employee{
      private Integer id;
      private String lastName;
      private String email;
      //getter/setter/tostring
  }

全局配置文件的属性

https://mybatis.org/mybatis-3/zh/configuration.html#mappers

  • 配置文件可以引入第三方配置文件中的配置,例如
  <configuration>
    <!-- 这里表示加载第三方配置 -->
    <properties resource="application.properties"></properties>
      
    <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
          <property name="driver" value="&{jdbc.driver}"/>
          <property name="url" value="&{jdbc.url}"/>
          <property name="username" value="&{jdbc.username}"/>
          <property name="password" value="&{jdbc.password}"/>
        </dataSource>
      </environment>
    </environments>
    <mappers>
      <mapper resource="mybatis/EmployeeMapper.xml"/>
    </mappers>
  </configuration>
  # application.properties文件
  jdbc.driver=com.mysql.jdbc.Driver
  jdbc.url=jdbc:mysql://loclhost:3306/myTestDB
  jdbc.username=root
  jdbc.password=123456

映射文件的属性

https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

  • cache – 该命名空间的缓存配置。

  • cache-ref – 引用其它命名空间的缓存配置。

  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。

  • sql – 可被其它语句引用的可重用语句块。

  • insert – 映射插入语句。

  • update – 映射更新语句。

  • delete – 映射删除语句。

  • select – 映射查询语句。(我们下面的例子中,使用的是这个)

  public interface EmployeeMapper {
    // 下面的 @Select 表示这是
    @Select("select * from tbl_employee where id = #{id}")
    Employee selectEmployee(int id);
  }

注意点

  • sqlSession是非线程安全的,所以不要把它作为一个成员变量,以避免多线程竞争。每次使用时都应该从sqlSessionFactory中去获取。