1、快速上手SpringBoot
1.1、parent
- 开发SpringBoot程序要继承spring-boot-starter-parent,所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的;
- spring-boot-starter-parent中定义了若干依赖管理,各版本间存在着诸多坐标版本不同;
- 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突;
- 继承parent的形式也可以采用引入依赖的形式实现效果。
1.2、starter
SpringBoot中常见项目名称,定义了当前项目使用的所有坐标依赖,以达到减少依赖配置的目的。
- 开发SpringBoot程序需要导入坐标时通常导入对应的starter;
- 每个不同的starter根据功能不同,通常包含多个依赖坐标;
- 使用starter可以实现快速配置的效果,达到简化配置的目的。
实际开发:
- 使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本的V;
- 如发生坐标错误,再指定Version(要小心版本冲突)。
1.3、引导类
package com.clp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Springboot0101QuickstartApplication {
public static void main(String[] args) {
//生成可配置的容器对象
ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args);
}
}
SpringBoot的引导类是Boot工程的执行入口,运行main()方法就可以启动项目;
SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean。
1.4、内嵌tomcat
内嵌Tomcat服务器是SpringBoot辅助功能之一;内嵌Tomcat工作原理是将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理;变更内嵌服务器思想是去除现有服务器,添加全新的服务器。
SpringBoot内嵌有三款服务器:
- tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件。
- jetty:更轻量级,负载性能远不及tomcat。
- undertow:undertow,负载性能勉强跑赢tomcat。
2、基础配置
2.1、属性配置
2.1.1、修改配置
SpringBoot提供了多种属性配置方式:
- application.properties;
在./resource/application.properties中: 用(什么技术就开什么配置) # 服务器的端口配置 server.port=80 # 修改banner(运行日志图标) #spring.main.banner-mode=off # 关闭运行日志图标 #spring.banner.image.location=logo.png # 设置日志相关(设置启动日志) logging.level.root=debug logging.level.com.clp=warn
- application.yml(常用);
application.yml: server: port: 80
- application.yaml;
application.yaml: server: port:80
不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留。加载优先级:application.properties -> application.yml -> application.yaml
2.2、YMAL
yaml:YAML(YAML,Ain't Markup Language):一种数据序列化格式。优点:① 容易阅读;② 容易与脚本语言交互;③ 以数据为核心,重数据轻格式。
2.2.1、yaml语法规则
(核心规则:数据前面要加空格与冒号隔开):
- 大小写敏感;
- 属性层级关系使用多行描述,每行结尾使用冒号结束;
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用tab键);
- 属性值前面添加空格(属性名与属性值之间用冒号+空格作为分隔);
- # 表示注释。
# 设置端口 server: port: 80 user: name: zhangsan age: 18 users1: - name: zhangsan age: 18 - name: lisi age: 17 - name: wangwu age: 20 users2: [{name: zhangsan, age: 17}, {name: lisi, age: 18}] a: d: name: zhangsan hobbies: - game - music - sleep hobbies2: [game, music, sleep]
2.2.2、数据读取
使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名...}
package com.clp.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/books")
public class BookController {
/**
* country: beijing
*/
//读取yml数据中的单一数据
@Value("${country}")
private String country;
/**
* user0:
* name: zhangsan
* age: 18
*/
@Value("${user0.name}")
private String name;
/**
* users:
* - name: zhangsan
* age: 18
* - name: lisi
* age: 20
* @return
*/
@Value("${users[0].age}")
private int age;
@GetMapping
public String getById() {
System.out.println("springboot is running..");
System.out.println("country: " + country);
System.out.println("user.name: " + name);
System.out.println("users[0].age: " + age);
return "springboot is running...";
}
}
封装全部数据到Environment对象:
package com.clp.controller;
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired //使用自动装配将application.yml中所有的数据封装到一个对象Environment中
private Environment env;
@GetMapping
public String getById() {
System.out.println("springboot is running..");
System.out.println(env.getProperty("server.port"));
return "springboot is running...";
}
}
封装部分数据:(注意:封装类需要定义为Spring管理的bean,否则无法进行属性注入)
package com.clp;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* application.yml:
* # 创建类,用于封装下面的数据
* # 由spring帮我们去加载数据到对象中,一定要告诉spring加载这些信息
* # 使用的时候从spring中直接获取信息使用
* datasource:
* driver: com.mysql.jdbc.Driver
* url: jdbc:mysql://localhost/springboot_db
* username: root
* pasword: 123456
*
* 步骤:
* 1、定义数据模型封装yaml文件中对应的数据(变量名要和键的名称一致)
* 2、定义为spring管控的bean(使用@Component)
* 3、指定加载的数据(使用@ConfigurationProperties)
*/
@Component
@ConfigurationProperties(prefix = "datasource") //指定加载的数据
public class MyDataSource {
private String driver;
private String url;
private String username;
private String password;
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
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;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
package com.clp.controller;
import com.clp.MyDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private MyDataSource myDataSource;
@GetMapping
public String getById() {
System.out.println("springboot is running..");
System.out.println("myDataSource: " +myDataSource);
return "springboot is running...";
}
}
2.2.3、yml文件中的变量引用
application.yml:
baseDir: C:\windows
# 使用${属性名}的方式引用数据
tempDir1: ${baseDir}\temp # \t不解析为制表符
# 使用双引号包裹的字符串,其中的转义字符可以生效
tempDir2: "${baseDir}\temp" # \t解析为制表符
3、整合第三方技术
3.1、整合JUnit
步骤:
- 导入对应的starter(创建工程时会自动导入);
<!--默认导入测试相关模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
- 测试类使用@SpringBootTest修饰;
- 使用自动装配的形式添加要测试的对象。
名称:@SpringBootTest
类型:测试类注解
位置:测试类定义上方
作用:设置JUnit加载的SpringBoot启动类
相关属性:classes:设置SpringBoot启动类。注意:如果测试类在SpringBoot启动类或子包中,可以省略启动类的设置,也就是省略classes的设定;测试类如果不存在于引导类所在的包或子包中需要通过classes属性指定引导类。
范例:
package com.clp;
import com.clp.dao.BookDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = Springboot03JunitApplication.class) //显式地写上引导类
class Springboot03JunitApplicationTests {
/**
* 测试步骤:
* 1、注入你要测试的对象;
* 2、执行要测试的对象对应的方法
*/
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
bookDao.save();
}
}
3.2、整合MyBatis
核心配置:数据库连接相关信息(连什么?连谁?什么权限?)
映射配置:SQL映射(XML/注解)
步骤:
- 创建新模块,选择Spring初始化,并配置模块相关基础信息;选择当前模块需要使用的技术集Dependencies(MyBatis Framework、MySQL Driver);
- 数据库连接相关信息转换成配置。设置数据源参数(在application.yml中):
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mydb username: root password: 123456
- 数据库SQL映射需要添加@Mapper被容器识别到。定义数据层接口与映射配置:
package com.clp.dao; import com.clp.domain.Book; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface BookDao { @Select("select * from tbl_book where id = #{id}") public Book getById(Integer id); }
3.3、整合MyBatis-Plus
3.4、整合Druid
步骤:
- 导入Druid对应的starter;
<!--导入druid相关依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency>
- 配置对应的设置或采用默认配置。变更Druid的配置方式如下:
# 方式1(推荐) spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc.mysql://localhost:3306/mydb?serverTimezone=UTC username: root password: 123456 # 方式2 #spring: # datasource: # driver-class-name: com.mysql.jdbc.Driver # url: jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC # username: root # password: 123456 # type: com.alibaba.druid.pool.DruidDataSource
整合第三方技术的通用方式:① 导入对应的starter;② 根据提供的配置格式,配置非默认值对应的配置项。
4、基于SpringBoot的SSMP整合案例
案例实现方案分析:
- 实体类开发——使用Lombok快速制作实体类;
- Dao开发——整合MyBatisPlus,制作数据层测试类;
- Service开发——基于MyBatisPlus进行增量开发,制作业务层测试类;
- Controller开发——基于Restful开发,使用PostMan测试接口功能;
- Controller开发——前后端开发协议制作;
- 页面开发——基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理(列表、新增、修改、删除、分页、查询);
- 项目异常处理;
- 按条件查询——页面功能调整、Controller修正功能、Service修正功能。
4.1、实体类快速开发
Lombok是一个Java类库,提供了一组注解,简化POJO实体类开发。
常用注解:
- @Data:为当前实体类在编译期设置对应的get() / set()方法,toString()方法, hashCode()方法,equals()方法等。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
4.2、数据层开发
技术实现方案:MyBatisPlus、Druid。
步骤:
- 导入MyBatisPlus与Druid对应的starter:
<!--因为在parent中没有维护下面这2个版本,所以需要手动加--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency>
- 配置数据源与MyBatisPlus对应的配置。(application.yml):
# 配置端口号 server: port: 80 # 配置druid数据源 spring: datasource: druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db username: root password: 123456 # 配置表前缀 mybatis-plus: global-config: db-config: table-prefix: tbl_ id-type: auto #设置自增策略,即执行insert语句时,id增加的方式
- 开发Dao接口,并继承BaseMapper并指定泛型:
package com.clp.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.clp.domain.Book; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface BookDao extends BaseMapper<Book> { // /** // * MyBatis的使用方式 // * @param id // * @return // */ // @Select("select * from tbl_book where id = #{id}") // Book getById(Integer id); }
为了方便调试可以开启MyBatisPlus的日志:
# 配置表前缀
mybatis-plus:
configuration:
# 配置日志方式,设置日志输出方式为标准输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.2.1、分页
分页操作需要设定分页对象IPage。
/**
* 分页功能需要添加一个拦截器(使用配置类进行配置)
*/
@Test
void testGetPage() {
IPage page = new Page(2, 5);
// 使用IPage封装分页数据,分页操作依赖MyBatis分页拦截器实现功能
IPage iPage = bookDao.selectPage(page, null); //ipage就是page
System.out.println(iPage.getCurrent()); // 2 当前页码值
System.out.println(iPage.getSize()); // 5
System.out.println(iPage.getTotal()); // 16
System.out.println(iPage.getPages()); // 4 页数
System.out.println(iPage.getRecords()); //记录
}
IPage对象中封装了分页操作中的所有数据:
- 数据;
- 当前页码值;
- 每页数据总量;
- 最大页码值;
- 数据总量。
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatis拦截器实现。
/**
* (配置类)
*/
@Configuration
public class MPConfig {
/**
* 配置MyBatisPlus拦截器,交给SpringBoot管理
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建mybatis拦截器(壳)
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加拦截器1(分页拦截器)
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
4.2.2、条件查询
使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用。查询条件支持动态条件封装。
/**
* 条件查询
*/
@Test
void testGetBy() {
// 方式1
QueryWrapper<Book> qw = new QueryWrapper<>();
//select * from tbl_book where name like 'a';
qw.like("name", "a");
List<Book> books = bookDao.selectList(qw);
System.out.println(books);
//方式2 使用Lambda表达式
String name = "a";
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
//lqw.like(Book::getName, name);
// 如果name不为null,则进行查询
lqw.like(name != null, Book::getName, name);
bookDao.selectList(lqw);
}
package com.clp.dao;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clp.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class BookDaoTest1Case {
@Autowired
private BookDao bookDao;
@Test
void testGetById() {
System.out.println(bookDao.selectById(1));
}
@Test
void save() {
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.insert(book);
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(17);
book.setType("abcdefg");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookDao.updateById(book);
}
@Test
void testDelete() {
bookDao.deleteById(16);
}
@Test
void testGetAll() {
List<Book> books = bookDao.selectList(null);
System.out.println(books);
}
/**
* 分页功能需要添加一个拦截器(使用配置类进行配置)
*/
@Test
void testGetPage() {
IPage page = new Page(2, 5);
IPage iPage = bookDao.selectPage(page, null); //ipage就是page
System.out.println(iPage.getCurrent()); // 2 当前页码值
System.out.println(iPage.getSize()); // 5
System.out.println(iPage.getTotal()); // 16
System.out.println(iPage.getPages()); // 4 页数
System.out.println(iPage.getRecords()); //记录
}
/**
* 条件查询
*/
@Test
void testGetBy() {
// 方式1
QueryWrapper<Book> qw = new QueryWrapper<>();
//select * from tbl_book where name like 'a';
qw.like("name", "a");
List<Book> books = bookDao.selectList(qw);
System.out.println(books);
//方式2 使用Lambda表达式
String name = "a";
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
//lqw.like(Book::getName, name);
// 如果name不为null,则进行查询
lqw.like(name != null, Book::getName, name);
bookDao.selectList(lqw);
}
}
4.3、业务层开发
Service层接口定义和数据层接口定义具有较大区别,不要混用。Service接口名称定义成业务名称,并与Dao接口名称进行区分。
package com.clp.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.clp.domain.Book;
import java.util.List;
public interface BookService {
Boolean save(Book book);
Boolean update(Book book);
Boolean delete(Integer id);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getPage(int currentPage, int pageSize);
}
package com.clp.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clp.dao.BookDao;
import com.clp.domain.Book;
import com.clp.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 该注解将该类定义成业务层对应的Bean
*/
@Service
public class BookServiceImpl implements BookService {
@Autowired
BookDao bookDao;
@Override
public Boolean save(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
@Override
public Book getById(Integer id) {
return bookDao.getById(id);
}
@Override
public List<Book> getAll() {
return bookDao.selectList(null);
}
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage, pageSize);
return bookDao.selectPage(page, null);
}
}
package com.clp.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.clp.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class BookServiceTestCase {
@Autowired
private BookService bookService;
@Test
void testGetById() {
Book book = bookService.getById(4);
System.out.println(book);
}
@Test
void save() {
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService.save(book);
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(17);
book.setType("abcdefg");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService.update(book);
}
@Test
void testDelete() {
bookService.delete(16);
}
@Test
void testGetAll() {
List<Book> books = bookService.getAll();
System.out.println(books);
}
/**
* 分页功能需要添加一个拦截器(使用配置类进行配置)
*/
@Test
void testGetPage() {
IPage iPage = bookService.getPage(2,5); //ipage就是page
System.out.println(iPage.getCurrent()); // 2 当前页码值
System.out.println(iPage.getSize()); // 5
System.out.println(iPage.getTotal()); // 16
System.out.println(iPage.getPages()); // 4 页数
System.out.println(iPage.getRecords()); //记录
}
}
4.3.1、业务层开发——快速开发
快速开发方案:
- 使用MyBatisPlus提供有业务通用接口(IService<T>)与业务层通用实现类(ServiceImpl<M,T>);
- 在通用类基础上做功能重载或功能追加;
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失。
package com.clp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.clp.domain.Book;
public interface IBookService extends IService<Book> {
}
package com.clp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.clp.dao.BookDao;
import com.clp.domain.Book;
import com.clp.service.IBookService;
import org.springframework.stereotype.Service;
@Service
public class IBookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
}
4.4、表现层开发
- 基于Resftul进行表现层接口开发;
- 使用Postman测试表现层接口功能。
实体数据:@RequestBody
路径变量:@PathVariable
package com.clp.controller;
import com.clp.domain.Book;
import com.clp.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService iBookService;
@GetMapping
public List<Book> getAll() {
return iBookService.list();
}
}
package com.clp.controller;
import com.clp.domain.Book;
import com.clp.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService iBookService;
@GetMapping
public List<Book> getAll() {
return iBookService.list();
}
@PostMapping
public Boolean save(@RequestBody Book book) {
return iBookService.save(book);
}
@PutMapping
public Boolean update(@RequestBody Book book) {
return iBookService.modify(book);
}
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id) {
return iBookService.delete(id);
}
/**
* http://localhost/books/2
*/
@GetMapping("{id}")
public Book getById(@PathVariable Integer id) {
return iBookService.getById(id);
}
}