SpirngBoot<读完包你更上一层楼>

时间:2022-12-07 12:51:39

目录

一、SpringBoot概念 

1.1 什么是SpringBoot

1.2 为什么要学习SpringBoot

1.3 SpringBoot的特点

1.4 总结

二、入门案例

2.1 创建工程

2.1.1 创建一个空工程

2.1.2 工程名为project_test:

2.1.3 设置jdk版本为1.8

2.1.4 新建一个module

2.1.5 填写项目坐标

2.2 添加依赖

2.2.1 添加父工程坐标

2.2.2 添加web启动器

2.2.3 管理jdk版本

2.2.4 完整的pom文件 

2.3 启动类

2.4 编写controller

2.5 启动测试

三、全注解配置和属性注入

3.1 回顾历史 

3.2 spring全注解配置

3.3 SpringBoot的属性注入

3.4 更优雅的注入

四、自动配置原理

4.1 @SpringBootApplication

4.1.1 @SpringBootConfiguration

4.1.2 @ComponentScan

4.1.3 @EnableAutoConfiguration

4.2 默认配置原理

4.3 总结

五、整合SpringMVC

5.1 修改端口

5.2 访问静态资源

5.3 添加拦截器

六、整合jdbc

七、整合mybatis

7.1 mybatis整合

7.1.1 测试案例 

7.1.2 Mapper的加载接口代理对象方式有2种

7.2 通用mapper

7.2.1 概念

7.2.2 案例测试 

7.2.3 tk.mybatis中Mapper的内置方法 

 八、thymeleaf

8.1 入门案例

8.1.1 编写接口

8.1.2 编写一个controller,返回一些用户数据,放入模型中,等会在页面渲染 

8.1.3 引入启动器

8.1.4 静态页面

8.1.5 测试

8.1.6 模板缓存

8.2 thymeleaf详解

8.2.1 表达式

8.2.2 表达式常见用法

8.2.3 常用th标签

8.2.4 基本用法

8.3 使用thymeleaf布局

九、Mybatis Plus 

9.1 简介

9.2 快速入门

9.2.1 创建工程,引入依赖

9.2.2 配置文件application.yml

9.2.3 实体类

9.2.4 dao

9.2.5 启动类

9.2.6 测试

​9.3 常用注解

​9.3.1 mybatis plus注解策略配置

9.3.2 排除实体类中非表字段

9.4 内置增删改查

 9.4.1 增加

 9.4.2 删除

 9.4.3 更新

 9.4.4 查询

9.5 分页

9.5.1 内置分页 

9.5.2 自定义xml分页

9.5.3 pageHelper分页 


一、SpringBoot概念 

在这一部分,我们主要了解以下3个问题:
· 什么是SpringBoot
· 为什么要学习SpringBoot
· SpringBoot的特点

1.1 什么是SpringBoot

springboot是spring快速开发脚手架,通过约定大于配置的方式,快速构建和启动spring项目

比如在springmvc的时候,xml文件中需要配置DispatcherServlet,视图解析器,静态资源访问等等,springboot都会约定好,就不用再xml中配置了。

1.2 为什么要学习SpringBoot

spring的缺点:

1,复杂的配置

项目各种配置是开发时的损耗, 写配置挤占了写应用程序逻辑的时间。

2,混乱的依赖管理。

项目的依赖管理非常的繁琐。决定项目里要用哪些库就已经够让人头痛的了,还要知道这些库的哪个版本 和其他库不会有冲突,这是一个棘手的问题。并且,一旦选错了依赖的版本,随之而来的就是各种的不兼容 的bug。 spring boot 可以解决上面2个问题

1.3 SpringBoot的特点

Spring Boot 特点:

快速开发spring应用的框架
内嵌tomcat和jetty容器,不需要单独安装容器,jar包直接发布一个web应用
简化maven配置,parent这种方式,一站式引入需要的各种依赖
基于注解的零配置思想
和各种流行框架,spring web mvc,mybatis,spring cloud无缝整合 

1.4 总结

spring boot 是spring快速开发脚手架,通过约定大于配置,优化了混乱的依赖管理,和复杂的配置,让我们用java -jar方式,运行启动java web项目 

二、入门案例

需求:创建HelloController,在页面中打印hello spring boot...

2.1 创建工程

2.1.1 创建一个空工程

SpirngBoot<读完包你更上一层楼>

2.1.2 工程名为project_test:

SpirngBoot<读完包你更上一层楼>

2.1.3 设置jdk版本为1.8

SpirngBoot<读完包你更上一层楼>

2.1.4 新建一个module

SpirngBoot<读完包你更上一层楼>

2.1.5 填写项目坐标

SpirngBoot<读完包你更上一层楼>

2.2 添加依赖

SpringBoot提供了一个名为spring-boot-starter-parent的构件,里面已经对各种常用依赖(并非全部)的版本进行了管理,我们的项目需要以这个项目为父工程,这样我们就不用操心依赖的版本问题了,需要什么依赖,直接引入坐标即可!

2.2.1 添加父工程坐标

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

2.2.2 添加web启动器

为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为 启动器 。因为我们是web项目,这里我们引入web启动器: 

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

需要注意的是,我们并没有在这里指定版本信息。因为SpringBoot的父工程已经对版本进行了管理了。这个时候,我们会发现项目中多出了大量的依赖: 

 SpirngBoot<读完包你更上一层楼>

 这些都是SpringBoot根据spring-boot-starter-web这个依赖自动引入的,而且所有的版本都已经管理好,不会出现冲突。

2.2.3 管理jdk版本

 默认情况下,maven工程的jdk版本是1.5,而我们开发使用的是1.8,因此这里我们需要修改jdk版本,只需要简单的添加以下属性即可:

    <properties>
        <java.version>1.8</java.version>
    </properties>

2.2.4 完整的pom文件 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lxy</groupId>
    <artifactId>spring-boot-demo-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

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

2.3 启动类

Spring Boot项目通过main函数即可启动,我们需要创建一个启动类:

SpirngBoot<读完包你更上一层楼>

 然后编写main函数:

package com.lxy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

2.4 编写controller

接下来,我们就可以像以前那样开发SpringMVC的项目了!
我们编写一个controller:

package com.lxy.controller;

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

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello, spring boot!";
    }
}

2.5 启动测试

接下来,我们运行main函数,查看控制台:

SpirngBoot<读完包你更上一层楼>

并且可以看到监听的端口信息: 

SpirngBoot<读完包你更上一层楼>

 1)监听的端口是8080
 2)SpringMVC的映射路径是:/
 3) /hello 路径已经映射到了 HelloController 中的 hello() 方法

打开页面访问:http://localhost:8080/hello 

SpirngBoot<读完包你更上一层楼>

 测试成功了!

三、全注解配置和属性注入

在入门案例中,我们没有任何的配置,就可以实现一个SpringMVC的项目了,快速、高效!
但是有同学会有疑问,如果没有任何的xml,那么我们如果要配置一个Bean该怎么办?比如我们要配置一个数据库连接池,以前会这么玩:

<!-- 配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

现在该怎么做呢?

3.1 回顾历史 

事实上,在Spring3.0开始,Spring官方就已经开始推荐使用java配置来代替传统的xml配置了,我们不妨来回顾一下Spring的历史: 

1)Spring1.0时代在此时因为jdk1.5刚刚出来,注解开发并未盛行,因此一切Spring配置都是xml格式,想象一下所有的bean都用xml配置,细思极恐啊,心疼那个时候的程序员2秒.

2)Spring2.0时代Spring引入了注解开发,但是因为并不完善,因此并未完全替代xml,此时的程序员往往是把xml与注解进行结合,貌似我们之前都是这种方式。

3)Spring3.0及以后3.0以后Spring的注解已经非常完善了,因此Spring推荐大家使用完全的java配置来代替以前的xml,不过似乎在国内并未推广盛行。然后当SpringBoot来临,人们才慢慢认识到java配置的优雅。

3.2 spring全注解配置

spring全注解配置主要靠java类和一些注解,比较常用的注解有: 

@Configuration :声明一个类作为配置类,代替xml文件
@Bean :声明在方法上,将方法的返回值加入Bean容器,代替 <bean> 标签
@value :属性注入
@PropertySource :指定外部属性文件,

我们接下来用java配置来尝试实现连接池配置:

首先引入Druid连接池依赖:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

创建一个jdbc.properties文件,编写jdbc属性(可以拷贝):

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.usernam=root
jdbc.passwor=linda198721

然后编写代码: 创建一个JdbcConfig类

package com.lxy.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
    @Value("${jdbc.url}")
    String url;
    @Value("${jdbc.driverClassName}")
    String driverClassName;
    @Value("${jdbc.username}")
    String username;
    @Value("${jdbc.password}")
    String password;

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

解读:

@Configuration :声明我们 JdbcConfig 是一个配置类


@PropertySource :指定属性文件的路径是: classpath:jdbc.properties


通过 @Value 为属性注入值


通过@Bean将 dataSource() 方法声明为一个注册Bean的方法,Spring会自动调用该方法,将方法的返回值加入Spring容器中。默认的对象名id=方法名,可以通过@Bean("自定义名字"),来指定新的对象名 

然后我们就可以在任意位置通过 @Autowired 注入DataSource了!

我们在 HelloController 中测试:

package com.lxy.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.sql.DataSource;

@RestController
public class HelloController {

    @Autowired
    private DataSource dataSource;

    @GetMapping("/hello")
    public String hello(){
        return "hello, spring boot!"+dataSource;
    }
}

然后Debug运行并查看:

SpirngBoot<读完包你更上一层楼>

属性注入成功了! 

3.3 SpringBoot的属性注入

 在上面的案例中,我们实验了java配置方式。不过属性注入使用的是@Value注解。这种方式虽然可行,但是不够强大,因为它只能注入基本类型值。
在SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入。

1)我们新建一个类,用来进行属性注入:

package com.lxy.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {
    private String url;
    private String driverClassName;
    private String username;
    private String password;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

(1)在类上通过@ConfigurationProperties注解声明当前类为属性读取类
(2)prefix="jdbc" 读取属性文件中,前缀为jdbc的值。
(3)在类上定义各个属性,名称必须与属性文件中 jdbc. 后面部分一致
(4)需要注意的是,这里我们并没有指定属性文件的地址,所以我们需要把jdbc.properties名称改为application.properties,这是SpringBoot默认读取的属性文件名: 

SpirngBoot<读完包你更上一层楼>

 2)在JdbcConfig中使用这个属性:

package com.lxy.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {

    @Bean
    public DataSource dataSource(JdbcProperties jdbc) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(jdbc.getUrl());
        dataSource.setDriverClassName(jdbc.getDriverClassName());
        dataSource.setUsername(jdbc.getUsername());
        dataSource.setPassword(jdbc.getPassword());
        return dataSource;
    }
}

通过 @EnableConfigurationProperties(JdbcProperties.class) 来声明要使用 JdbcProperties 这个类的对象 .

3)可以通过以下方式注入JdbcProperties:

@Autowired注入:

@Autowired
private JdbcProperties prop;

 构造函数注入

private JdbcProperties prop;
public JdbcConfig(Jdbcproperties prop){
    this.prop = prop;
}

声明有@Bean的方法参数注入 

@Bean
public Datasource dataSource(JdbcProperties prop){
    // ...
}

本例中,我们采用第三种方式。

4)测试结果

SpirngBoot<读完包你更上一层楼>

5)这种方法的优势 

大家会觉得这种方式似乎更麻烦了,事实上这种方式有更强大的功能,也是SpringBoot推荐的注入方式。两者对比关系: SpirngBoot<读完包你更上一层楼>

 优势:

Relaxed binding:松散绑定
(1)不严格要求属性文件中的属性名与成员变量名一致。支持驼峰,中划线,下划线等等转换,甚至支持对象引导。比如:user.friend.name:代表的是user对象中的friend属性中的name属性,显然friend也是对象。@value注解就难以完成这样的注入方式。
(2)meta-data support:元数据支持,帮助IDE生成属性提示(写开源框架会用到)。

3.4 更优雅的注入

事实上,如果一段属性只有一个Bean需要使用,我们无需将其注入到一个类(JdbcProperties)中。而是直接在需要的地方声明即可:

(可删除之前创建的JdbcProperties类)在JdbcConfig中修改

package com.lxy.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class JdbcConfig {

    @Bean
    // 声明要注入的属性前缀,SpringBoot会自动把相关属性通过set方法注入到DataSource中
    @ConfigurationProperties(prefix = "jdbc")
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        return dataSource;
    }
}

我们直接把 @ConfigurationProperties(prefix = "jdbc") 声明在需要使用的 @Bean 的方法上,然后SpringBoot就会自动调用这个Bean(此处是DataSource)的set方法,然后完成注入。使用的前提是:该类必须有对应属性的set方法! 

四、自动配置原理

通过刚才的案例看到,一个整合了SpringMVC的WEB工程开发,变的无比简单,那些繁杂的配置都消失不见了,这是如何做到的?
这些都是从springboot启动器开始的:

SpirngBoot<读完包你更上一层楼>

 我们重点关注@SpringBootApplication注解

4.1 @SpringBootApplication

点击进入,查看源码:

SpirngBoot<读完包你更上一层楼>

 这里重点的注解有3个:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

4.1.1 @SpringBootConfiguration

我们继续点击查看源码:SpirngBoot<读完包你更上一层楼>

通过这段我们可以看出,在这个注解上面,又有一个 @Configuration 注解。这个注解的作用就是声明当前类是一个配置类,然后Spring会自动扫描到添加了 @Configuration 的类,并且读取其中的配置信息。

4.1.2 @ComponentScan

我们跟进源码:SpirngBoot<读完包你更上一层楼>

并没有看到什么特殊的地方。我们查看注释:SpirngBoot<读完包你更上一层楼> 

大概的意思:

配置组件扫描的指令。提供了类似与 <context:component-scan> 标签的作用
通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包 

而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中。 

4.1.3 @EnableAutoConfiguration

关于这个注解,官网上有一段说明:SpirngBoot<读完包你更上一层楼>

简单翻译一下:SpirngBoot<读完包你更上一层楼>

4.2 默认配置原理

@EnableAutoConfiguration会开启SpringBoot的自动配置,并且根据你引入的依赖来生效对应的默认配置,springboot如何做到的?
其实在我们的项目中,已经引入了一个依赖:spring-boot-autoconfigure,其中定义了大量自动配置类:

 SpirngBoot<读完包你更上一层楼>

SpirngBoot<读完包你更上一层楼>

 非常多,几乎涵盖了现在主流的开源框架,例如:

redis
jms
amqp
jdbc
jackson
mongodb
jpa
solr
elasticsearch

等等。。。。。

我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类:SpirngBoot<读完包你更上一层楼> 

打开WebMvcAutoConfiguration:SpirngBoot<读完包你更上一层楼> 

我们看到这个类上的4个注解: 

@Configuration :声明这个类是一个配置类

@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })

这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依赖自然会有,后两个需要引入SpringMVC才会有。这里就是判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效! 

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

这个条件与上面不同,OnMissingBean,是说环境中没有指定的Bean这个才生效。其实这就是自定义配置的入口,也就是说,如果我们自己配置了一个WebMVCConfigurationSupport的类,那么这个默认配置就会失效! 

接着,我们查看该类中定义了什么:

视图解析器:SpirngBoot<读完包你更上一层楼> 

处理器适配器(HandlerAdapter):SpirngBoot<读完包你更上一层楼> 

还有很多,这里就不一一截图了。 

4.3 总结

SpringBoot为我们提供了默认配置,而默认配置生效的条件一般有两个:

引入了相关依赖
没有自定义配置类

五、整合SpringMVC

刚才案例已经能实现mvc自动配置,这里我们主要解决以下3个问题 

修改端口
静态资源
拦截器配置

5.1 修改端口

查看SpringBoot的全局属性可知,端口通过以下方式配置:

# 映射端口
server.port=80

重启服务后测试:SpirngBoot<读完包你更上一层楼>

5.2 访问静态资源

ResourceProperties的类,里面就定义了静态资源的默认查找路径:

SpirngBoot<读完包你更上一层楼>

默认的静态资源路径为:

classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public 

只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。
我们习惯会把静态资源放在 classpath:/static/ 目录下。我们创建目录,并且添加一些静态资源:

SpirngBoot<读完包你更上一层楼>

重启项目后测试:

SpirngBoot<读完包你更上一层楼>

5.3 添加拦截器

拦截器也是我们经常需要使用的,在SpringBoot中该如何配置呢?

首先我们定义一个拦截器:

package com.lxy.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {
    private Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        logger.debug("处理器执行前执行!");
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        logger.debug("处理器执行后执行!");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        logger.debug("跳转后执行!");
    }

}

通过实现 WebMvcConfigurer 并添加 @Configuration 注解来实现自定义部分SpringMvc配置:

package com.lxy.config;

import com.lxy.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    /**
     * 通过@Bean注解,将我们定义的拦截器注册到Spring容器
     * @return
     */

    @Bean
    public LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    /**
     * 重写接口中的addInterceptors方法,添加自定义拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 通过registry来注册拦截器,通过addPathPatterns来添加拦截路径
        registry.addInterceptor(this.loginInterceptor()).addPathPatterns("/**");
    }
}

ant path路径匹配通配符
‘?’ 匹配任何单字符
‘*’ 匹配0或者任意数量的字符
‘/**’ 匹配0或者更多的目录  (代表根目录下的所有路径都被拦截)

结构如下:SpirngBoot<读完包你更上一层楼>

接下来运行并查看日志:

你会发现日志中什么都没有,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置。SpringBoot通过 logging.level.*=debug 来配置日志级别,*填写包名 

# 设置com.lxy包的日志级别为debug
logging.level.com.lxy=debug

 再次运行查看:

SpirngBoot<读完包你更上一层楼>

六、整合jdbc

导入资料中的t_user.sql文件  如果你也想要这个资源请在下发评论,我会发给你 

SpirngBoot<读完包你更上一层楼>

引入依赖

(引入依赖之前需要先把之前添加的德鲁伊连接池的依赖删除) 

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

当然,不要忘了数据库驱动,SpringBoot并不知道我们用的什么数据库,这里我们选择MySQL:

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>

配置连接池

其实,在刚才引入jdbc启动器的时候,SpringBoot已经自动帮我们引入了一个连接池: 

SpirngBoot<读完包你更上一层楼>

HikariCP应该是目前速度最快的连接池了,我们看看它与c3p0的对比: 

SpirngBoot<读完包你更上一层楼>

 因此,我们只需要在application.properties中指定连接池参数即可:

# 连接四大参数
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=linda198721

# 可省略,SpringBoot自动推断
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.hikari.idle-timeout=60000
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.minimum-idle=10

实体类创建:

package com.lxy.domain;

import java.util.Date;

public class User {
    private Long id;
    // 用户名
    //自动转换下换线到驼峰命名user_name -> userName
    private String userName;
    // 密码
    private String password;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    // 性别,1男性,2女性
    private Integer sex;
    // 出生日期
    private Date birthday;
    // 创建时间
    private Date created;
    // 更新时间
    private Date updated;
    // 备注
    private String note;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", name='" + name + '\'' +
                ", updated=" + updated +
                ", note='" + note + '\'' +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

 dao类创建:

package com.lxy.dao;

import com.lxy.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class JdbcDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<User> findAll(){
        return jdbcTemplate.query("select * from tb_user",new BeanPropertyRowMapper<>(User.class));
    }
}

测试:

package com.lxy.dao;

import com.lxy.domain.User;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
public class JdbcDaoTest extends TestCase {

    @Autowired
    private JdbcDao jdbcDao;

    @Test
    public void findAll(){
       jdbcDao.findAll().forEach(user -> {
           System.out.println(user);
       });
    }
}

SpirngBoot<读完包你更上一层楼>

这里可以看到SpringBoot支持下划线转驼峰的形式,成功取到userName。

七、整合mybatis

7.1 mybatis整合

7.1.1 测试案例 

SpringBoot官方并没有提供Mybatis的启动器,不过Mybatis官网自己实现了: 

<!--mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

配置,基本没有需要配置的:

# mybatis 别名扫描
mybatis.type-aliases-package=com.lxy.domain
# mapper.xml文件位置,如果没有映射文件,请注释掉
mybatis.mapper-locations=classpath:mappers/*.xml

实体类,直接使用jdbc用到的实体类

package com.lxy.domain;

import java.util.Date;

public class User {
    private Long id;
    // 用户名
    //自动转换下换线到驼峰命名user_name -> userName
    private String userName;
    // 密码
    private String password;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    // 性别,1男性,2女性
    private Integer sex;
    // 出生日期
    private Date birthday;
    // 创建时间
    private Date created;
    // 更新时间
    private Date updated;
    // 备注
    private String note;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", name='" + name + '\'' +
                ", updated=" + updated +
                ", note='" + note + '\'' +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

创建接口:

package com.lxy.dao;

import com.lxy.domain.User;

import java.util.List;

public interface UserDao {
    public List<User> findAll();
}

创建UserDao.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.lxy.dao.UserDao">
    <select id="findAll" resultType="user">
        select * from tb_user
    </select>
</mapper>

7.1.2 Mapper的加载接口代理对象方式有2种

第一种:使用@Mapper注解(不推荐)

需要注意,这里没有配置mapper接口扫描包,因此我们需要给每一个Mapper接口添加 @Mapper 注解,才能被识别。

@Mapper
public interface UserMapper {
}

第二种设置MapperScan,注解扫描的包(推荐)

@MapperScan("dao所在的包"),自动搜索包中的接口,产生dao的代理对象

package com.lxy;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.lxy.dao")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

测试
引入测试构建:(之前已经引入)

SpirngBoot<读完包你更上一层楼>测试代码:

package com.lxy.dao;

import com.lxy.domain.User;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDaoTest {
    @Autowired
    private UserDao userDao;
    @Test
    public void testFindAll() {
        List<User> list = userDao.findAll();
        System.out.println(list);
    }
}

测试结果:

SpirngBoot<读完包你更上一层楼>

这里可以看到mybatis是不支持下划线转驼峰的写法的 

7.2 通用mapper

7.2.1 概念

使用Mybatis时,最大的问题是,要写大量的重复SQL语句在xml文件中,除了特殊的业务逻辑SQL语句之外,还有大量结构类似的增删改查SQL。而且,当数据库表结构改动时,对应的所有SQL以及实体类都需要更改。这大量增加了程序员的负担。避免重复书写CRUD映射的框架有两个

通用mybatis(tk mybatis)
mybatis plus,通能更加强大,后面实战项目中讲解 

7.2.2 案例测试 

(1)通用Mapper的作者也为自己的插件编写了启动器,我们直接引入即可: 

因为tk mybatis中包含mybatis,所以我们可以把之前的mybatis删除

SpirngBoot<读完包你更上一层楼>

 (2)实体类代码:

tk mybatis 实体类使用的注解是jpa注解

在这里主键必须采用自增策略,并且要标注好是id主键

package com.lxy.domain;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Table(name = "tb_user")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 用户名
    //自动转换下换线到驼峰命名user_name -> userName
    private String userName;
    // 密码
    private String password;
    // 姓名
    private String name;
    // 年龄
    private Integer age;
    // 性别,1男性,2女性
    private Integer sex;
    // 出生日期
    private Date birthday;
    // 创建时间
    private Date created;
    // 更新时间
    private Date updated;
    // 备注
    private String note;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", name='" + name + '\'' +
                ", updated=" + updated +
                ", note='" + note + '\'' +
                '}';
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

注意事项:
1. 默认表名=类名,字段名=属性名
2. 表名可以使用 @Table(name = "tableName") 进行指定
3. @Column(name = "fieldName") 指定
4. 使用 @Transient 注解表示跟字段不进行映射 

5. 主键和主键策略必须进行指定

不需要做任何配置就可以使用了。 

(3)定义mapper接口,不需要编写映射文件xxx.xml

由于在Application.java中使用了接口扫描的注解,这里的Mapper已经可以自动注入了,不需要再引入@Repository;

接口需要继承Mapper,注意引入tk.mybatis.mapper;

一旦继承了Mapper,继承的Mapper就拥有了Mapper所有的通用方法;

package com.lxy.dao;

import com.lxy.domain.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {

}

(4)  mapper接口扫描

注意要把Application.java中,MapperScan类改成tk-mybatis构件的类

SpirngBoot<读完包你更上一层楼>

(5)测试

package com.lxy.dao;

import com.lxy.domain.User;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import tk.mybatis.mapper.entity.Example;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest extends TestCase {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testFindAll() {
        userMapper.selectAll().forEach(user -> {
            System.out.println(user);
        });
    }

    @Test
    public void testByExample () {
        Example example = new Example(User.class);
        example.createCriteria().andLike("name", "%a%");
        userMapper.selectByExample(example).forEach(user -> {
            System.out.println(user);
        });
    }

}

 (6)自定义映射方法

创建一个UserMapper.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.lxy.dao.UserMapper">
    <select id="findByUser" resultType="user">
        SELECT
        *
        FROM
        tb_user
        <where>
            <if test="name != null">
                name like '%${name}%'
            </if>
            <if test="note != null">
                and note like '%${note}%'
            </if>
        </where>
    </select>
</mapper>

在UserMapper接口中添加方法:

package com.lxy.dao;

import com.lxy.domain.User;
import tk.mybatis.mapper.common.Mapper;

import java.util.List;

public interface UserMapper extends Mapper<User> {

    public List<User> findByUser(User user);
}

测试:

    @Test
    public void testFindByUser() {
        User user = new User();
        user.setName("a");
        user.setNote("c");
        userMapper.findByUser(user).forEach(user1 -> {
            System.out.println(user1);
        });

    }

7.2.3 tk.mybatis中Mapper的内置方法 

Select 方法: List<T> select(T record); 说明:根据实体中的属性值进行查询,查询条件使用等号


方法: T selectByPrimaryKey(Object key); 说明:根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号


方法: List<T> selectAll(); 说明:查询全部结果,select(null)方法能达到同样的效果


方法: T selectOne(T record); 说明:根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号


方法: int selectCount(T record); 说明:根据实体中的属性查询总数,查询条件使用等号

Insert 方法: int insert(T record); 说明:保存一个实体,null的属性也会保存,不会使用数据库默认值
方法: int insertSelective(T record); 说明:保存一个实体,null的属性不会保存,会使用数据库默认值 

Update 方法: int updateByPrimaryKey(T record); 说明:根据主键更新实体全部字段,null值会被更新
方法: int updateByPrimaryKeySelective(T record); 说明:根据主键更新属性不为null的值 

Delete 方法: int delete(T record); 说明:根据实体属性作为条件进行删除,查询条件使用等号
方法: int deleteByPrimaryKey(Object key); 说明:根据主键字段进行删除,方法参数必须包含完整的主键属性 

Example方法 : List<T> selectByExample(Object example); 说明:根据Example条件进行查询 重点:这个查询支持通过 Example 类指定查询列,通过 selectProperties 方法指定查询列
方法: int selectCountByExample(Object example); 说明:根据Example条件进行查询总数


方法: int updateByExample(@Param("record") T record, @Param("example") Object example); 说明:根据Example条件更新实体 record 包含的全部属性,null值会被更新


方法: int updateByExampleSelective(@Param("record") T record, @Param("example") Object example); 说明:根据Example条件更新实体 record 包含的不是null的属性值


方法: int deleteByExample(Object example); 说明:根据Example条件删除数据 

 八、thymeleaf

 概念:

Thymeleaf 是一个跟 FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下特点:

(1)动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,无网络显示静态内容,有网络用后台得到数据替换静态内容


(2)与SpringBoot完美整合,springboot默认整合thymeleaf

8.1 入门案例

8.1.1 编写接口

编写UserService,调用UserMapper的查询所有方法

package com.lxy.service;

import com.lxy.dao.UserMapper;
import com.lxy.domain.User;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public class UserService {

    @Autowired
    private UserMapper userMapper;

    public List<User> findAll(){
        return userMapper.selectAll();
    }
}

8.1.2 编写一个controller,返回一些用户数据,放入模型中,等会在页面渲染 

package com.lxy.controller;

import com.lxy.domain.User;
import com.lxy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;


import java.util.List;

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public String all(Model model){
        List<User> list = userService.findAll();
        model.addAttribute("users",list);
        // 返回模板名称(就是classpath:/templates/目录下的html文件名)
        return "users";
    }
}

8.1.3 引入启动器

直接引入启动器:

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

SpringBoot会自动为Thymeleaf注册一个视图解析器:

SpirngBoot<读完包你更上一层楼>

 与解析JSP的InternalViewResolver类似,Thymeleaf也会根据前缀和后缀来确定模板文件的位置:

SpirngBoot<读完包你更上一层楼>

默认前缀: classpath:/templates/
默认后缀: .html

所以如果我们返回视图: users ,会指向到 classpath:/templates/users.html
一般我们无需进行修改,默认即可。 

8.1.4 静态页面

根据上面的文档介绍,模板默认放在classpath下的templates文件夹,我们新建一个html文件放入其中:

SpirngBoot<读完包你更上一层楼>

编写html模板,渲染模型中的数据:
注意,把html 的名称空间,改成: xmlns:th="http://www.thymeleaf.org" 会有语法提示

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
  <style type="text/css">
    table {border-collapse: collapse; font-size: 14px; width: 80%; margin: auto}
    table, th, td {border: 1px solid darkslategray;padding: 10px}
  </style>
</head>
<body>
<div style="text-align: center">
  <span style="color: darkslategray; font-size: 30px">欢迎光临</span>
  <hr/>
  <table class="list">
    <tr>
      <th>序号</th>
      <th>id</th>
      <th>姓名</th>
      <th>用户名</th>
      <th>年龄</th>
      <th>性别</th>
      <th>生日</th>
      <th>备注</th>
      <th>操作</th>
    </tr>
    <tr th:each="user,status:${users}" th:object="${user}">
      <td th:text="${user.id}">1</td>
      <td th:text="*{name}">张三</td>
      <td th:text="*{userName}">zhangsan</td>
      <td th:text="${user.age}">20</td>
      <td th:text="${user.sex}==1? '男': '女'">男</td>
      <td th:text="${#dates.format(user.birthday,'yyyy-MM-dd')}">1980-02-30</td>
      <td th:text="${user.note}">1</td>
      <td>
        <a href="#">删除</a>
        <a href="#">修改</a>
        <a href="#">审核</a>
      </td>
    </tr>
  </table>
</div>
</body>
</html>

8.1.5 测试

接下来,我们打开页面测试一下:

SpirngBoot<读完包你更上一层楼>

8.1.6 模板缓存

Thymeleaf会在第一次对模板解析之后进行缓存,极大的提高了并发处理能力。但是这给我们开发带来了不便,修改页面后并不会立刻看到效果,我们开发阶段可以关掉缓存使用:

# 开发阶段关闭thymeleaf的模板缓存
spring.thymeleaf.cache=false

注意:
在Idea中,我们需要在修改页面后按快捷键: Ctrl + Shift + F9 对项目进行rebuild才可以。
我们可以修改页面,测试一下。
 

8.2 thymeleaf详解

8.2.1 表达式

它们分为三类
1. 变量表达式
2. 选择或星号表达式
3. URL表达式

(1) 变量表达式

变量表达式即OGNL表达式或Spring EL表达式(在Spring中用来获取model attribute的数据)。如下所示:

${session.user.name}

它们将以HTML标签的一个属性来表示:

<h5>表达式</h5>
<span>${text}</span>
<span th:text="${text}">你好 thymleaf</span>

(2) 选择(星号)表达式

选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如下: *{customer.name}

被指定的object由th:object属性定义:users.html

<tr th:each="user : ${users}" th:object="${user}">
    <td th:text="${user.id}">1</td>
    <td th:text="*{name}">张三</td>
    <td th:text="*{userName}">zhangsan</td>
    ....

(3) URL表达式

URL表达式指的是把一个有用的上下文或回话信息添加到URL,这个过程经常被叫做URL重写。 @{/order/list}  URL还可以设置参数: @{/order/details(id=${orderId}, name=*{name})} 相对路径:@{../documents/report}

让我们看这些表达式:

<form th:action="@{/createOrder}">
<a href="main.html" th:href="@{/main}">

url表达式:

<a th:href="@{/delete(id=${user.id}, userName=*{userName})}">删除</a>

文本替换:

<a th:href="|/update/${user.id}|">修改</a>

字符串拼接

<a th:href="'/approve/' + ${user.id}">审核</a>

8.2.2 表达式常见用法

(1) 字面(Literals)

文本文字(Text literals): 'one text', 'Another one!',…
数字文本(Number literals): 0, 34, 3.0, 12.3,…
布尔文本(Boolean literals): true, false
空(Null literal): null

文字标记(Literal tokens): one, sometext, main,…

(2) 文本操作(Text operations) 

字符串连接(String concatenation): +
文本替换(Literal substitutions): |The name is ${name}|

(3) 算术运算(Arithmetic operations)

二元运算符(Binary operators): +, -, *, /, %
减号(单目运算符)Minus sign (unary operator): - 

(4) 布尔操作(Boolean operations)

二元运算符(Binary operators): and, or
布尔否定(一元运算符)Boolean negation (unary operator): !, not 

(5) 比较和等价(Comparisons and equality)

比较(Comparators): >, <, >=, <= (gt, lt, ge, le)
等值运算符(Equality operators): ==, != (eq, ne) 

(6) 条件运算符(Conditional operators)

If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue) 

8.2.3 常用th标签

 SpirngBoot<读完包你更上一层楼>

SpirngBoot<读完包你更上一层楼>

 还有非常多的标签,这里只列出最常用的几个

8.2.4 基本用法

(1)  赋值、字符串拼接

字符串拼接还有另外一种简洁的写法

<a th:href="|/update/${user.id}|">修改</a>
<a th:href="'/approve/' + ${user.id}">审核</a>

(2)  条件判断 If/Unless

Thymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中, <a> 标签只有在 th:if 中条件成立时才显示:

<h5>if指令</h5>
<a th:if="${users.size() > 0}">查询结果存在</a><br>
<a th:if="${users.size() <= 0}">查询结果不存在</a><br>
<a th:unless="${session.user != null}" href="#">登录</a><br>

th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容。
也可以使用 (if) ? (then) : (else) 这种语法来判断显示的内容

(3)  for 循环

    <tr th:each="user,status:${users}" th:object="${user}" th:bgcolor="${status.even} ? 'gray'">
      <th th:text="${status.count}">1</th>
      <td th:text="${user.id}">1</td>
      <td th:text="*{name}">张三</td>
      <td th:text="*{userName}">zhangsan</td>
      <td th:text="${user.age}">20</td>
      <td th:text="${user.sex}==1? '男': '女'">男</td>
      <td th:text="${#dates.format(user.birthday,'yyyy-MM-dd')}">1980-02-30</td>
      <td th:text="${user.note}">1</td>
      <td>
        <a th:href="@{/delete(id=${user.id},userName=*{userName})}">删除</a>
        <a th:href="|/update/${user.id}|">修改</a>
        <a th:href="'approve' + ${user.id}">审核</a>
      </td>
    </tr>

status称作状态变量,属性有:

index:当前迭代对象的index(从0开始计算)
count: 当前迭代对象的index(从1开始计算)
size:被迭代对象的大小
current:当前迭代变量
even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
first:布尔值,当前循环是否是第一个
last:布尔值,当前循环是否是最后一个

(4)  内联文本

使用的是后台传过来的Model中的数据 

内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”激活,th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。 

在thymeleaf指令中显示:

<h6 th:text="${text}">静态内容</h6>

 使用内联文本显示model attribute:

<h5>内联文本</h5>
<div>
    <h6 th:inline="text">[[${text}]]</h6>
    <h6 th:inline="none">[[${text}]]</h6>
    <h6>[[${text}]]</h6>
</div>

原则能用指令就用th指令 

(5)  内联js

 内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”激活,th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。

<h5>内联js</h5>
<script th:inline="javascript">
    /*<![CDATA[*/
    var text = '[[${text}]]';
    alert(text);
    /*]]>*/
</script>

(6)  内嵌变量

为了模板更加易用,Thymeleaf还提供了一系列Utility对象(内置于Context中),可以通过#直接访问:

dates : java.util.Date**的功能方法类。
calendars : 类似#dates,面向java.util.Calendar
numbers : 格式化数字的功能方法类
strings : 字符串对象的功能类,contains,startWiths,prepending/appending等等。
objects: 对objects的功能类操作。
bools: 对布尔值求值的功能方法。
arrays:对数组的功能类方法。
lists: 对lists功能类方法
sets
maps 

下面用一段代码来举例一些常用的方法:

1. dates

<h5>内置变量</h5>
<h6 th:text="${#dates.createNow()}">获取当前日期</h6>

 2.strings

<h5>内置变量</h5>
<h6 th:text="${#dates.createNow()}">获取当前日期</h6>
<h6 th:text="${#strings.substring(text, 6, 9)}">截取字符串</h6>
<h6 th:text="${#strings.length(text)}">获得长度</h6>
<h6 th:text="${#strings.randomAlphanumeric(6)}">随机字符串</h6>
<h6 th:text="${#strings.equals(text, 'hello text....')}"></h6>

8.3 使用thymeleaf布局

使用thymeleaf布局非常的方便
在/resources/templates/目录下创建footer.html,内容如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy(title)">
    &copy; 2020 lxy版权所有<br>
    <span th:text="${title}">title footer</span>
</footer>
</body>
</html>

在页面任何地方引入:

<h5>thymeleaf布局</h5>
<div th:insert="footer :: copy('开课吧1')"></div>
<div th:replace="footer :: copy('开课吧2')"></div>
<div th:include="footer :: copy('开课吧3')"></div>

th:insert :保留自己的主标签,保留th:fragment的主标签。
th:replace :不要自己的主标签,保留th:fragment的主标签。
th:include :保留自己的主标签,不要th:fragment的主标签。(官方3.0后不推荐) 

返回的HTML如下:

<h5>thymeleaf布局</h5>
<div><footer>
    &copy; 2020 lxy版权所有<br>
    <span>lxy</span>
</footer></div>
<footer>
    &copy; 2020 lxy版权所有<br>
    <span>lxy</span>
</footer>
<div>
    &copy; 2020 lxy版权所有<br>
    <span>lxy</span>
</div>

九、Mybatis Plus 

9.1 简介

SpirngBoot<读完包你更上一层楼>

Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,避免了我们重复CRUD语句。 

9.2 快速入门

9.2.1 创建工程,引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lxy</groupId>
    <artifactId>mybatis-plus-demo-quickstart-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <mybatisplus.version>3.3.2</mybatisplus.version>
        <skipTests>true</skipTests>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <!--简化开发 实体类不用写get set方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

9.2.2 配置文件application.yml

yml配置简介

在Springboot中,推荐使用properties或者YAML文件来完成配置,但是对于较复杂的数据结构来说,YAML又远远优于properties。我们快速介绍YAML的常见语法格式。 

先来看一个Springboot中的properties文件和对应YAML文件的对比:

#properties(示例来源于Springboot User guide):
environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

可以明显的看到,在处理层级关系的时候,properties需要使用大量的路径来描述层级(或者属性),比如environments.dev.url和environments.dev.name。其次,对于较为复杂的结构,比如数组(my.servers),写起来更为复杂。而对应的YAML格式文件就简单很多: 

#YAML格式
environments:
    dev:
        url: http://dev.bar.com
        name: Developer Setup
    prod:
        url: http://foo.bar.com
        name: My Cool App
my:
    servers:
        - dev.bar.com
        - foo.bar.com

application.yml :

# DataSource Config
spring:
  datasource:
    driver-class-name: org.h2.Driver
    schema: classpath:db/schema-h2.sql
    data: classpath:db/data-h2.sql
    url: jdbc:h2:mem:test
    username: root
    password: test
# Logger Config
logging:
  level:
    com.lxs.quickstart: debug

数据库脚本文件/db/data-h2.sql和/db/schema-h2.sql(拷贝)
h2数据库是一个基于内存的数据库,在jvm启动时,自动执行脚本加载相应的数据
springboot 中使用h2数据库直接按照上面配置,配置schema表结构脚本和data数据脚本即可
注意这里用户名密码可以省略不写,或者随意设定 

9.2.3 实体类

package com.lxy.quickstart.entity;

import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

9.2.4 dao

package com.lxy.quickstart.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lxy.quickstart.entity.User;

public interface UserMapper extends BaseMapper<User> {
}

9.2.5 启动类

package com.lxy.quickstart;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

9.2.6 测试

package com.lxy.quickstart.mapper;

import com.lxy.quickstart.entity.User;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest extends TestCase {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        System.out.println(("----- selectAll method test ------"));
        List<User> userList = userMapper.selectList(null);
        //Assert.assertEquals(6, userList.size());
        userList.forEach(System.out::println);
    }
}

测试结果:

SpirngBoot<读完包你更上一层楼>9.3 常用注解

MyBatisPlus提供了一些注解供我们在实体类和表信息出现不对应的时候使用。通过使用注解完成逻辑上匹配。

SpirngBoot<读完包你更上一层楼>9.3.1 mybatis plus注解策略配置

如果mysql自增主键注解策略设置如下

@TableId(type = IdType.AUTO)
private Long id;

默认主键策略:

/**
* 采用雪花算法生成全局唯一主键
**/
ASSIGN_ID(3), 

9.3.2 排除实体类中非表字段

使用 @TableField(exist = false) 注解 

9.4 内置增删改查

9.4.1 增加

    @Test
    public void testInsert() {
        User user = new User();
        user.setName("aaa");
        user.setEmail("lxy@163.com");
        user.setAge(3);
        Assert.assertTrue(userMapper.insert(user) > 0);
        userMapper.selectList(null).forEach(System.out :: println);
    }

SpirngBoot<读完包你更上一层楼>

9.4.2 删除

    @Test
    public void testDelete() {
//        //主键删除
//        userMapper.deleteById(3l);
//        userMapper.selectList(null).forEach(System.out :: println);

//        //批量删除:1
//        userMapper.delete(new QueryWrapper<User>().like("name", "J"));
//        userMapper.selectList(null).forEach(System.out :: println);

//        //批量删除:2
//        userMapper.delete(Wrappers.<User>query().like("name", "J"));
//        userMapper.selectList(null).forEach(System.out :: println);
        //批量删除:3
        userMapper.delete(Wrappers.<User>query().lambda().like(User::getName, "J"));
        userMapper.selectList(null).forEach(System.out :: println);
        
    }

9.4.3 更新

    @Test
    public void testUpdate() {
//        //基本修改
//        userMapper.updateById(new User().setId(1l).setName("慧科"));
//        userMapper.selectList(null).forEach(System.out :: println);
        // //批量修改:1
        // mapper.update(null, Wrappers.<User>update().set("email", "huike@163.com").like("name","J"));
        // mapper.selectList(null).forEach(System.out :: println);
        //批量修改:2
        userMapper.update(new User().setEmail("huike@163.com"), Wrappers.<User>update().like("name", "J"));
        userMapper.selectList(null).forEach(System.out :: println);
    }

在更新时,使用的方法必须先在对应的实体类加上

@Accessors(chain = true)这个注解,此注解代表使用get set方法之后返回的是实体类的对象,而不是void,所以可以使用如上的更新方法便于操作。

 9.4.4 查询

    @Test
    public void testSelectTwo() {
        //基本查询
        // System.out.println(mapper.selectOne(Wrappers.<User>query().eq("name", "Tom")));
        //投影查询
        userMapper.selectList(new QueryWrapper<User>().select("id", "name")).forEach(user -> {
            System.out.println(user);
        });
    }

映射查询只能查找到两列数据,其余列全部为null。 

9.5 分页

9.5.1 内置分页 

(1)添加分页插件

package com.lxy.quickstart.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;

public class MybatisPlusConfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        // 开启 count 的 join 优化,只针对 left join !!!
        return new PaginationInterceptor().setCountSqlParser(new JsqlParserCountOptimize(true));
    }
}

(2) 优化left join count场景

在一对一join操作时,也存在优化可能,看下面sql :

select u.id,ua.account from user u left join user_account ua on u.id=ua.uid
#本来生成的count语句像这样
select count(1) from (select u.id,ua.account from user u left join user_account ua on
u.id=ua.uid)

 这时候分页查count时,其实可以去掉left join直查user,因为user与user_account是1对1关系,如下:

查count:
select count(1) from user u
查记录:
select u.id,ua.account from user u left join user_account ua on u.id=ua.uid limit 0,50

(3) 测试

    @Test
    public void testPage() {
        System.out.println("------ baseMapper 自带分页 ------");
        Page<User> page = new Page<>(1, 5);
        IPage<User> pageResult = userMapper.selectPage(page, new QueryWrapper<User>().eq("age", 331));
        System.out.println("总条数 ------> " + pageResult.getTotal());
        System.out.println("当前页数 ------> " + pageResult.getCurrent());
        System.out.println("当前每页显示数 ------> " + pageResult.getSize());
        pageResult.getRecords().forEach(System.out :: println);
    }

SpirngBoot<读完包你更上一层楼>

9.5.2 自定义xml分页

application.yml配置文件

# 配置mybatis plus
mybatis-plus:
  type-aliases-package: com.lxy.quickstart.entity #别名搜索
  mapper-locations: classpath:/mappers/*.xml #加载映射文件

UserMapper接口:

package com.lxy.quickstart.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.lxy.quickstart.entity.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper extends BaseMapper<User> {
    /**
     * 如果映射的接口方法有2个参数需要@Param定义参数名,定义参数名后,映射文件中使用p.属性 c.属性,具体访
     问
     *
     * @param page
     * @param condition
     * @return
     */
    public IPage<User> selectUserByPage(@Param("p") IPage<User> page, @Param("c") User condition);

}

UserMapper.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.lxy.quickstart.mapper.UserMapper">
    <sql id="selectSql">
        SELECT
            *
        FROM
            user
    </sql>
    <select id="selectUserByPage" resultType="user">
        <include refid="selectSql"></include>
        <where>
            <if test="c.age !=null">
                age = #{c.age}
            </if>
            <if test="c.email !=null">
                and email like '%${c.email}%'
            </if>
        </where>
    </select>
</mapper>

测试:

    @Test
    public void testXmlPage() {
        System.out.println("------ baseMapper 自定义xml分页 ------");
        Page<User> page = new Page<>(1, 5);
        User user = new User();
        user.setAge(331);
        user.setEmail("test");
        IPage<User> pr = userMapper.selectUserByPage(page, user);
        System.out.println("总条数 ------> " + pr.getTotal());
        System.out.println("当前页数 ------> " + pr.getCurrent());
        System.out.println("当前每页显示数 ------> " + pr.getSize());
        pr.getRecords().forEach(System.out :: println);
    }

SpirngBoot<读完包你更上一层楼>

9.5.3 pageHelper分页 

引入pageHelper依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
</dependency>

mybatis plus 整合pageHelper的配置类 :

package com.lxy.quickstart.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import com.github.pagehelper.PageInterceptor;
import org.springframework.context.annotation.Bean;

public class MybatisPlusConfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        // 开启 count 的 join 优化,只针对 left join !!!
        return new PaginationInterceptor().setCountSqlParser(new JsqlParserCountOptimize(true));
    }

    /**
     * 两个分页插件都配置,不会冲突
     * pagehelper的分页插件
     */
    @Bean
    public PageInterceptor pageInterceptor() {
        return new PageInterceptor();
    }
}

 映射文件:

<?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.lxy.quickstart.mapper.UserMapper">
    <sql id="selectSql">
        SELECT
            *
        FROM
            user
    </sql>
    <select id="selectUserByPage" resultType="user">
        <include refid="selectSql"></include>
        <where>
            <if test="c.age !=null">
                age = #{c.age}
            </if>
            <if test="c.email !=null">
                and email like '%${c.email}%'
            </if>
        </where>
    </select>

    <select id="selectUserByPage2" resultType="user">
        <include refid="selectSql"></include>
        <where>
            <if test="age !=null">
                age = #{age}
            </if>
            <if test="email !=null">
                and email like '%${email}%'
            </if>
        </where>
    </select>
</mapper>

UserMapper添加代码:

package com.lxy.quickstart.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.lxy.quickstart.entity.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface UserMapper extends BaseMapper<User> {
    /**
     * 如果映射的接口方法有2个参数需要@Param定义参数名,定义参数名后,映射文件中使用p.属性 c.属性,具体访
     问
     *
     * @param page
     * @param condition
     * @return
     */
    public IPage<User> selectUserByPage(@Param("p") IPage<User> page, @Param("c") User condition);

    public List<User> selectUserByPage2(User condition);
}

 测试类:

     @Test
    public void testPageHelper(){
        //条件对象
        User u = new User();
        u.setAge(331);
        u.setEmail("test");

        PageInfo<User> page = PageHelper.startPage(2,10).doSelectPageInfo(() ->{
            //自定义xml映射文件
            userMapper.selectUserByPage2(u);
//            //使用mp内置方法
//            userMapper.selectList(Wrappers.<User>query());
        });

        page.getList().forEach(System.out :: println);

        System.out.println("总行数=" + page.getTotal());
        System.out.println("当前页=" + page.getPageNum());
        System.out.println("每页行数=" + page.getPageSize());
        System.out.println("总页数=" + page.getPages());
        System.out.println("起始行数=" + page.getStartRow());
        System.out.println("是第一页=" + page.isIsFirstPage());
        System.out.println("是最后页=" + page.isIsLastPage());
        System.out.println("还有下一页=" + page.isHasNextPage());
        System.out.println("还有上一页=" + page.isHasPreviousPage());
        System.out.println("页码列表" + Arrays.toString(page.getNavigatepageNums()));

    }

SpirngBoot<读完包你更上一层楼>

 至此SpringBoot的知识点及其源码就全部结束了,如果您看到了这里,我相信一定对您有很大的帮助,如果方便的话还请给博主三连,谢谢观看!!!