SpringBoot 简介
简化
Spring应用的初始搭建以及开发过程创建SpringBoot工程的四种方式
//Rest模式
@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping
public String getById(){
System.out.println("springboot is running...");
return "springboot is running...";
}
}
Spring程序与SpringBoot程序对比:
如果IDEA无法联网,如何创建SpringBoot工程呢?
基于SpringBoot官网创建项目,地址:SpringBoot官网
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.4version>
parent>
<groupId>com.itheimagroupId>
<artifactId>springboot_01_03_quickstartartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
制作引导类Application
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
小结:
@SpringBootApplication
public class Springboot01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01QuickstartApplication.class, args);
}
}
小结:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
dependencies>
Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty
内置服务器:
小结:
总结:
#服务器端口配置
server.port=80
#关闭运行日志图标(banner)
spring.main.banner-mode=off
#设置日志相关
logging.level.root=debug
SpringBoot提供了3种配置文件的格式:
如果三种配置文件共存,则优先properties
小结:
空格
如果数据量过大,可以封装全部数据到Environment对象
@Component
@ConfigurationProperties(prefix = "datasource")
public class MyDataSource {
private String drive;
private String url;
private String username;
private String password;
public MyDataSource() {
}
public MyDataSource(String drive, String url, String username, String password) {
this.drive = drive;
this.url = url;
this.username = username;
this.password = password;
}
public String getDrive() {
return drive;
}
public void setDrive(String drive) {
this.drive = drive;
}
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{" + "drive='" + drive + '\'' + ", url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + '}';
}
}
小结:
@Value
配合SpEL读取单个数据 ${属性名}
方式引用属性值双引号
包裹起来作为字符解析@Autowired
自动装配数据到Environment对象中REST
(Representational State Transfer),表现形式状态转换
按照REST风格访问资源时使用行为动作
区分对资源进行了何种操作
名称:@RequestMapping
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问路径
范例:
@RequestMapping(value = "/users", method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody User user){
system.out.println( "user save. . ." + user);
return "{ 'module' : 'user save ' }";
}
小结:
区别
@RequestParam用于接收url地址传参或表单传参
@RequestBody用于接收json数据
@PathVariable用于接收路径参数,使用{参数名称}描述路径参数
应用
后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
如果发送非json格式数据,选用@RequestParam接收请求参数
采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值
名称:@RestController
类型:类注解
位置:基于SpringMVC的RESTful开发控制器类定义上方
作用:设置当前控制器类为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能
范例:
@RestController
public class BookController {}
名称:@GetMapping @PostMapping @PutMapping @DeleteMapping
类型:方法注解
位置:基于SpringMVC的RESTful开发控制器方法定义上方
作用:设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping对应GET请求
范例:
@GetMapping("/{id}")
public String getById(@PathVariable Integer id){
system.out.println( "book getById.. . "+id);
return "{'module' : 'book getById'}";
}
属性
小结:
SpringBoot整合JUnit:
@SpringBootTest
@SpringBootTest
class Springboot04JunitApplicationTests {
//1.注入你要测试的对象
@Autowired
private BookDao bookDao;
@Test
void contextLoads() {
//2.执行要测试的对象对应的方法
bookDao.save();
}
}
小结:
自动装配
的形式添加要测试的对象若是将测试类移到其他包,再运行,会出现以下错误:
原 因:当前的测试类并在引导类所在包或则子包下(不在同级目录),无法被扫描,测试类无法找到引导类。
@SpringBootTest(classes = Springboot04JunitApplication.class)
class Springboot04JunitApplicationTests {
}
注意事项:
如果测试类在SpringBoot启动类的包或子包中,可以省略启动类的设置,也就是省略classes的设定
小结:
MyBatis-Plus与MyBatis区别:
小结:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.6version>
dependency>
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root
小结:
勾选SpringMVC与MySQL坐标
导入MyBatis-Plus和Druid坐标
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.23version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.2version>
dependency>
修改配置文件为yml格式
设置端口为80方便访问
server:
port: 80
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
常用注解:@Data
为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
@SpringBootTest
public class BookDaoTest {
@Autowired
private BookDao bookDao;
@Test
void testSave() {
Book book = new Book();
book.setName("测试数据");
book.setType("测试类型");
book.setDescription("测试描述数据");
bookDao.insert(book);
}
@Test
void testGetById() {
System.out.println(bookDao.selectById(2));
}
...
}
注意:如果这里直接运行testSave方法,会出现以下错误:
原因: 雪花算法丢失精度
解决方法:配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略
)
mybatis-plus:
global-config:
db-config:
table-prefix: tb1_
id-type: auto
为方便调试可以开启MyBatisPlus的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
分页
分页操作需要设定分页对象IPage
@Test
void testGetPage() {
IPage page = new Page(1,2);
bookDao.selectPage(page, null);
}
如果直接在测试类这么写,会出现以下问题:
由于我的数据比较少,如果数据比较多,会全部显示出来,而不是出现分页的效果,可以看到sql语句尾部缺少了limit
,因为无法做到分页。想要分页,必须使用拦截器(分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现)
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//定义Mp拦截器
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加具体的拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
结果如下:
小结:
条件查询
使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
@Test
void testGetByCondition(){
IPage page = new Page(1,2);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Book::getName,"Spring");
bookDao.selectPage(page,lqw);
}
@Test
void testGetByCondition(){
QueryWrapper<Book> qw = new QueryWrapper<Book>();
qw.like("name","Spring");
bookDao.selectList(qw);
}
Service层接口定义与数据层接口定义具有较大区别,不要混用
接口定义
public interface BookService {
boolean save(Book book);
boolean delete(Integer id);
boolean update(Book book);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getByPage(int currentPage,int pageSize);
}
实现类定义
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
//大于0,是因为测出来结果是修改的行数
public Boolean save(Book book) {
return bookDao.insert(book) > 0;
}
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
}
测试类定义
@SpringBootTest
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
void testGetById(){
bookService.getById(9);
}
@Test
void testGetAll(){
bookService.getAll();
}
@Test
void testGetByPage(){
bookService.getByPage(1,5);
}
}
快速开发
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
}
追加
功能@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
@Autowired
private BookDao bookDao;
public Boolean insert(Book book) {
return bookDao.insert(book) > 0;
}
public Boolean modify(Book book) {
return bookDao.updateById(book) > 0;
}
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
public Book get(Integer id) {
return bookDao.selectById(id);
}
}
总结小结:
业务层消息一致性处理
//作为springmvc的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常信息
@ExceptionHandler(Exception.class)
public R doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
ex.printStackTrace();
return new R(false,null,"系统错误,请稍后再试!");
}
}
@Data
public class R{
private Boolean flag;
private Object data;
private String msg;
public R(Boolean flag,Object data,String msg){
this.flag = flag;
this.data = data;
this.msg = msg;
}
}
//添加
handleAdd () {
//发送ajax请求
axios.post("/books",this.formData).then((res)=>{
//如果操作成功,关闭弹层,显示数据
if(res.data.flag){
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else {
this.$message.error(res.data.msg);
}
}).finally(()=>{
this.getAll();
});
},
@PostMapping
public R save(@RequestBody Book book) throws IOException {
Boolean flag = bookService.insert(book);
return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
}
//添加
handleAdd () {
//发送ajax请求
axios.post("/books",this.formData).then((res)=>{
if(res.data.flag){
this.dialogFormVisible = false;
this.$message.success(res.data.msg);
}else {
this.$message.error(res.data.msg);
}
}).finally(()=>{
this.getAll();
});
}
小结:
表现层开发
小结:
表现层消息一致性处理
@Data
public class R{
private Boolean flag;
private Object data;
}
public R(){}
public R(Boolean flag){
this.flag = flag;
}
public R(Boolean flag,Object data){
this.flag = flag;
this.data = data;
}
}
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public R getAll() {
return new R(true, bookService.list());
}
@PostMapping
public R save(@RequestBody Book book) {
// R r = new R();
// boolean flag = bookService.save(book);
// r.setFlag(flag);
// return r;
return new R(bookService.save(book));
}
@PutMapping
public R update(@RequestBody Book book) {
return new R(bookService.modify(book));
}
@DeleteMapping("{id}")
public R delete(@PathVariable Integer id) {
return new R(bookService.delete(id));
}
@GetMapping("{id}")
public R getById(@PathVariable Integer id) {
return new R(true, bookService.getById(id));
}
@GetMapping("{currentPage}/{PageSize}")
public R getPage(@PathVariable int currentPage, int pageSize) {
return new R(true, bookService.getPage(currentPage, pageSize));
}
}
小结:
前端发送异步请求,调用后端接口
//列表
getAll() {
axios.get("/books").then((res)=>{
console.log(res.data);
});
},
小结:
列表页
将查询数据返回到页面,利用前端数据双向绑定进行数据展示
methods: {
//列表
getAll() {
//发送异步请求
axios.get("/books").then((res)=>{
// console.log(res.data);
this.dataList =res.data.data;
});
},
添加功能
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
},
//添加
handleAdd() {
//发送异步请求
axios.post("/books",this.formData).then((res)=>{
//如果操作成功,关闭弹层,显示数据
if(res.data.flag){
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else {
this.$message.error("添加失败");
}
}).finally(()=>{
//重新加载数据
this.getAll();
});
},
添加完数据之后,点击新建,发现之前创建的数据仍在表单里,如下:
解决这种情况,需要设置“重置表单,清除数据”
//重置表单
resetForm() {
this.formData = {};
},
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
},
//取消
cancel(){
this.dialogFormVisible = false;
this.$message.info("操作取消");
},
小结:
删除功能
// 删除
handleDelete(row) {
// console.log(row);
axios.delete("/books/" + row.id).then((res) => {
if (res.data.flag) {
this.$message.success("删除成功");
} else {
this.$message.error("删除失败");
}
}).finally(() => {
this.getAll();
});
},
通过上面的方法可以实现删除的功能,但是很容易误删,需要设置弹窗提醒,是否删除
// 删除
handleDelete(row) {
//1.弹出提示框
this.$confirm("此操作永久删除当前数据,是否继续?","提示",{
type:'info'
}).then(()=>{
//2.做删除业务
axios.delete("/books/"+row.id).then((res)=>{
}).finally(()=>{
this.getAll();
});
}).catch(()=>{
//3.取消删除
this.$message.info("取消删除操作");
});
}
修改功能
//弹出编辑窗口
handleUpdate(row) {
axios.get("/books/" + row.id).then((res) => {
if (res.data.flag && res.data.data != null) {
//展示弹层,加载数据
this.formData = res.data.data;
this.dialogFormVisible4Edit = true;
} else {
this.$message.error("数据同步失败,自动刷新");
}
}).finally(()=>{
this.getAll();
});
},
// 删除
handleDelete(row) {
// console.log(row);
axios.delete("/books/" + row.id).then((res) => {
if (res.data.flag) {
this.$message.success("删除成功");
} else {
this.$message.error("数据同步失败,自动刷新");
}
}).finally(() => {
this.getAll();
});
},
//修改
handleEdit() {
axios.put("/books",this.formData).then((res)=>{
//如果操作成功,关闭弹层并刷新页面
if(res.data.flag){
this.dialogFormVisible4Edit = false;
this.$message.success("修改成功");
}else {
this.$message.error("修改失败,请重试");
}
}).finally(()=>{
this.getAll();
});
},
cancel(){
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("操作取消");
},
小结:
分页功能
这样操作有一个BUG,最后一页,若只有一个数据,删了以后,最后一页的页面还在
处理方法:
对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if( currentPage > page.getPages()){
page = bookService.getPage((int)page.getPages(), pageSize);
}
return new R(true, page);
}
条件查询
由于这三个属性(name、type、description)都是跟着分页走的,所以直接放到pagination中
pagination: { //分页相关模型数据
currentPage: 1, //当前页码
pageSize:10, //每页显示的记录数
total:0, //总记录数
name: "",
type: "",
description: ""
}
<div class="filter-container">
<el-input placeholder="图书类别" v-model="pagination.type" style="width: 200px;"
class="filter-item">el-input>
<el-input placeholder="图书名称" v-model="pagination.name" style="width: 200px;"
class="filter-item">el-input>
<el-input placeholder="图书描述" v-model="pagination.description" style="width: 200px;"
class="filter-item">el-input>
<el-button @click="getAll()" class="dalfBut">查询el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建el-button>
div>
//分页查询
getAll() {
//获取查询条件,拼接查询条件
// url:/books/1/10?type=???name=???&description=???
param = "?name=" + this.pagination.name;
param += "&type=" + this.pagination.type;
param += "&description=" + this.pagination.description;
console.log("-----------------" + param);
//发送异步请求
axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res) => {
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pageSize = res.data.data.size;
this.dataList = res.data.data.records;
});
},
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {
System.out.println("参数=====>"+book);
IPage<Book> pageBook = bookService.getPage(currentPage,pageSize);
return new R(null != pageBook ,pageBook);
}
public interface IBookService extends IService<Book> {
IPage<Book> getPage(Integer currentPage,Integer pageSize,Book book);
}
@Service
public class IBookServiceImpl extends ServiceImpl<BookDao,Book> implements IBookService {
public IPage<Book> getPage(Integer currentPage,Integer pageSize,Book book){
IPage page = new Page(currentPage, pageSize);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
lqw.like(Strings.isNotEmpty(book.getName()), Book::getName, book.getName());
lqw.like(Strings.isNotEmpty(book.getType()), Book::getType, book.getType());
lqw.like(Strings.isNotEmpty(book.getDescription()), Book::getDescription, book.getDescription());
return bookDao.selectPage(page, lqw);
}
}