这里我们的jdk,安装的是8,所以版本选成8.
如果是mvc项目,这里要用web。选这个。
写个入门案例
package com.itheima.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Just Ching
* @create 2022-02-09 6:34
*/
//rest开发模式
@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping
public String getById(){
System.out.println("sb is running");
return "spring boot is running";
}
}
https://start.spring.io/
点击下方generate生成。
下载这个文件,放进IDEA工作空间。就可以了。
这个创建速度十分快。
c
创建一个全新的模块,maven的pom,copy springboot的pom就行
org.springframework.boot
spring-boot-starter-parent
2.6.3
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
然后new一个全新的类。
,然后就可以用了。
README.md;mvnw.cmd;.gitignore;HELP.md;mvnw;.mvn;*.iml;
现在在pom里,导包不用加版本号,它自己选择最适合的。避免冲突。
里面放的都是坐标版本。
点开一看。
里面的依赖管理。
我们建立项目选的web类型,自动给导入starter。
导入了starter,starter有web要用的包。
这句话就是启动spring容器。
这个能扫描到它所在包的bean。别的包扫描不到。
内嵌的tomcat核心。
将tomcat执行过程抽取出来,变成一个对象。然后把这个对象交给spring容器去管。
不用tomcat,用jetty。排除这个,导入jetty。
发的值少,用路径参数@pathvariable。
值多,用json。
不同的提交类型对应不同的方法。
这样可以取到值给自己的方法。
1.直接去掉value,自动识别了属于是。
路径提到类上。
@ResponseBody,也提出来,放在类上,这个类里所有的方法都带有这个注解
上面俩注解可以合并
2.能不能去掉method?
复制一份,模块文件。然后修改新工程里的pom文件。
别的文件删了,就剩这俩。
idea里导入全新的模块
然后导入的时候选maven工程。
在IDEA打开后,再打开pom文件。删掉这两句。
或者修改
里面能配置,是因为pom里导入的spring相关的包。
必须得有stater web包才能配置。
boot使用的配置文件。 properties,改个key和value。
改成这样,我们启动项目的时候。访问路径就会变化。
前面加前缀,访问。
banner。导入starter包就能用。
改成自己的图片。
改成debug调试的时候可以看到boot启动的每一步。
改成error,出错才有日志。默认是info级别。
格式写的不一样罢了。
yaml里写配置信息,没有提示。是因为boot现在不认这是配置文件。
所以我们要改的让它认识这是配置文件。
然后点击加进去就行。
然后成功后的样子。
如果添加的时候不识别,那就先建properties文件。再添加。
容易阅读。
语法规则
属性前面有空格。
同样的名称只允许出现一次。
数组表示,用-,分开一个数组对象。
controller里读取。用spring的value注入。
读取user的属性。
转义字符的使用
这里就转义字符\t,起作用了。制表符。
使用引号包裹的字符串,里面转义的字符可以生效
1.封装全部数据
整一个,所有数据的变量对象。自动装进。
然后用这个对象的getProperty方法获取值。
2.封装自己需要的数据。
比如我们只想要datasource。
创建类,用于封装下面的数据,这个类定义成bean。注意:类里的属性名,与配置文件属性名必须一样
让spring 帮我们去加载数据到对象中,一定 要告诉spring加载这组信息
用@ConfigurationProperties将配置文件的属性绑定。
后面生成对象,会把属性里的值赋值给对象。
目的学会思想:拿到任何第三方技术的时候,怎么开展工作。
两个步骤。
test里的这个类就是整合Junit的类。
随便建一个boot工程,Junit是自带的。pom里这个是自动导入的。
1.建一个dao类,里面有方法,测试里面的方法。
2.在test里的那个类里测试。
@SpringBootTest
这个注解定义了这个类是测试类。注入对象,就可以用这个对象的方法。
如果当前的测试类,在引导类所在的包及其子包下。不需要修改什么,这个测试类就可以启动。
那如果当前的测试类,不在引导类所在的包及其子包下。需要在注解后加一句话。
写上引导类的类名。
新建一个项目,SQL那里选上Mybatis Framework。和MySQL Driver
pom文件里发生了一些变化。
配上datasource。
mapper层写好数据库查询方法。
然后在测试类里运行。
配时区。
解决方法1:url后面加上。
driver废弃,建议换新的driver。
这里修改成这样即可。
正常建立boot项目,spring里没有引进mybatis plus。只导入mysQL的驱动。
然后去mvn里查,mybatis plus。
找到要用的版本定义的坐标。导入pom。
在mapper的接口里。继承BaseMapper,把要操作的对象的泛型传进去。
里面有很多写好了的方法。国人开发好的技术。
但后面会遇到一个问题与数据库的表设定有关系。
也就是它自己默认搜的表,没和我们数据库的表对应上。
实体类的名和数据库表名不一样。
方法一:
我们可以在domain里的实体类上加一个注解。@TableName("数据库里的表名。")
方法二:在yml文件里,加上前缀或后缀(与数据库里表的不同)。
数据源是一种用来提高数据库连接性能的常规手段,数据源会负责维持一个数据库连接池,当程序创建数据源实例时,系统会一次性地创建多个数据库连接,并把这些数据库连接保存在连接池中。当程序需要进行数据库访问时,无须重新获得数据库连接,而是从连接池中取出一个空闲的数据库连接,当程序使用数据库连接访问结束后,无须关闭数据库连接,而是将数据库连接归还给连接池即可。通过这种方式,就可比避免频繁地获取数据库连接,关闭数据库连接所导致的性能下降。
数据源是给mybatis用的。
方式一:
导入druid坐标。
在yml文件里配上
方式二:没导druid的starter。
建一个项目,导入MP得坐标。
自己建一个数据库表
boot里收录了,lombok得坐标。
实体类加@data注解,除了构造方法。例如tostring,get set,hashcode。
都写好了。
application.yml
server:
port: 80
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssmp?serverTimezone=UTC
username: root
password: a839846976
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
接口dao
package com.book.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.book.domain.Book;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BookDao extends BaseMapper {
}
加数据的时候, 参数类型对应失败。
这里id,没有像数据库里那样自动生成。
Exception: nested exception is org.apache.ibatis.reflection.ReflectionException:
Could not set property 'id' of 'class com.book.domain.Book' with value '1492005888272625666' Cause: java.lang.IllegalArgumentException: argument type mismatch。
这里就选标准的形式。再运行就得到整个运行过程。
在方法里写好分页的方法
如果不用拦截器,结果是,SQL语句里没有limit语句。
这个limit限制,必须得自己配置。
写一个配置类,用拦截器的方法,来加上limit语句。。
Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
@configuration,标记为配置类,这样可以读取里面的配置信息。
然后@bean注入MP的拦截器。创建MP的拦截器,添加内部拦截器。
添加一个分页的拦截器,PagenationInnerInterceptor。
return出来就行。
就能实现limit分页的功能了。
结果,getRecords,就是得到当前第二页的所有数据。
getTotal就是获取所有数据的条数。
getPages,就是获得这些数据总共分了多少页。
分页查询的代码:
MGConfig.java
package com.book.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor (){
MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
bookService里
IPage getPage(int currentPage,int pageSize);
bookServiceimpl里
@Override
public IPage getPage(int currentPage, int pageSize) {
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
里面封装了一个查询对象。可以通过修改这个对象的属性,更改条件。
就比如 like属性。
定义一个查询类型。
用like方法,匹配查询,含有name属性含有,spring,的book实体。
类似这句SQL。
结果,看日志里。
这个方法的改进方法,这样写属性的时候,name属性就不会自己写错了。
这里给name当成字符串了。
解决方法:里面判断一下。是不是null
给前面整的按条件查询的条件代入。
MP里的方法,返回值都为int类型,影响几行。
在test建立的service包下测试。
在业务层不像数据库层那样能看详细日志。
调试的时候还是打印来看。
bookService 接口
package com.book.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.book.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 getAll();
IPage getPage(int currentPage,int pageSize);
}
bookServiceimpl
package com.book.Service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.book.Service.BookService;
import com.book.dao.BookDao;
import com.book.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author Just Ching
* @create 2022-02-11 18:39
*/
@Service
public class BookServiceimpl implements BookService {
@Autowired
private 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.selectById(id);
}
@Override
public List getAll() {
return bookDao.selectList(null);
}
@Override
public IPage getPage(int currentPage, int pageSize) {
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
/*或者bookDao.selectPage(page,null);
return page,都是一个东西。都返回一个page*/
}
}
mybatis甚至帮我做了业务层的通用接口和它的实现类。........
但是很蠢,都是写死的方法。
这个类继承IService
然后在Service实现类里,用写好的方法。继承ServiceImpl
人家没写的方法你就自己写一下。
或者自己重写。
对象数据,用@requestBody,接收传过来的JSON数据
BookController.java
package com.book.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.book.Service.BookService;
import com.book.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author Just Ching
* @create 2022-02-11 22:48
*/
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@GetMapping
public List getAll(){
return bookService.getAll();
}
@PostMapping
public Boolean save(@RequestBody Book book)
{
return bookService.save(book);
}
@PutMapping
public Boolean update(@RequestBody Book book)
{
return bookService.update(book);
}
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id){
return bookService.delete(id);
}
@GetMapping("{id}")
public Book getById(@PathVariable Integer id){
return bookService.getById(id);
}
/*http://localhost/books/1/10*/
@GetMapping("{currentPage}/{pageSize}")
public IPage getPage(@PathVariable int currentPage,int pageSize){
return bookService.getPage(currentPage, pageSize);
}
}
主要是让前端看到操作成功没有
只要前后端协议好了就行。
前后端数据协议。
R.java
package com.book.controller.utils;
import lombok.Data;
@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;
}
}
BookController2.java
package com.book.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.book.Service.BookService;
import com.book.controller.utils.R;
import com.book.domain.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController2 {
@Autowired
private BookService bookService;
@GetMapping
public R getAll(){
return new R(true,bookService.getAll());
}
@PostMapping
public R save(@RequestBody Book book)
{
return new R(bookService.save(book));
}
@PutMapping
public R update(@RequestBody Book book)
{
return new R(bookService.update(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));
/*这里直接flag是true*/
}
/*http://localhost/books/1/10*/
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,int pageSize){
return new R(true,bookService.getPage(currentPage,pageSize));
}
}
不然会有idea的小bug。
然后在控制台,看显示的信息。
这里发的是get请求,如果是post请求。.post
我们下一步要让res.data展示出来。要展示的表的数据来源“dataList”。
我们只需要让dataList里面有数据,就能展示出来。
EL UI,的双向数据绑定。
res的data里,有flag和data。所以要再写一个。
触发handleCreate()函数。 然后把新增的界面展示出来
重启项目,就能弹出。这是个弹出页面。点击之后,
在handle方法里,把弹出属性设置为true,就弹出了。
然后写完数据,点确定,我们要把数据发到后台。它关联一个handleAdd(),方法。
我们把这个formData,给post过去
flag为true了。
添加成功或失败的功前端源码:
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
//判断当前操作是否成功
if(res.data.flag){
/*1.成功关闭弹层*/
this.dialogFormVisible= false;
this.$message.success("添加成功");
}else {
this.$message.error("添加失败");
}
/*不管成功失败,都要查询一遍结果*/
}).finally(()=>{
this.getAll();
});
},
但随之而来的小问题,随着我们新建一个图书,里面的值还在。所以我们需要,每次弹层的时候,
清空里面的数据。
给里面的formData清空。
ELMENT UI里面把行封装成对象。row指的是这一行的数据。
我们随便点一个删除,把操作打成consloe.log(row),控制台里看row的信息。
,如果手抖删错了也不行,得在删除前做一个提醒。
然后点确定才进行删除。写一下确定按钮对应的功能。catch操作的是取消按钮,没有catch就是执行确定的按钮。
我们之前写好的删除功能如下:
把它和删除前的确认结合即可。
handleDelete(row) {
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
/*1.成功关闭弹层*/
this.$message.success("删除成功");
this.dialogFormVisible= false;
}else {
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
},
结合后的代码:
// 删除
handleDelete(row) {
this.$confirm("此操作将永久删除当前信息,是否继续?","提示",{type:"info"}).then(()=>{
axios.delete("/books/"+row.id).then((res)=>{
if(res.data.flag){
this.$message.success("删除成功");
}else {
this.$message.error("删除失败");
}
}).finally(()=>{
this.getAll();
});
}).catch(()=>{
this.$message.info("取消操作");
});
},
点开修改功能,类似添加页面,里面有回显的数据。然后修改这些数据。
先弹出编辑窗口
//弹出编辑窗口
handleUpdate(row) {
/*后端 update功能是get方法。*/
axios.get("/books/"+row.id).then((res)=> {
if(res.data.flag && res.data.data != null)
{
//编辑用的弹出窗口。
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
}else {
this.$message.error("数据同步失败,自动刷新")
}
}).finally(()=>{
this.getAll();
});
},
然后在编辑窗口里修改。看看弹层里对应的方法,写那个方法。
这个handleEdit功能给表里的数据传进去。跟添加功能一样,给弹出的那个页面的数据,传进数据库。不过这个弹层的
点取消去cancel方法。
抛一个数据库IO异常。
正常返回给前台,不是true就是false,你这里抛出的异常给前端,别人不好处理。
我们要保证的,除了异常,也要给前端他们读的懂的数据。
SpringMVC里有异常处理器,在表现层里处理就好,因为异常会一层一层往上抛出去。
这里拦截异常,然后把它处理成R对象传给前台。
这里用@RestControllerAdvice,是因为要用response。可以点开注解进去看看。
我们在controller抛出一个异常,检查一下。
@PostMapping
public R save(@RequestBody Book book) throws IOException {
if(book.getName().equals("123")) throw new IOException();
return new R(bookService.save(book));
}
在前台接收这个msg。
R里整个构造器。
页面上的布局 LayOut
vue里定义的三个初始数据。前端定义的分页的值。
我们需要给从前端,把值传给后台让它做一个分页处理。然后把值返回给前台。
这里@PathVariable。注解,一个参数后面要跟一个注解。这里必须两个
然后我们点击要跳到的页面的数字。
这里会返回一个currentPage。点下2,currentPage=2。我们在红线的方法里处理。
有两个BUG,
1是id的问题,每次都是从1开始。
解决:这里绑定id即可。
2.是如果当前页数据删完了,想跳到第三页,第三页却已经不存在。
解决:如果当前的页码值,大于总页码值数,使用最大页码值作为当前页码值。
这三条数据没有绑定。而且点了这个查询按钮,跳到getALL方法,说明条件查询做在getAll方法里。
定义这一下这三个数据模型。
完成绑定。
然后我们接收查询里传来的信息
通过param参数传到后端。
getAll() {
param ="?type="+this.pagination.type;
param +="&name="+this.pagination.name;
param +="&description" +this.pagination.description;
//发送异步请求,用箭头函数,把里面的数据取出来。
axios.get("/books/"+ this.pagination.currentPage + "/"+this.pagination.pageSize + param).then((res)=>{
this.pagination.currentPage=res.data.data.current;
this.pagination.total=res.data.data.total;
this.dataList=res.data.data.records;
});
},
在controller层里用book对象接收。
输出可看到。
把book放进方法里,接口里没有这个方法。点击refactor,添加这个方法。
service层改成这样,然后去实现类里改。
实现类里重写的方法。实现了模糊查询。
@Override
public IPage getPage(int currentPage, int pageSize, Book book) {
LambdaQueryWrapper lqw =new LambdaQueryWrapper();
lqw.like(Strings.isEmpty(book.getType()),Book::getType,book.getType());
lqw.like(Strings.isEmpty(book.getName()),Book::getName,book.getName());
lqw.like(Strings.isEmpty(book.getDescription()),Book::getDescription,book.getDescription());
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,lqw);
/*或者bookDao.selectPage(page,null);
return page,都是一个东西。都返回一个page*/
}
@Override
public IPage getPage(int currentPage, int pageSize) {
IPage page=new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
/*或者bookDao.selectPage(page,null);
return page,都是一个东西。都返回一个page*/
}