12.微服务与分布式—1.SpringBoot

本文详细介绍了SpringBoot的核心概念、为什么学习SpringBoot以及它的特点。通过实例演示了从创建工程到配置数据库、整合SpringMVC、Mybatis等步骤,包括自动配置原理、属性注入以及Thymeleaf模板引擎的使用。最后,还涵盖了Mybatis Plus的快速入门和分页配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. springboot概念

在这一部分,我们主要了解以下3个问题:

  • 什么是SpringBoot
  • 为什么要学习SpringBoot
  • SpringBoot的特点

什么是SpringBoot

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

为什么要学习SpringBoot

spring的缺点:

  • 复杂的配置,
    • 项目各种配置是开发时的损耗, 写配置挤占了写应用程序逻辑的时间。
  • 混乱的依赖管理。
    • 项目的依赖管理非常的繁琐。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本 和其他库不会有冲突,这是一个棘手的问题。并且,一旦选错了依赖的版本,随之而来的就是各种的不兼容 的bug。

spring boot 可以解决上面2个问题

SpringBoot的特点

Spring Boot 特点:

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

更多细节,大家可以到官网查看。

总结

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

2. 入门案例

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

创建工程

我们先新建一个空的工程:

工程名为project:

设置jdk版本为1.8:

新建一个module:

使用maven来构建:

然后填写项目坐标:

目录结构:

项目结构:

添加依赖

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

 

添加父工程坐标

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

添加web启动器

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

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

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

 

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

管理jdk版本

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

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

完整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.sh</groupId>
    <artifactId>spring-boot-demo</artifactId>
    <packaging>pom</packaging>
    <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>

启动类

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

然后编写main函数:

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

编写controller

接下来,我们就可以像以前那样开发SpringMVC的项目了!

我们编写一个controller:

代码:

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

启动测试

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

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

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

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

测试成功了!

3. 全注解配置和属性注入

在入门案例中,我们没有任何的配置,就可以实现一个SpringMVC的项目了,快速、高效!

但是有同学会有疑问,如果没有任何的xml,那么我们如果要配置一个Bean该怎么办?比如我们要配置一个数据库连接池,以前会这么玩:

现在该怎么做呢?

回顾历史

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

  • Spring1.0时代

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

  • Spring2.0时代

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

  • Spring3.0及以后

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

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.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/lxs
jdbc.username=root
jdbc.password=123456

然后编写代码:

@Configuration
@PropertySource("classpath:application.properties")
public class JdbcConfig {
   //spring全注解方式
    @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 中测试:

@RestController
public class HelloController {
    @Autowired
    private DataSource dataSource;
    @GetMapping("hello")
    public String hello() {
        return "hello, spring boot!" + dataSource;
    }
}

然后Debug运行并查看:

属性注入成功了!

SpringBoot的属性注入

在上面的案例中,我们实验了java配置方式。不过属性注入使用的是@Value注解。这种方式虽然可行,但是不够强大,因为它只能注入基本类型值。

在SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入。

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

@ConfigurationProperties(prefix = "jdbc")
public class JdbcProperties {
    private String url;
    private String driverClassName;
    private String username;
    private String password;
    // ... 略
    // getters 和 setters
}
  • 在类上通过@ConfigurationProperties注解声明当前类为属性读取类
  • prefix="jdbc" 读取属性文件中,前缀为jdbc的值。
  • 在类上定义各个属性,名称必须与属性文件中 jdbc. 后面部分一致
  • 需要注意的是,这里我们并没有指定属性文件的地址,所以我们需要把jdbc.properties名称改为application.properties,这是SpringBoot默认读取的属性文件名:

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

@Configuration
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {
    //springboot自动注入方式
    @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 这个类的对象
  • 然后你可以通过以下方式注入JdbcProperties:
    • @Autowired注入
      @Autowired
      private JdbcProperties prop;

       

    • 构造函数注入
      private JdbcProperties prop;
      public JdbcConfig(Jdbcproperties prop){
          this.prop = prop;
      }

       

    • 声明有@Bean的方法参数注入
      @Bean
      public Datasource dataSource(JdbcProperties prop){
          // ...
      }

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

3)测试结果:

大家会觉得这种方式似乎更麻烦了,事实上这种方式有更强大的功能,也是SpringBoot推荐的注入方式。两者对比关系:

优势:

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

更优雅的注入

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

@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方法!

我们将jdbc的url改成:/lxs,再次测试:

4. 自动配置原理

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

这些都是从springboot启动器开始的

我们重点关注@SpringBootApplication注解

@SpringBootApplication

点击进入,查看源码:

这里重点的注解有3个:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan

@SpringBootConfiguration

我们继续点击查看源码:

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

@ComponentScan

我们跟进源码:

并没有看到什么特殊的地方。我们查看注释:

大概的意思:

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

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

@EnableAutoConfiguration

关于这个注解,官网上有一段说明:

The second class-level annotation is @EnableAutoConfiguration . This annotation tells Spring Boot to “guess” how you want to configure Spring, based on the jar dependencies that you have added. Since spring-boot-starter-web added Tomcat and Spring MVC, the auto-configuration assumes that you are developing a web application and sets up Spring accordingly.

简单翻译以下:

第二级的注解 @EnableAutoConfiguration ,告诉SpringBoot基于你所添加的依赖,去“猜测”你想要如何配置Spring。比如我们引入了 spring-boot-starter-web ,而这个启动器中帮我们添加了 tomcat 、 SpringMVC 的依赖。此时自动配置就知道你是要开发一个web应用,所以就帮你完成了web及SpringMVC的默认配置了!

总结,SpringBoot内部对大量的第三方库进行了默认配置,我们引入对应库所需的依赖,那么默认配置就会生效。

默认配置原理

@EnableAutoConfiguration会开启SpringBoot的自动配置,并且根据你引入的依赖来生效对应的默认配置,

springboot如何做到的?

其实在我们的项目中,已经引入了一个依赖:spring-boot-autoconfigure,其中定义了大量自动配置类:

还有:

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

  • redis
  • jms
  • amqp
  • jdbc
  • jackson
  • mongodb
  • jpa
  • solr
  • elasticsearch

... 等等

我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类:

打开WebMvcAutoConfiguration:

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

  • @Configuration :声明这个类是一个配置类
  • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    • 这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依赖自然会有,后两个需要引入SpringMVC才会有。这里就是判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效!
  • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    • 这个条件与上面不同,OnMissingBean,是说环境中没有指定的Bean这个才生效。其实这就是自定义配置的入口,也就是说,如果我们自己配置了一个WebMVCConfigurationSupport的类,那么这个默认配置就会失效!

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

视图解析器:

处理器适配器(HandlerAdapter):

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

总结

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

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

5.整合SpringMVC

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

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

修改端口

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

# 映射端口

server.port=80

重启服务后测试:

访问静态资源

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

默认的静态资源路径为:

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

只要静态资源放在这些目录中任何一个,SpringMVC都会帮我们处理。

我们习惯会把静态资源放在 classpath:/static/ 目录下。我们创建目录,并且添加一些静态资源:

重启项目后测试:

添加拦截器

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

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

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配置:

@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或者更多的目录

结构如下:

接下来运行并查看日志:

你会发现日志中什么都没有,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置。

SpringBoot通过 logging.level.*=debug 来配置日志级别,*填写包名

# 设置com.lxs包的日志级别为debug

logging.level.com.lxs=debug

再次运行查看:

2018-05-05 17:50:01.811 DEBUG 4548 --- [p-nio-80-exec-1] com.lxs.interceptor.LoginInterceptor
: preHandle method is now running!
2018-05-05 17:50:01.854 DEBUG 4548 --- [p-nio-80-exec-1] com.lxs.interceptor.LoginInterceptor
: postHandle method is now running!
2018-05-05 17:50:01.854 DEBUG 4548 --- [p-nio-80-exec-1] com.lxs.interceptor.LoginInterceptor
: afterCompletion method is now running!

6.整合jdbc

导入资料中的t_user.sql文件

引入依赖

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

        <!--测试依赖-->
        <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>5.1.46</version>
</dependency>

配置连接池

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

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

因此,我们只需要指定连接池参数即可:

# 连接四大参数
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=123
# 可省略,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

实体类

public class User implements Serializable {
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;
}

dao

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

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class JdbcDaoTest {
    @Autowired
    private JdbcDao jdbcDao;
    @Test
    public void findAll() {
        List<User> list = jdbcDao.findAll();
        for (User user : list) {
            System.out.println(user);
        }
    }
}

7.整合mybatis

mybatis

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.lxs.domain
# mapper.xml文件位置,如果没有映射文件,请注释掉
mybatis.mapper-locations=classpath:mappers/*.xml

实体类

直接使用jdbc用到的实体类

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;
//getter和setter方法
}

接口

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

映射文件

<?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.lxs.demo.dao.UserDao">
    <select id="findAll" resultType="user">
        select * from tb_user
    </select>
</mapper>

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

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

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

@Mapper
    public interface UserMapper {
}

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

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

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

测试

引入测试构建
 

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

测试代码

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

通用mapper

概念

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

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

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

<!-- 通用mapper -->
<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

实体类

tk mybatis 实体类使用的注解是jpa注解(jpa注解:https://blog.csdn.net/yiyelanxin/article/details/100107335

@Table(name = "tb_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 用户名
    private String userName;
    ....

注意事项:

  • 1. 默认表名=类名,字段名=属性名
  • 2. 表名可以使用 @Table(name = "tableName") 进行指定
  • 3. 字段名可用@Column(name = "fieldName") 指定
  • 4. 使用 @Transient 注解表示跟字段不进行映射
  • 5.主键映射不能省略,@GeneratedValue(strategy = GenerationType.IDENTITY)
  • 6.自动转换下换线到驼峰命名 例:user_name -> userName

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

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
    public List<User> findByUser(User user);
}

自定义映射文件

映射复杂方法 resources/mappers/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">
<!--命名空间:namespace="接口的完整路径"-->
<mapper namespace="com.lxs.demo.dao.UserMapper">
    <!--resultType/resultMap-->
    <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>

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

Select

方法说明
List<T> select(T record)根据实体中的属性值进行查询,查询条件使用等号
T selectOne(T record)根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
int selectCount(T record)根据实体中的属性查询总数,查询条件使用等号
List<T> selectAll()查询全部结果,select(null)方法能达到同样的效果
T selectByPrimaryKey(Object key)根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号

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条件删除数据

注意:要把MapperScan类改成tk-mybatis构件的类

import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@EnableConfigurationProperties
@MapperScan("com.lxs.demo.dao")
public class Application {

注意:必须使用tk mybatis的MapperScan

安装插件JBLSpringBootAppGen可自动生成启动类

启动测试

测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDaoTest {
    @Autowired
    private UserDao userDao;

    @Test
    public void testFindByUser() {
        User condition = new User();
        condition.setName("a");
        List<User> list = userMapper.findByUser(condition);
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testFindAll() {
        List<User> list = userDao.selectAll();
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testFindById() {
        User user = userDao.selectByPrimaryKey(4);
        System.out.println(user);
    }

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

    @Test
    public void testInsert() {
        User user = new User();
        user.setAge(18);
        user.setBirthday(new Date());
        user.setCreated(new Date());
        user.setName("周星驰");
        userDao.insert(user);
    }
}

8.Thymeleaf

概念

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

  • 动静结合:Thymeleaf 在有网络和无网络的环境下皆可运行,无网络显示静态内容,有网络用后台得到数据替换静态内容
  • 与SpringBoot完美整合,springboot默认整合thymeleaf

8.1 入门案例

编写接口

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

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    public List<User> queryAll() {
        return this.userDao.selectAll();
    }
}

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

据,放入模型中,等会在页面渲染

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

引入启动器

直接引入启动器:

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

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

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

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

所以如果我们返回视图: users ,会指向到 classpath:/templates/users.html

一般我们无需进行修改,默认即可。

静态页面

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

编写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>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 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>
            </table>
        </div>
    </body>
 
</html>

我们看到这里使用了以下语法

  • ${} :这个类似与el表达式,但其实是ognl的语法,比el表达式更加强大
  • th- 指令: th- 是利用了Html5中的自定义属性来实现的。如果不支持H5,可以用 data-th- 来代替
    • th:each :类似于 c:foreach 遍历集合,但是语法更加简洁
    • th:text :声明标签中的文本
      • 例如 <td th-text='${user.id}'>1</td> ,如果user.id有值,会覆盖默认的1
      • 如果没有值,则会显示td中默认的1。这正是thymeleaf能够动静结合的原因,模板解析失败不影响页面的显示效果,因为会显示默认值!

测试

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

模板缓存

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

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

注意:

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

我们可以修改页面,测试一下。

8.2 thymeleaf详解

表达式

它们分为三类

  • 1. 变量表达式
  • 2. 选择或星号表达式
  • 3. URL表达式

变量表达式

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

${session.user.name}

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

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

选择(星号)表达式

选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(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>

....

 

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>

表达式常见用法

字面(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,…

文本操作(Text operations)

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

算术运算(Arithmetic operations)

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

布尔操作(Boolean operations)

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

比较和等价(Comparisons and equality)

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

条件运算符(Conditional operators)

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

常用th标签

关键字功能介绍案例
th:id替换id<input th:id="'xxx' + ${collect.id}"/>
th:text文本替换<p th:text="${collect.description}">description</p>
th:utext支持html的文本替换<p th:utext="${htmlcontent}">content</p>
th:object替换对象<div th:object="${session.user}">
th:value属性赋值<input th:value = "${user.name}" />
th:with变量赋值运算<div th:with="isEvens = ${prodStat.count}%2 == 0"></div>
th:style设置样式<div th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''"></div>
th:onclick点击事件<td th:onclick = "'getCollect()'"></td>
th:each属性赋值<tr th:each = "user,userStat:${users}">
th:if判断条件<a th:if = "${userId == collect.userId}">
th:unless和th:if判断相反<a th:href="@{/login} th:unless=${session.user != null}">Login</a>
th:href链接地址<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>
th:switch多路选择配合th:case使用<div th:switch="${user.role}">
th:fragmentth:switch的一个分支<p th:case = "'admin'">User is an administrator</p>
th:includ布局标签,替换内容到引入的文件<head th:include="layout :: htmlhead" th:with="title='xx'"></head>
th:replace布局标签,替换整个标签到引入的文件<div th:replace="fragments/header :: title"></div>
th:selectdselected选择框选中th:selected="(${xxx.id} == ${configObj.dd})"
th:src图片类地址引入<img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" />
th:inline定义js脚本可以使用变量<script type="text/javascript" th:inline="javascript">
th:action表单提交的地址<form action="subscribe.html" th:action="@{/subscribe}">
th:remove删除某个属性<tr th:remove="all"> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。
th:attr设置标签属性,多个属性可以用逗号分隔比如 th:attr="src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少。

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

基本用法

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:class="${status.even} ?'grey'">
    <td th:text="${status.even}"></td>
    <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. 内联文本

内联文本:[[…]]内联文本的表示方式,使用时,必须先用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>
            <h6>${text}</h6>
        </div>

原则能用指令就用th指令

5. 内联js

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

        <h5>内联js</h5>
        <script th:inline="javascript">/*inline="none"关闭,默认开启*/
            /*<![CDATA[*/
            var text = '[[${text}]]';
            console.log(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

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

dates

<h5>内置变量</h5>
<h6 th:text="${#dates.createNow()}">获取当前日期</h6>
<h6 th:text="*{#dates.format(birthday,'yyyy-MM-dd')}>日期格式转换</h6>

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>

使用thymeleaf布局

使用thymeleaf布局非常的方便

在/resources/templates/目录下创建footer.html,内容如下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<footer th:fragment="copy(title)">
    &copy; 2020 开课吧版权所有<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如下:

9. Mybatis Plus

简介

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

快速入门

创建工程,引入依赖

<?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.com.sh</groupId>
    <artifactId>mybatis-plus-demo-quickstart</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>

        <!--h2数据库-->
        <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>

        <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>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

配置文件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.sh.quickstart: debug

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

数据库脚本文件/db/data-h2.sql和/db/schema-h2.sql(拷贝)

data-h2.sql:

DELETE FROM user2;

INSERT INTO user2 (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com');

schema-h2.sql:

DROP TABLE IF EXISTS user2;

CREATE TABLE user2
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);

h2数据库是一个基于内存的数据库,在jvm启动时,自动执行脚本加载相应的数据

springboot 中使用h2数据库直接按照上面配置,配置schema表结构脚本和data数据脚本即可

注意这里用户名密码可以省略不写,或者随意设定

启动类

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

实体类

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

dao

public interface UserMapper extends BaseMapper<User> {
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
    @Resource
    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);
    }
}

常用注解

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

注解名称说明
@TableName实体类的类名和数据库表名不一致
@TableId实体类的主键名称和表中主键名称不一致
@TableField实体类中的成员名称和表中字段名称不一致>

mybatis plus注解策略配置

  • mysql自增主键注解策略设置如下
@TableId(type = IdType.AUTO)
private Long id;
  • 默认主键策略
/**
* 采用雪花算法生成全局唯一主键
**/
ASSIGN_ID(3),
  • 排除实体类中非表字段
    • 使用 @TableField(exist = false) 注解
  • 主键策略参考源码IdType

内置增删改查

package com.sh.quickstart.mapper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.sh.quickstart.entity.User;
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.Arrays;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect(){
        List<User> list = userMapper.selectList(null);
        list.forEach(user -> {
            System.out.println(user);
        });

        //Assert.assertEquals(23,list.size());
    }

    @Test
    public void testInsert(){
        User user = new User();
        user.setName("开课吧");
        user.setEmail("com.sh@qq.com");
        user.setAge(3);
        Assert.assertTrue(userMapper.insert(user)>0);
        userMapper.selectList(null).forEach(System.out::println);
    }
    @Test
    public void testDelete(){
        //主键删除
        //userMapper.deleteById(2l);

        //批量删除:1
        //userMapper.delete(new QueryWrapper<User>().like("name","J"));

        //批量删除:2
        //userMapper.delete(Wrappers.<User>query().like("name","J"));

        //批量删除:3
        userMapper.delete(Wrappers.<User>query().lambda().like(User::getName,"J"));

        userMapper.selectList(null).forEach(System.out::println);

    }
    @Test
    public void testUpdate(){
        //基本修改
        //userMapper.updateById(new User().setId(1l).setName("惠科").setAge(18));

        //批量修改1
        //userMapper.update(null,Wrappers.<User>update().set("email","huike@163.com").like("name","J"));

        //批量修改2
        userMapper.update(new User().setEmail("huike@163.com"),Wrappers.<User>update().like("name","J"));

        userMapper.selectList(null).forEach(System.out::println);
    }

    @Test
    public void testSelect2(){
        //基本查询
        System.out.println(userMapper.selectOne(Wrappers.<User>query().eq("name","Tom")));

        //投影查询
        userMapper.selectList(new QueryWrapper<User>().select("id","name")).forEach(user -> {
            System.out.println(user);
        });

    }

}

分页

内置分页

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

}

优化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

测试

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

 

自定义xml分页

application.yml配置文件

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

UserMapper接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.sh.quickstart.entity.User;
import org.apache.ibatis.annotations.Param;
public interface UserMapper extends BaseMapper<User> {
    /**
     * 如果映射的接口方法有2个参数需要@Param定义参数名,定义参数名后,映射文件中使用p.属性 c.属性,具体访问
     * @param page
     * @param conditioin
     * @return
     */
    public IPage<User> selectUserByPage(@Param("p") IPage<User> page, @Param("c") User conditioin);

    public List<User> selectUserByPage2(User conditioin);


}

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.sh.quickstart.mapper.UserMapper">
    <sql id="selectSql">
        SELECT * FROM user2
    </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分页 ------");
        //参数1:第几页;参数2:每页行数
        Page<User> page = new Page<>(2, 5);
        //条件对象
        User user = new User();
        user.setAge(331);
        user.setEmail("test4");
        IPage<User> pageResult = userMapper.selectUserByPage(page,user);
        System.out.println("总条数 ------> " + pageResult.getTotal());
        System.out.println("总页数 ------> " + pageResult.getPages());
        System.out.println("当前页数 ------> " + pageResult.getCurrent());
        System.out.println("当前每页显示数 ------> " + pageResult.getSize());
        pageResult.getRecords().forEach(System.out :: println);
    }

pageHelper分页

引入pageHelper依赖

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

mybatis plus 整合pageHelper的配置类

@Configuration
public class MybatisPlusConfig {
    /**
     * mp分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        // 开启 count 的 join 优化,只针对 left join !!!
         return new PaginationInterceptor().setCountSqlParser(new JsqlParserCountOptimize(true));
    }
    /**
     * 两个分页插件都配置,不会冲突
     * pagehelper的分页插件
     */
    @Bean
    public PageInterceptor pageInterceptor() {
        return new PageInterceptor();
    }
}

映射文件

<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>

测试

    @Test
    public void testPageHelper() {
        System.out.println("------ PageHelper分页 ------");

        //条件对象
        User u = new User();
        u.setAge(331);
        u.setEmail("test4");

        PageInfo<User> page = PageHelper.startPage(1, 2).doSelectPageInfo(()->{
            userMapper.selectUserByPage2(u);
        });
        //参数:当前页,每页行数
        //PageHelper.startPage(1,2);
        //PageInfo<User> page = new PageInfo<>(userMapper.selectList(Wrappers.<User>query()));

        //PageInfo<User> page = new PageInfo<>(userMapper.selectUserByPage2(u));


        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()));
    }

记账管理项目:https://gitee.com/shujuanlanyan/account.git

旅游管理项目:https://gitee.com/shujuanlanyan/travel.git

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值