SpringBoot

SpringBoot项目整合基础版(非多模块版)

步骤1:项目的创建

在创建时顺便引勾选mysql、mybatis、lombok,spring web

步骤2:配置yaml文件

application.yaml还可以通过spring.profiles.active方式,选中激活启用

spring:
  profiles:
    active: dev

application-dev.yaml对应到spring.profiles.active中的dev,即dev的值可以进行替换,如果active值为xxx,则对应的配置文件为application-xxx.yaml,具体文件配置如下

server:
  port: 8080
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/goodssystem?useSSL=false
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initialSize: 5
      minIdle: 5
      maxActive: 20
mybatis:
  mapper-locations: classpath:com/xshiedu/mapper/*Mapper.xml
  type-aliases-package: com.xshiedu.pojo
logging:
  level:
    com.xshiedu.mapper: debug
  file:
    path: spring.log
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  pageSizeZero: false
  params: count=countSql

步骤3:配置pom文件

如下的pom文件为基础的一些配置,如果有额外的jar包,则另行添加

此处通过dependencyManagement标签管理依赖版本,通过dependencies标签引入依赖

<properties>
    <java.version>1.8</java.version>
    <mysql.version>5.1.49</mysql.version>
    <lombok.version>1.18.12</lombok.version>
    <fastjson.version>1.2.73</fastjson.version>
    <log4j.version>1.2.17</log4j.version>
    <pagehelper.version>1.2.10</pagehelper.version>
    <mybatis-version>2.1.3</mybatis-version>
    <druid.version>1.2.3</druid.version>
</properties>

<dependencies>
    <!--druid数据源-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <!--fastjson-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <!--log4j-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
            <version>${lombok.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

步骤4:指定文件目录下编写代码

image-20201116114727637

步骤5:配置对应的mapper.xml文件

image-20201116120119821

步骤6:将对应的静态资源添加到resources中

要注意,在创建好SpringBoot项目时,会自动帮忙创建static和templates文件夹,可以将静态文件放到这2个文件夹中

image-20201116121108552

注意:

1、在修改完成后,要注意修改原页面中请求路径,因为在Springboot中项目层级无需添加static层级,直接pages/…即可

2、需要修改IndexController,首页跳转控制器

image-20201116121656226

步骤7:添加json消息转换器(fastjson版)(选)

如果使用的是fastjson,则需要手动添加fastjson消息转换器

本质上,代码与之前学习的注解版SSM中的消息转换器相同

@Configuration
public class FastJsonConfig implements WebMvcConfigurer {
    //添加额外的消息转换器
    @Bean
    public FastJsonHttpMessageConverter fastJsonHttpMessageConverter(){
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        com.alibaba.fastjson.support.config.FastJsonConfig fastJsonConfig = new com.alibaba.fastjson.support.config.FastJsonConfig();
        fastJsonConfig.setDateFormat("yyyy-MM-dd");
        converter.setFastJsonConfig(fastJsonConfig);
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(mediaTypes);
        return converter;
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(fastJsonHttpMessageConverter());
    }
}

image-20201116121946157

步骤8:为SpringBoot启动类添加MapperScan注解

此处扫描的即为mapper接口的路径

image-20201116122030723

此处可以在对应的Mapper接口中添加@Mapper注解,但一般不推荐(后期不好管理,需要每个Mapper接口单独添加)

spring boot其他细节

1、开启启动自动浏览器打开

注意在使用的是org.springframework.core.env.Environment,而对应的启动方式为Runtime.getRuntime().exec("cmd /c start http://localhost:" +environment.getProperty("server.port"));

其中environment通过getProperty获取yaml文件中的属性

package com.xshiedu;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

import java.io.IOException;

@MapperScan("com.xshiedu.mapper")
@SpringBootApplication
public class SpringbootDay02Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootDay02Application.class, args);
        Environment environment = context.getBean(Environment.class);
        try {
            Runtime.getRuntime().exec("cmd /c start http://localhost:" +environment.getProperty("server.port"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、日志配置

日志的配置通常有如下几个级别,在S2就已经学习过,记录的级别从上往下 由低到高,级别越低输出的内容越多,通常使用Debug级别

  • TRACE
  • DEBUG
  • INFO
  • WARN
  • ERROR

实际应用中,spring boot 内已经整合了日志管理logback、log4j等日志依赖,查看方式如下

找到项目pom文件中的parent注解

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点击查看spring-boot-starter-parent注解中,找到如下注解

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.5.RELEASE</version>
</dependency>

再往下点

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
    <version>2.3.5.RELEASE</version>
    <scope>compile</scope>
</dependency>

展开后会发现,对应的相关日志配置

<dependencies>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-to-slf4j</artifactId>
        <version>2.13.3</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jul-to-slf4j</artifactId>
        <version>1.7.30</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

logback的使用

官方推荐使用的xml名字的格式为:logback-spring.xml而不是logback.xml,因为带spring后缀的可以使用这个标签,将对应的logback-spring.xml放置到resources中,并按如下方式进行设置

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration  scan="true" scanPeriod="10 seconds">
    <!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="D:/mylog" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 DEBUG 日志 -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_debug.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
        以及指定<appender>。<logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
              如果未设置此属性,那么当前logger将会继承上级的级别。
        addtivity:是否向上级logger传递打印信息。默认是true。
    -->
    <!--<logger name="org.springframework.web" level="info"/>-->
    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
     -->


    <!--
        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
        不能设置为INHERITED或者同义词NULL。默认是DEBUG
        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
    -->

    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <logger name="com.nmys.view" level="debug"/>
    </springProfile>

    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>

    <!--生产环境:输出到文件-->
    <!--<springProfile name="pro">-->
        <!--<root level="info">-->
            <!--<appender-ref ref="CONSOLE" />-->
            <!--<appender-ref ref="DEBUG_FILE" />-->
            <!--<appender-ref ref="INFO_FILE" />-->
            <!--<appender-ref ref="ERROR_FILE" />-->
            <!--<appender-ref ref="WARN_FILE" />-->
        <!--</root>-->
    <!--</springProfile>-->
</configuration>

需要注意,其中的彩色日志可以选择,如果想要添加彩色日志,需要额外下载Grep Console 彩色日志插件,可以在如下区域自己DIY颜色

image-20201117121312960

最后在yaml文件中添加如下的配置

logging:
  level:
    com.xshiedu.mapper: debug
  file:
    path: mylog
  config: classpath:log/logback-spring.xml

3、Mybatis整合

自行到官方下载Spring boot starter对应的mybatis,并将其引入到pom文件中

image-20201117122012246

mybatis一级配置

这个一级配置用的相对较少,重点是关注mybatis的二级配置

package org.mybatis.spring.boot.autoconfigure;

/**
 * Configuration properties for MyBatis.
 *
 * @author Eddú Meléndez
 * @author Kazuki Shimizu
 */
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

  public static final String MYBATIS_PREFIX = "mybatis";

  /**
   * 本地mybatis的配置文件,如果我们用xml方式配置的话,他就会去加载这个配置文件
   */
  private String configLocation;

  /**
   * 本地的mapper的映射文件:UserMapper.java  -> classpath:mapper/UserMapper.xml
   * 这是一个数组,可以写多个位置
   */
  private String[] mapperLocations;

  /**
   * mybatis的别名包配置,如果有多个,可以使用 , ; \t\n 其中一种符号分隔开
   */
  private String typeAliasesPackage;

  /**
   * 自定义数据类型转换器的报名,如果多个可以使用 , ; \t\n 其中一种符号分隔开
   */
  private String typeHandlersPackage;

  /**
   * 指示是否对MyBatis xml配置文件执行状态检查,这里默认为false
   */
  private boolean checkConfigLocation = false;

  /**
   * defaultExecutorType的配置项,可选值:SIMPLE, REUSE, BATCH。
   */
  private ExecutorType executorType;

  /**
   * 配置的一些map值
   */
  private Properties configurationProperties;
  
  /**
   * 这个就是mybatis的config配置文件了,如果我们设置了configLocation,自己配置的xml文件,那么该对象就不会被使用。
   */
  @NestedConfigurationProperty
  private Configuration configuration;

}

mybatis的二级配置

package org.apache.ibatis.session;
public class Configuration {
  // mybatis的环境对象,指定数据源,数据库软件,事物管理器等等。这里就不细说了,
  protected Environment environment;
  // 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。
  protected boolean safeRowBoundsEnabled;
  // 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。
  protected boolean safeResultHandlerEnabled = true;
  // java的驼峰与数据库的下划线做映射,没有给默认值,boolean的默认值是false,所以默认不开启
  protected boolean mapUnderscoreToCamelCase;
  // 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。
  protected boolean aggressiveLazyLoading;
  // 是否允许单个语句返回多结果集(需要数据库驱动支持)
  protected boolean multipleResultSetsEnabled = true;
  // 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。
  protected boolean useGeneratedKeys;
  // 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。
  protected boolean useColumnLabel = true;
  // 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
  protected boolean cacheEnabled = true;
  // 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。
  protected boolean callSettersOnNulls;
  // 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)
  protected boolean useActualParamName = true;
  // 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)
  protected boolean returnInstanceForEmptyRow;
  // 指定 MyBatis 增加到日志名称的前缀。
  protected String logPrefix;
  // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。可选值:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
  protected Class <? extends Log> logImpl;
  // 指定 VFS 的实现。自定义 VFS 的实现的类全限定名。
  protected Class <? extends VFS> vfsImpl;
  // MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  // 没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  // 指定对象的哪些方法触发一次延迟加载。
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  // 设置超时时间,它决定数据库驱动等待数据库响应的秒数。
  protected Integer defaultStatementTimeout;
  // 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。
  protected Integer defaultFetchSize;
  // 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  // 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应 WARNING: 输出警告日志 FAILING: 映射失败 (抛出 SqlSessionException)
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  // 变量
  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  // 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); 
  protected String databaseId;

  protected Class<?> configurationFactory;

  // 下面这几个是注册器,我们无法配置。在通过java的方式可以进行修改,因为这是springboot项目,像比如插件,类型转换器,语言驱动器等这些得通过java代码以及注解来进行操作。
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<String>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
}

可以通过一级、二级配置的属性,来配置对应的yml文件,形如

mybatis:
  mapper-locations: classpath:com/xshiedu/mapper/*Mapper.xml
  type-aliases-package: com.xshiedu.pojo
  configuration:
    map-underscore-to-camel-case: true

4、多profile文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yaml

比如application-dev.yaml,可以通过spring.profiles.active属性选择激活哪一个对应的环境

多文档块方式

server:
  port: 8081
spring:
  profiles:
    active: prod
---
server:
  port: 8083
spring:
  profiles: dev
---
server:
  port: 8084
spring:
  profiles: prod 

5、自动配置(了解)

通过 https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties 提供的官方文档可以知道对应的配置文件如何编写(实际应用中就有对应的fastjson的消息转换器配置类)

原理

1)SpringBoot启动的时候加载主配置类,开启了自动配置功能

2)利用@EnableAutoConfiguration导入组件

  • 可以查看selectImports()方法的内容;

  • List configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置

     SpringFactoriesLoader.loadFactoryNames()
     扫描所有jar包类路径下  META-INF/spring.factories
     把扫描到的这些文件的内容包装成properties对象
     从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在容器中
    

将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中并用他们来做自动配置;

3)每一个自动配置类进行自动配置功能

4)以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

@Configuration   //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class)  //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中

@ConditionalOnWebApplication //Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;    判断当前应用是否是web应用,如果是,当前配置类生效

@ConditionalOnClass(CharacterEncodingFilter.class)  //判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;

@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)  //判断配置文件中是否存在某个配置  spring.http.encoding.enabled;如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {

    //他已经和SpringBoot的配置文件映射了
    private final HttpEncodingProperties properties;

    //只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
        this.properties = properties;
    }

    @Bean   //给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
        return filter;
    }

根据当前不同的条件判断,决定这个配置类是否生效?

一旦这个配置类生效,这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;

5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类

@ConfigurationProperties(prefix = "spring.http.encoding")  //从配置文件中获取指定的值和bean的属性进行绑定
public class HttpEncodingProperties {
   public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

精髓:

1)、SpringBoot启动会加载大量的自动配置类(具体可以参照以前编写的注解版SSM)

2)、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;

3)、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)

4)、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值;

6、Swagger

引言

无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致,后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新,因此急需一个方案来解决这个问题,Swagger便应运而生。

通过这套规范,你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,生成多种语言的客户端和服务端的代码,以及在线接口调试页面等等。这样只需要更新Swagger描述文件,做到调用端代码、服务端代码以及接口文档的一致性。

但对于开发来说,编写这个yml或json格式的描述文件,本身也是有一定负担的工作,特别是在后面持续迭代开发的时候,往往会忽略更新这个描述文件,直接更改代码。久而久之,这个描述文件也和实际项目渐行渐远。所以SpringSwagger规范纳入自身的标准,建立了Spring-swagger项目,后面改成了现在的Springfox,只需在项目中引入对应的依赖,即可实现在线的接口文档。

缺陷

本身内嵌代码到java源码中,对项目自身有一定的负担

Swagger官方工具(了解)

工具分类

Swagger Codegen︰通过Codegen可以将描述文件生成html格式和cwiki形式的接口文档,同时也能生成多种语言的服务端和客户端的代码。支持通过jar包,docker,node等方式在本地化执行生成。也可以在后面的Swagger Editor中在线生成。

Swagger Ul︰提供了一个可视化的UI页面展示描述文件。接口的调用方、测试、项目经理等都可以在该页面中对相关接口进行查阅和做一些简单的接口请求。该项目支持在线导入描述文件和本地部署UI项目。

Swagger Editor︰类似于markendown编辑器的编辑Swagger描述文件的编辑器,该编辑支持实时预览描述文件的更新效果。也提供了在线编辑器和本地部暑编辑器两种方式。

Swagger Inspector︰和postman类似,是一个可以对接口进行测试的在线版的postman。比在swagger Ul里面做接口请求,会返回更多的信息,也会保存你请求的实际请求参数等数据。

Swagger Hub︰集成了上面所有项目的各个功能,你可以以项目和版本为单位,将你的描述文件上传到5wagger Hub中。在Swagger Hub中可以完成上面项目的所有工作,需要注册账号,分免费版和收费版。

Springfox Swagger︰Spring 基于swagger规范,可以将基于SpringMVC和Spring Boot项目的项目代码,自动生成JSON格式的描述文件。本身不是属于Swagger官网提供的,在这里列出来做个说明,方便后面作一个使用的展开。

实际应用

在实际开放应用中,我们使用的是Spring-swagger,其中已经使用了上述的一些功能,我们只需要详细去了解Spring boot中如何去整合使用Swagger即可

SpringBoot整合Swagger

2.9.2 常用版本
1、引入依赖

直接搜素springfox,导入swagger2和swagger ui即可,此处选择的是引用量最多的版本,2.9.2

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
2、编写Swagger配置类
package com.xshiedu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xshiedu.controller"))
                .paths(PathSelectors.any())
                .build().apiInfo(new ApiInfoBuilder()
                        .title("speringboot-day02")
                        .description("这是一个springboot的案例项目")
                        .version("2.0")
                        .contact(new Contact("fanglaoc","www.xshiedu.com","597830351@qq.com"))
                        .build());
    }
}
3、访问Swagger管理界面

比如此处端口号为8080,则对应的访问页为http://localhost:8080/swagger-ui.html

需要主题的是Swagger3.0后对应的访问页面方式与之前一些区别

image-20201119125157358

因为之前编写代码时,采用的是rest风格的接口声明

image-20201119130044009

在对应的Swagger控制中即可完成类似postman的测试功能

3.0.x版本(目前最新)
1、引入依赖

引入依赖方式与2.9.2类似,只不过需要引入多一个包,springfox-boot-starter

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0</version>
</dependency>
2、编写Swagger配置类
package com.xshiedu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableOpenApi
public class SwaggerConfig {
    @Bean
    public Docket createRestApi(){
        return new Docket(DocumentationType.SWAGGER_2)
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xshiedu.controller"))
                .paths(PathSelectors.any())
                .build().apiInfo(new ApiInfoBuilder()
                        .title("方佬C的测试项目")
                        .description("方佬C的测试项目的描述,这是一个XXX项目,拥有非常XXX的功能")
                        .version("1.0")
                        .contact(new Contact("fanglaoc","http://www.xshiedu.com","597830351@qq.com"))
                        .build());
    }
}
3、访问Swagger管理界面

此处需要注意的是,swagger3.0后对应的页面访问地址更改为http://localhost:8080/swagger-ui/index.htm

Swagger相关注解

@Api

用于指定控制器类的描述,作用范围在 类

@Controller
@Api(tags = "商品详情相关接口")
@RequestMapping("goodsOpr")
public class GoodsDetailController {
}
@ApiOperation

用于指定的接口方法的描述,作用范围在 方法上

@GetMapping("getAll")
@ResponseBody
@ApiOperation(value = "获取商品详情",notes = "<span style='color:red'>描述:</span>&nbsp;主要用于获取商品的详情")
public ServerResponse getGoodsDetail(Param param){
    return goodsDetailService.getGoodsDetail(param);
}
@ApiImplicitParams与@ApiImplicitParam

使用该注解,可以为参数设置一些初始化的值

@GetMapping("details")
@ResponseBody
@ApiImplicitParams({
    @ApiImplicitParam(name = "id",value = "商品编号",defaultValue = "2",dataType = "int")
})
public ServerResponse getGoodsDetail(Integer id){
    return goodsDetailService.getGoodsById(id);
}

其中需要注意的是,如果对应的接口是restful风格,则需要加多一个paramType参数,并将其设置 为path,如下所示

@GetMapping("details/{id}")
@ResponseBody
@ApiImplicitParams({
    @ApiImplicitParam(name = "id",value = "商品编号",defaultValue = "2",paramType = "path",dataType = "int")
})
public ServerResponse getGoodsDetail(@PathVariable Integer id){
    return goodsDetailService.getGoodsById(id);
}

对应请求参数为Json格式的,则无需进行额外的操作,会自行解析对象,拼接json

@ApiResponses和@ApiResponse
@ApiResponses({
        @ApiResponse(code = 401,message = "当前请求没有授权"),
        @ApiResponse(code = 404,message = "路径错误"),
})

api的相应结果,对应配置如下

image-20201119132651790

7、SpringMvc视图配置

要在springboot中使用springmvc,只需要引入spring-boot-starter-web即可,而实际上在我们创建好对应的web项目时,就已经完成好了这样的操作(注意springboot项目中的pom文件是继承自spring-boot-starter-parent):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

可以在application.yaml文件中对springmvc的一些参数进行配置

静态资源过滤

mvc中可以使用以下的一些方式对资源进行配置

static-path-pattern:过滤的资源路径匹配规则

resources: static-locations 配置过滤后相应的静态文件夹路径(注意,系统默认提供classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/等静态资源过滤路径)

spring
  mvc:
    static-path-pattern: /**
  resources:
    static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/templates/,classpath:/userdesign/

8、模版框架Thymeleaf

引言

之前我们学习过的Java模版框架有Jsp,实际上这类框架属于后端模版框架,在前后端分离的项目中使用较少(如果后期工作中没有前端开发人员,个人又不想用前端的模版框架,那么可以使用JSP或者thymeleaf这类专门的后端模版框架)

**强烈注意:**各位要知道目前学过的后端模版框架有jsp以及thymeleaf

知识回顾

1)JSP中的EL,对应的${}为对应的界定符

2)Spring中的SpEL,对应的#{}为对应的界定符

3)mybatis中,#{}表示对应的占位符,而实际上也可以使用${},但二者会有一些区别(常问的面试题

  • #方式能够很大程度防止sql注入,$方式无法防止Sql注入。
  • $方式一般用于传入数据库对象,例如传入表名.
  • 一般在mybatis的mapper文件中,能用#的就别用$
  • #{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号,在sql执行前会使用PreparedStatement的参数设置方法,按序给sql的?号占位符设置参数值

4)在配置文件中,可以先在配置文件中定义好属性,再通过${}的方式获取(比如datasource连接参数、pom依赖的版本号管理)

简介

Spring官方支持的服务的渲染模板中,并不包含jsp。而是Thymeleaf和Freemarker等,而Thymeleaf与SpringMVC的视图技术,及SpringBoot的自动化配置集成非常完美,几乎没有任何成本,你只用关注Thymeleaf的语法即可

  • 动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。
    • 原因(面试题):thymeleaf自身支持 html 原型,在html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解析html 时会忽略未定义的标签属性,因而thymeleaf 的模板可以静态地运行;当有数据返回到页面时,thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
  • 开箱即用:它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰,同时开发人员也可以扩展和创建自定义的方言。(然而Vue更香
  • 多方言支持:Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。(然而前后端分离,Vue更香
  • 与SpringBoot完美整合,SpringBoot提供了Thymeleaf的默认配置,并且为Thymeleaf设置了视图解析器,我们可以像以前操作jsp一样来操作Thymeleaf,代码几乎没有任何区别,就是在模板语法上有区别

简单案例

1、引入依赖

spring-boot项目自身就集成了thymeleaf,如对版本无特殊需求,则直接用springboot项目中默认配置的版本

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2、关闭thymeleaf缓存

thymeleaf默认是开启缓存的,因此经常性会有一种情况,数据库的内容更新,但对应的前端页面

spring:  
  thymeleaf:
    cache: false
3、spring-boot默认配置类

通过观察ThymeleafProperties默认配置类可以得知,对应的默认前缀后缀如下,当然也可以通过修改prefix和suffix对前后缀进行修改

image-20201120115235511

如果希望修改,可以通过yaml文件直接修改

  thymeleaf:
    cache: false
    prefix: classpath:/thymeleafPages/
    suffix: .html

如果这么写,则需要在对应的项目路径下创建一个thymeleafPages文件夹(如下所示),即名字用户自定义

image-20201120120655911

4、新建一个Controller接口

需要注意的是,Thymeleaf的主要作用是把model中的数据渲染到html中,所以需要使用到model

@RequestMapping("thymeleaf")
public String testThymeleaf(Model model){
    GoodsSort goodsSort = new GoodsSort(1,"肉类");
    model.addAttribute(goodsSort);
    return "hello";
}
5、在配置好的前缀文件夹中创建html文件

需要注意的是,需要给html头部添加对应的前缀

<html lang="en" xmlns:th="http://www.thymeleaf.org">

html内容

需要注意的是,如果此处没有热部署,每次都需要重新启动(Vue的话则基本不需要)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${goodsSort.id}"></h1>
    <h1 th:text="${goodsSort.name}"></h1>
</body>
</html>

相关语法

与JSP类似的,作为一个模版框架,提供了一系列的语法,实际上与OGNL(Object Graph Navigation Language,对象导航语言)类似,按如下内容进行展开

  • 变量
  • 方法
  • 条件判断
  • 循环
  • 运算 [ 逻辑运算、布尔运算、比较运算、条件运算 ]
  • 其它
变量

通过model的方式进行设置,对应的语法与以前的JSP中的EL表达式很相似,但这个为OGNL

<body>
    <h1 th:text="${goodsSort.id}">你好</h1>
    <h1 th:text="${'商品编号:' + goodsSort.id}"></h1>
    <h1 th:text="${msg}">你好啊啊啊</h1>
</body>
@RequestMapping("thymeleaf")
public String testThymeleaf(Model model){
    GoodsSort goodsSort = new GoodsSort(1,"肉类");
    String msg = "hello";
    model.addAttribute(goodsSort);
    model.addAttribute(msg);
    return "hello";
}

Thymeleaf崇尚模板是纯正的html代码,脱离模板引擎,在纯静态环境也可以直接运行,如果我们直接在html中编写 ${}这样的表达式,显然在静态环境下就会出错,这不符合Thymeleaf的理念。

Thymeleaf中所有的表达式都需要写在"指令"中,指令是HTML5中的自定义属性,在Thymeleaf中所有指令都是以th:开头。因为表达式${user.name}是写在自定义属性中,因此在静态环境下,表达式的内容会被当做是普通字符串,浏览器会自动忽略这些指令,这样就不会报错了。

如果我们不经过SpringMVC,而是直接用浏览器打开编写的页面:在静态环境下,th指令不会被识别,会显示对应的缺省内容,“你好啊啊啊”

兼容性问题(data-th-text)

如果浏览器不支持h5或者这种th:的命名空间写法,那么可以把th:text换成 data-th-text

防止html注入 (th:utext)

th:text指令出于安全考虑,会把表达式读取到的值进行处理

如,<p>你好</p>将会被格式化输出为$lt;p$gt;你好$lt;/p$lt;如果想要不进行格式化输出,而是要输出原始内容,则使用th:utext来代替

方法

ognl表达式本身就支持方法调用

<h1 th:text="${msg.split('')[0]}">你好啊啊啊</h1>

最后的结果为h,即将 hello 分割为数组后,拆分成单个元素的效果

h
常用内置对象
对象 作用
#ctx 获取Thymeleaf自己的Context对象
#requset 如果是web程序,可以获取HttpServletRequest对象
#response 如果是web程序,可以获取HttpServletReponse对象
#session 如果是web程序,可以获取HttpSession对象
#servletContext 如果是web程序,可以获取HttpServletContext对象

9、持久层辅助工具–通用mapper

springboot整合通用mapper的方式与之前有所不同,具体步骤如下:

1、引入依赖

因为Springboot中整合了通用mapper,即tk.mybatis,因而在使用时可以不添加对应的版本号

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>RELEASE</version>
</dependency>

2、创建对应的表与实体类

@Table 注解可以配置对应的实体类对应的表名(不写默认与实体类同名)

@Id 配置的为对应实体类的主键(用于对应通用mapper提供的 与主键相关的方法,比如selectByPrimaryKey)

@GeneratedValue 配置的为主键的自增

@Column 配置的为对应的属性与数据库表字段的对应(不写默认同名)

package com.xshiedu.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

import javax.lang.model.element.Name;
import javax.persistence.*;
import java.io.Serializable;

/**
 * @Author FangLaoC
 * @Date 2020-11-24
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "dog")
public class Dog  implements Serializable {

	private static final long serialVersionUID =  7655274100142747381L;

	/**
	 * 自增id
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;

	/**
	 * 名字
	 */
	@Column(name = "name")
	private String name;

}

3、编写对应的Mapper接口

public interface DogMapper extends Mapper<Dog> {
}

4、编写对应的接口以及实现类

public interface DogService {
    List<Dog> getDogs();
}
@Service
public class DogServiceImpl implements DogService {
    @Autowired
    DogMapper dogMapper;

    @Override
    public List<Dog> getDogs() {
        return dogMapper.selectAll();
    }
}

5、对应的controller编写

@RequestMapping("dog")
@ResponseBody
public List<Dog> getDogs(){
    return dogService.getDogs();
}

6、添加@MapperScan注解

与之前类似,在启动类中添加@MapperScan注解,但要替换为tk.mybatis.MapperScan类

@MapperScan("com.xshiedu.mapper")
@SpringBootApplication
public class SpringbootDay02Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootDay02Application.class, args);
        Environment environment = context.getBean(Environment.class);
        try {
            Runtime.getRuntime().exec("cmd /c start http://localhost:" +environment.getProperty("server.port"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

10、分页插件–pagehelper

1、引入springboot starter对应的pagehelper

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.3.0</version>
</dependency>

2、在对应的application.yaml中配置对应的分页参数

pagehelper:
  dialect: mysql
  auto-runtime-dialect: on
  reasonable: true

3、对应的sql操作

@Override
public PageInfo<Cat> getCats2(Integer current, Integer pageSize) {
    PageHelper.startPage(current,pageSize);
    List<Cat> cats = catMapper.selectList(null);
    PageInfo<Cat> pageInfo = new PageInfo<>(cats);
    return pageInfo;
}

11、持久层辅助工具–mybaits plus

简介

mybatis plus本身是Mybatis的增强工具,不是新的框架,只是在原有的Mybatis基础上进行增强,简化开发,提高开发效率

官方链接:https://baomidou.com/

特点

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CRUD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

支持各类数据库

  • mysql 、mariadb 、oracle 、db2 、h2 、hsql 、sqlite 、postgresql 、sqlserver 、presto 、Gauss 、Firebird
  • Phoenix 、clickhouse 、Sybase ASE 、 OceanBase 、达梦数据库 、虚谷数据库 、人大金仓数据库 、南大通用数据库 、

组成结构

image-20201126071815806

第一个简单的mybatis plus案例

1、引入依赖

在引入依赖时,需要注意此时无需引入mybatis有关的依赖了,mybatis plus已经提供了

如下依赖二选一

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.4.1</version>
</dependency>

注意在springboot项目中引入如下依赖,对应的application配置文件会有提示(推荐)
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
2、创建对应的表和实体类

通过该方式可以得知,其与通用mapper极其相似,

package com.xshiedu.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.io.Serializable;


@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "cat")
public class Cat  implements Serializable {

	private static final long serialVersionUID =  5110000713364692791L;

	/**
	 * 猫的id
	 */
	@TableId(value = "id",type = IdType.AUTO)
	private Integer id;

	/**
	 * 名字
	 */
	@TableField(value = "name")
	private String name;

}

对应表的sql语句

create table cat
(
    id int auto_increment comment '猫的id'
        primary key,
    name varchar(20) null comment '名字'
);

insert into cat
values(null,'小猫'),(null,'小狗'),(null,'小花'),(null,'小力')
3、编写对应的Mapper接口
public interface CatMapper extends BaseMapper<Cat> {
}
4、编写对应的Service接口以及实现类
public interface CatService {
    List<Cat> getCats();
}

@Service
public class CatServiceImpl implements CatService {
    @Autowired
    CatMapper catMapper;

    @Override
    public List<Cat> getCats() {
        return catMapper.selectList(null);
    }
}

5、对应的controller
@RequestMapping("cat")
@ResponseBody
public List<Cat> getCats(){
    return catService.getCats()
        ;
}
6、添加@MapperScan注解

MapperScan注意为spring框架中的原来的包,此处要与通用mapper中的tk.mybatis中的注解区分开

@SpringBootApplication
@MapperScan("com.xshiedu.mapper")
public class TestmpApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestmpApplication.class, args);
    }
}

如果项目较为简单,只是用mabatis-plus中的方法,yaml文件中的mybatis配置中的mapper文件位置以及起别名均可省略

mybatis:
  mapper-locations: classpath:com/xshiedu/mapper/*Mapper.xml
  type-aliases-package: com.xshiedu.pojo

常用注解

@TableName :数据库表的映射名

@TableId : @TableId(value="id", type= IdType.AUTO) 自增且为主键

@TableField:数据库列的映射名

补充Lombok的另外2个注解:

@Accessors(chain = true) 链式操作
@EqualsAndHashCode(callSuper = false) 比较方法的重写以及HashCode方法的生成

package com.xshiedu.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 
 * </p>
 *
 * @author jish
 * @since 2020-11-27
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)

@TableName("cat")
public class Cat implements Serializable {

    private static final long serialVersionUID=1L;

    /**
     * 猫的id
     */
    @TableId(value="id", type= IdType.AUTO)
    private Integer id;
    
    /**
     * 名字
     */
    @TableField(value="name")
    private String name;

}

代码生成器

1、引入依赖
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.2</version>
</dependency>

**注意:**如果使用官方默认的模版以及生成器,则去官网自己下,然后步骤2、3则无参考价值,2、3步主要为用户自定义的模版

2、代码生成器文件

随便找个地方丢

package com.xshiedu;


import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import org.apache.commons.lang3.StringUtils;


import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        mpg.setTemplateEngine(new VelocityTemplateEngine());

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("fanglaoc");
        gc.setOpen(false);
        gc.setSwagger2(false); //实体属性 Swagger2 注解
        gc.setKotlin(false);
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/goodssystem?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
//        pc.setModuleName(scanner("mp"));
        pc.setParent("com.xshiedu");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };



        /*xml模版*/
         String templatePath = "/templates/mapper.xml.vm";
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 + pc.getModuleName()
                return projectPath + "/src/main/resources/com/xshiedu/mapper"
                        + "/" + tableInfo.getEntityName() + "Mapper" +StringPool.DOT_XML;
            }
        });

        /*controller模版*/
        templatePath = "/templates/controller.java.vm";
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 + pc.getModuleName()
                String expand = projectPath + "/src/main/java/com/xshiedu/controller";
                String file = String.format((expand + File.separator + "%s" + ".java"), tableInfo.getControllerName());
                return file;
            }
        });

        /*mapper接口*/
        templatePath = "/templates/mapper.java.vm";
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 + pc.getModuleName()
                String expand = projectPath + "/src/main/java/com/xshiedu/mapper";
                String file = String.format((expand + File.separator + "%s" + ".java"), tableInfo.getMapperName());
                return file;
            }
        });

        /*实体类模版*/
        templatePath = "/templates/entity.java.vm";
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 + pc.getModuleName()
                String expand = projectPath + "/src/main/java/com/xshiedu/entity";
                String file = String.format((expand + File.separator + "%s" + ".java"), tableInfo.getEntityName());
                return file;
            }
        });

        /*pojo模版*/
        templatePath = "/templates/service.java.vm";
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 + pc.getModuleName()
                String expand = projectPath + "/src/main/java/com/xshiedu/service";
                String file = String.format((expand + File.separator + "%s" + ".java"), tableInfo.getServiceName().substring(1));
                return file;
            }
        });

        /*pojo模版*/
        templatePath = "/templates/serviceImpl.java.vm";
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 + pc.getModuleName()
                String expand = projectPath + "/src/main/java/com/xshiedu/service/impl";
                String file = String.format((expand + File.separator + "%s" + ".java"), tableInfo.getServiceImplName());
                return file;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig.setXml(null);
        templateConfig.setController(null);
        templateConfig.setMapper(null);
        templateConfig.setServiceImpl(null);
        templateConfig.setService(null);
        templateConfig.setEntity(null);
        mpg.setTemplate(templateConfig);



        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
//         templateConfig.setEntity("templates/entity.java");
//         templateConfig.setService("templates/service.java");
//         templateConfig.setController("templates/controller.java");
//         templateConfig.setServiceImpl("templates/serviceImpl.java");
//         templateConfig.setMapper("templates/mapper.java");

//        templateConfig.setXml(null);
//        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//        strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);

        // 公共父类
//        strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
//        strategy.setSuperEntityColumns("id");
        //表名,多个英文逗号分割
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);

        mpg.execute();
    }
}
3、将模版文件放到指定目录

image-20201127161053252

controller.java.vm
package ${package.Controller};


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

#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end

/**
 * <p>
 * $!{table.comment} 前端控制器
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end

#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end

}

#end
entity.java.vm
package ${package.Entity};

#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.EqualsAndHashCode;
#end

/**
 * <p>
 * $!{table.comment}
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
#if(${entityLombokModel})
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
#end

###if(${table.convert})
@TableName("${table.name}")
###end
#if(${swagger2})
@ApiModel(value="${entity}对象", description="$!{table.comment}")
#end
#if(${superEntityClass})
public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#else
public class ${entity} implements Serializable {
#end

    #if(${entitySerialVersionUID})
    private static final long serialVersionUID=1L;
    #end

#foreach($field in ${table.fields})
    #if(${field.keyFlag})
        #set($keyPropertyName=${field.propertyName})
    #end

    #if("$!field.comment" != "")
    /**
     * ${field.comment}
     */
    #end
    #if(${field.keyFlag})
    ## 主键
        #if(${field.keyIdentityFlag})
@TableId(value="${field.name}", type= IdType.AUTO)
        #end
    #end
    #if(!${field.keyIdentityFlag})
    @TableField(value="${field.name}")
    #end
    private ${field.propertyType} ${field.propertyName};
    #end


}
mapper.java.vm
package ${package.Mapper};

import ${package.Entity}.${entity};
import ${superMapperClassPackage};

/**
 * <p>
 * $!{table.comment} Mapper接口
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {

}
mapper.xml.vm
<?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="${package.Mapper}.${table.mapperName}">

    #if(${enableCache})
        <!-- 开启二级缓存 -->
        <cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>

    #end
    #if(${baseResultMap})
        <!-- 通用查询映射结果 -->
        <resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
            #foreach($field in ${table.fields})
                #if(${field.keyFlag})##生成主键排在第一位
                    <id column="${field.name}" property="${field.propertyName}" />
                #end
            #end
            #foreach($field in ${table.commonFields})##生成公共字段
                <result column="${field.name}" property="${field.propertyName}" />
            #end
            #foreach($field in ${table.fields})
                #if(!${field.keyFlag})##生成普通字段
                    <result column="${field.name}" property="${field.propertyName}" />
                #end
            #end
        </resultMap>

    #end
    #if(${baseColumnList})
        <!-- 通用查询结果列 -->
        <sql id="Base_Column_List">
                #foreach($field in ${table.commonFields})
                    ${field.columnName},
                #end
                ${table.fieldNames}
        </sql>
    #end
</mapper>
service.java.vm
package ${package.Service};

import ${package.Entity}.${entity};
import ${superServiceClassPackage};

/**
 * <p>
 * $!{table.comment} 服务类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */

public interface ${table.serviceName.substring(1)}{

}
serviceImpl.java.vm
package ${package.ServiceImpl};

import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName.substring(1)};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;


/*
** <p>
* $!{table.comment} 服务实现类
* </p>
*
* @author ${author}
* @since ${date}
*/

@Service
public class ${table.serviceImplName} implements ${table.serviceName.substring(1)} {

}

分页插件

mybatis中也提供了分页插件,与PageHelper插件类似,通用mapper和pageHelper通常一起整合,而mp本身自带分页插件,具体的配置方式如下所示

1、配置分页拦截器

如下所示,在以前的注解版项目中,我们也为PageHelper设置了分页拦截器image-20201130111523889

对应的mp中的也要设置对应的拦截器

@Configuration
//对应的mapper路径
@MapperScan("com.xshiedu.mapper")
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
         paginationInterceptor.setOverflow(true);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
         paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}
2、在service层实现类中编写对应代码

在pagehelper中,对应的写法为

PageHelper.startPage(pageNum, pageSize);
List<Love> loveList = loveMapper.selectAll();
PageInfo<Love> pageInfo = new PageInfo<>(loveList);

在MP分页插件中使用的方式为

@Service
public class CatServiceImpl implements CatService {
    @Autowired
    private CatMapper catMapper;

    @Override
    public IPage<Cat> getCats(Integer current, Integer pageSize) {
        IPage<Cat> page = new Page<>(current,pageSize);
        catMapper.selectPage(page,null);
        return page;
    }
}

但mybatis-plus中的分页插件存在缺陷,不能像pagehelper一样,对分页进行合理化,如数据一共有5页,当查询第6页时,查询的为最后一页,而mp中的分页插件只能设置超过界限,查询对应的数据页中的第一页

image-20201201090101240

3、Controller层
@RestController
@RequestMapping("/cat")
public class CatController {
    @Autowired
    private CatService catService;

    @RequestMapping("getCat")
    public IPage<Cat> getCats(Integer current, Integer pageSize){
        return catService.getCats(current,pageSize);
    }
}

注意

在实际应用中,可以采取pagehelper和mp的mapper方式进行组合使用

mybatis中的复杂条件查询

1、Wrapper简介

如果想进行复杂条件查询,那么需要使用条件构造器,需要使用到Wrapper

image-20201201121622033

  • Wrapper : 条件构造抽象类,最顶端父类
    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : Entity 对象封装操作类,不是用lambda语法
      • UpdateWrapper : Update 条件封装,用于Entity对象更新操作
      • AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
        • LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper
2、使用Wrapper

如下将按照分类,对Wrapper的使用进行举例使用

1、ge、gt、le、lt、isNull、isNotNull
  • ge 大于等于
  • gt 大于
  • le 小于等于
  • lt 小于
  • isNull 为空
  • isNotNull 不为空

通过观察可得,对应的Wrapper条件可以使用链式操作

@Override
public PageInfo<Cat> getCats2(Integer current, Integer pageSize) {
    PageHelper.startPage(current,pageSize);
    QueryWrapper<Cat> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNotNull("name")
        .ge("id",2)
        .lt("id",4);
    List<Cat> cats = catMapper.selectList(queryWrapper);
    PageInfo<Cat> pageInfo = new PageInfo<>(cats);
    return pageInfo;
}
SELECT count(0) FROM cat WHERE (name IS NOT NULL AND id >= ? AND id < ?)
2、eq、ne
  • eq 等于
  • ne 不等于
queryWrapper.isNotNull("name")
    .ge("id",2)
    .lt("id",4)
    .eq("name","小花");
SELECT count(0) FROM cat WHERE (name IS NOT NULL AND id >= ? AND id < ? AND name = ?)
3、between、notBetween
  • between 在…之间
  • notBetween 不在…之间
@Override
public PageInfo<Cat> getCats2(Integer current, Integer pageSize) {
    PageHelper.startPage(current,pageSize);
    QueryWrapper<Cat> queryWrapper = new QueryWrapper<>();
    queryWrapper.between("id",1,3);
    List<Cat> cats = catMapper.selectList(queryWrapper);
    PageInfo<Cat> pageInfo = new PageInfo<>(cats);
    return pageInfo;
}
SELECT count(0) FROM cat WHERE (id BETWEEN ? AND ?)
4、allEq

allEq 表示全等于, 一般用于整个对象的比较

@Override
public PageInfo<Cat> getCats2(Integer current, Integer pageSize) {
    PageHelper.startPage(current,pageSize);
    //构架查询条件
    Map<String,Object> queryMapper = new HashMap<>();
    queryMapper.put("id",1);
    queryMapper.put("name","小猫");
    QueryWrapper<Cat> queryWrapper = new QueryWrapper<>();
    //设置条件
    queryWrapper.allEq(queryMapper);

    List<Cat> cats = catMapper.selectList(queryWrapper);
    PageInfo<Cat> pageInfo = new PageInfo<>(cats);
    return pageInfo;
}
SELECT count(0) FROM cat WHERE (name = ? AND id = ?)
5、like、notLike、likeLeft、likeRight
  • like为给属性前后默认加%%
  • not like 相当于不属于该模糊条件
  • likeLeft为在左边加
  • likeRight为在右边加
@Override
public PageInfo<Cat> getCats2(Integer current, Integer pageSize) {
    PageHelper.startPage(current,pageSize);
    QueryWrapper<Cat> queryWrapper = new QueryWrapper<>();
    //设置条件
    queryWrapper.likeRight("name","小");
    List<Cat> cats = catMapper.selectList(queryWrapper);
    PageInfo<Cat> pageInfo = new PageInfo<>(cats);
    return pageInfo;
}
6、in、notIn、inSql、notinSql
  • notIn("age",{1,2,3})--->age not in (1,2,3

  • notIn("age", 1, 2, 3)--->age not in (1,2,3)

    对于较为复杂的需要子查询,可以使用inSql 或者notInSql

    形如inSql("age", "1,2,3,4,5,6")—>age in (1,2,3,4,5,6),如果希望1,2,3,4,5,6是通过sql语句查出来的,可以使用如下方式

  • inSql("id", "select id from table where id < 3")—>id in (select id from table where id < 3)

7、or、and

在连接对应的条件时,如果不使用连接方法or或者and,那么默认为or

queryWrapper.isNotNull("name")
    .ge("id",2)
    //.and()
    .lt("id",4)
    //.and
    .eq("name","小花");

如果希望他们之间的连接为or,可以使用or()

queryWrapper.isNotNull("name")
    .ge("id",2)
    .or()
    .lt("id",4)
SELECT count(0) FROM cat WHERE (name IS NOT NULL AND id >= ? OR id < ?)
8、orderBy、orderByDesc、orderByAsc

如果希望对最后查询的结果集进行排序,则可以使用该方式

queryWrapper.orderByDesc("id")
    .likeRight("name","小");

使用这些方法有一个好处,没有明确要求放置的位置

SELECT id,name FROM cat WHERE (name LIKE ?) ORDER BY id DESC LIMIT ?
9、last

将last括号内的内容直接拼接到sql语句的末尾

注意

1、使用该方式存在sql注入的风险

2、last方法一个wrapper只能使用一次,如果存在多个,以最后一个last方法中的内容为准

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.last("order by id desc");
10、select

添加后,可以指定查找语句时,选择想要查找的对应列,当然如果不加默认查所有

queryWrapper.orderByDesc("id")
    .select("name")
    .likeRight("name","小");
SELECT name FROM cat WHERE (name LIKE ?) ORDER BY id DESC LIMIT ?
11、exists notExists
  • exists 如果存在结果集
  • notExists 如果不存在结果集

image-20201201141216266

可以得知,对应的参数也为子查询语句

QueryWrapper<Cat> queryWrapper = new QueryWrapper<>();
//设置条件
queryWrapper.orderByDesc("id")
    .select("name")
    .likeRight("name","小")
    .exists("select 1"); //(此处以一个简答的子查询select 1为例子)
SELECT name FROM cat WHERE (name LIKE ? AND EXISTS (select 1)) ORDER BY id DESC LIMIT ?
12、set setSql
  • set 设置好对应的条件
  • setSql 可以直接写上条件语句,其参数也可以填写 子查询语句

针对更新而言,我们知道对应mp中的mapper提供了update的2个重载方法,如下所示

image-20201201140106943

updateById(T t)

单个参数的方法只需要将整个对象丢进去即可

update(T t ,Wrapper<T> updateWrapper)

多个参数的话,需要用户去构建对应都的updateWrapper,即可以传入除了对象外,自定的数值,如下所示

 //修改值
User user = new User();
user.setAge(99);

UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
    .like("name", "h")
    .set("name", "弟弟")//除了可以查询还可以使用set设置修改的字段
    .setSql(" email = '123@qq.com'");//也可以写为子查询
int result = userMapper.update(user, userUpdateWrapper);
UPDATE user SET age=?, update_time=?, name=?, email = '123@qq.com' WHERE name LIKE ? 
13、lambda嵌套应用案例–or/and

对于or里面嵌套了一个and语句的话,又特别想要使用的方式来实现,而不是采用写死的sql,则可以使用lambda嵌套来实现

//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
    .like("name", "h")
    .or(i -> i.eq("name", "李白").ne("age", 20));
int result = userMapper.update(user, userUpdateWrapper);
UPDATE user SET name=?, age=?
WHERE name LIKE ? 
OR ( name = ? AND age <> ? )