1、新建项目
Spring Web + MySQL Driver + Dev Tools添加后三个选项
修改配置文件为yml格式
端口设置为80 方便访问
手动导入Mybatis Plus 和 Druid连接池
这里遇到一个问题,就是修改MP或druid版本号会报错?
2、创建数据库表对应的实体类
开发实体类
@Data替代了get/set方法,toString方法,hasCode方法,equals方法等
3、数据层开发(dao层)
主流的数据层技术:Mybatis、MP(Mybatis Plus)、Hibernate
(1)导入MP和Druid对应的依赖(第一步已完成)
完成druid的配置:
yml中输入data然后提示中选择driver-class-name,然后前面添加druid即可
然后再对MP的表前缀进行配置(Mybatis plus完成配置):
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
(2)然后创建数据层接口:
New新建 dao.BookDao接口
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
//泛型 填写对应的实体类
然后在Test文件夹下,建立相应dao的测试类,类上添加注解:@SpringBootTest
New dao.BookDaoTestCase
在这个测试类中测试了CRUD:
import com.bo.pojo.Book;
import lombok.AllArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
*
* @date 2022/4/26 - 14:34
*/
@SpringBootTest
public class BookDaoTestCase {
@Autowired
private BookDao bookDao;
//查询操作
//根据id查询
@Test
void testGetById(){
System.out.println(bookDao.selectById(2));
}
//增加记录
//不设置id字段会报错,mybatis系统异常,MP默认生成id策略由他自己设置,雪花算法
//我们想要使用表的自增策略,需要在yml中配置 id-type: auto
@Test
void testSave(){
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(2);
book.setType("测试数据Update");
book.setName("测试数据Update");
book.setDescription("测试数据Update");
bookDao.updateById(book);
}
//删除
@Test
void testDelete(){
bookDao.deleteById(2);
}
//查询所有的数据
//所有查询都以select开头
@Test
void testGetAll(){
System.out.println(bookDao.selectList(null));
}
}
(3)在yml文件中开启MP运行日志:
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
辅助debug,服务器上线时不要开启!!!
(4)实现分页查询
IPage是一个接口,Page是它的实现类,新建IPage接口对象,第一个参数为第几页(从1开始),第二个参数介绍每页有多少行记录(从1开始)
page对象中封装了分页操作中的所有数据:
数据、当前页码、每页数据总量、最大页码值、数据总量
最主要的是添加MP的拦截器!!!才能实现分页功能(也就是为sql语句拼接上limit)
@Test
void testGetPage(){
//IPage是一个接口
//哪一页的数据,一页显示多少数据(1,5) 第一页,一页显示5条
//分页功能实现 select * from tbl_book limit ?.?
//分页若想使用,必须使用拦截器实现
//返回值还是IPage对象 page
//返回的数据存在了page对象内
IPage page = new Page(2,5);
bookDao.selectPage(page,null);
//需要调用page对象的方法取出
System.out.println(page.getCurrent());
System.out.println(page.getSize());
System.out.println(page.getTotal());
System.out.println(page.getPages());
System.out.println(page.getRecords());
}
MP的分页拦截器:
New config.MPConfig
功能就是为SQL语句拼接部分内容 limit ....
@Configuration
public class MPConfig {
//交给spring一个bean
//bean是MP的拦截器
//这个拦截器里面里面添加了PaginationInnerInterceptor,添加了一个分页的子拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
(4)添加按条件查询的功能
上面的所有方法都可以添加查询条件!
@Test
void testGetBy(){
//QueryWrapper<>就是查询条件 泛型可写可不写QueryWrapper与QueryWrapper<>均可
//设置条件
//like 包括 就是sql中的like
//eq 等于
//ne 不等于
//lt 小于
//...
// QueryWrapper<Book> qw = new QueryWrapper<Book>();
//问题一:
// "name"是手写的,假如写错了怎么解决,属性容易写错
// qw.like("name","Spring"); name写错了会出错
//解决方法:
// qw.like(Book::getName(),name);
// 调用Book的getName方法
//问题二:
//如果name为null,会直接把null当作"null"拼接
//方法一:if判断再连接 if(name!=null) lqw.like(Book::getName,name);
//方法二:lqw.like(name!=null,Book::getName,name);
String name = null;
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(name!=null,Book::getName,name);
bookDao.selectList(lqw);
}
4、业务层开发(service层)
业务层接口关注的是业务的名称,login(String username, String password)适合业务层的登录业务,而相同功能的selectByUserNamePassword(String username, String password)适合数据层接口。
如果是业务方法,要定义为业务名,如果只是一般的增删改查,就写增删改查即可。
创建业务层接口-->创建业务层实现类-->在实现的方法调用dao中的方法(而该方法又是BaseMapper定义好的)
New service.BookService 接口
/**
* @date 2022/4/26 - 16:35
*/
//业务方法就叫业务名login()
//一些操作就叫操作名
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);
}
定义完业务层接口,接着创建实现类,在service包下新建impl包,在该包下面:
New BookServiceImpl类
/**
* @date 2022/4/26 - 16:39
*/
@Service //定义为业务层对应的bean
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public Boolean save(Book book) {
//大于0操作成功
//小于=0操作失败
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<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);
}
下面进行测试:
Test文件夹下 New service.BookServiceTestCase
/**
* @date 2022/4/26 - 16:44
*/
@SpringBootTest
public class BookServiceTestCase {
@Autowired
private BookService bookService;
@Test
void getById(){
System.out.println(bookService.getById(4));
}
@Test
void testSave(){
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
System.out.println(bookService.save(book));
}
//更新操作
@Test
void testUpdate(){
Book book = new Book();
book.setId(3);
book.setType("id2AAAA");
book.setName("测试数据Update");
book.setDescription("测试数据Update");
System.out.println(bookService.update(book));
}
//删除
@Test
void testDelete(){
System.out.println(bookService.delete(3));
}
//查询所有的数据
//所有查询都以select开头
@Test
void testGetAll(){
System.out.println(bookService.getAll());
}
@Test
void testGetPage(){
IPage<Book> page = bookService.getPage(2, 5);
//需要调用page对象的方法取出
System.out.println(page.getCurrent());
System.out.println(page.getSize());
System.out.println(page.getTotal());
System.out.println(page.getPages());
System.out.println(page.getRecords());
}
}
上面的步骤过于繁琐,如何简化?步骤如下
快速开发方案:使用MyBatisPlus提供业务层通用接口(IService<T>)与业务层通用实现类(ServiceImpl<M,T>),在通用类基础上做功能重载或功能追加,注意重载时不要覆盖原始操作,避免原始提供的功能丢失。
第一步--创建service接口,继承IServic<xxx> xxx填写对应的实体类
第二步--实现类定义
5、表现层开发(Controller层)
在该层完成表现层处理:
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService iBookService;
@GetMapping
public List<Book> getAll(){
return iBookService.list();
}
//传的是json数据
@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.removeById(id);
}
@GetMapping("{id}")
public Book getById(@PathVariable Integer id){
return iBookService.getById(id);
}
@GetMapping("{currentPage}/{pageSize}")
public IPage<Book> getPage(@PathVariable int currentPage, @PathVariable int pageSize){
return iBookService.getPage(currentPage,pageSize);
}
}
但是返回给前端的数据格式多种多样,前端处理起来很麻烦,所以需要统一一下后端的返回格式:
这里在utils包下面新增R类(Result),作为我们返回值的统一格式;
然后对Controller做修改:
@RestController
@RequestMapping("/books")
public class BookController2 {
@Autowired
private IBookService iBookService;
@GetMapping
public R getAll(){
return new R(true,iBookService.list());
}
//传的是json数据
@PostMapping
public R save(@RequestBody Book book){
return new R(iBookService.save(book));
}
@PutMapping
public R update(@RequestBody Book book){
return new R(iBookService.modify(book));
}
@DeleteMapping("{id}")
public R delete(@PathVariable Integer id){
return new R(iBookService.removeById(id));
}
@GetMapping("{id}")
public R getById(@PathVariable Integer id){
return new R(true, iBookService.getById(id));
}
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize){
return new R(true,iBookService.getPage(currentPage,pageSize));
}
}
6、前后端结合
前后端分离结构设计中页面归属前端服务器,这里暂时采用了单体工程
单体工程中页面放置在resour目录下的static目录中(建议执行clean)
在vue的数据模型中,dataList存储当前页要展示的列表数据;下面两个控制表单是否可见,formData用来记录表单数据,修改和新增用同一个表单数据。
导入前端页面后,然后在vue页面中利用axios发起异步请求,这里测试能否发起一个请求,在钩子函数created()中调用getAll()方法,刷新页面时自动执行。axios.xxx()发送请求,then接受数据后做处理。
查询全部数据并展示:
实现新增功能:
首先要弹出添加对话框,同时还要清除formData里面的旧数据
然后,点击确定之后,提交表单数据到后台,实现添加功能(当前操作失败的时候,不要关闭对话框):
点击取消按钮,会关闭添加或编辑窗口:
实现删除操作(then确认,catch取消):
修改操作:
相当于列表功能+新增功能组合,首先要弹出编辑窗口,并且要将准备修改的数据展示出来:
然后修改之后,提交到后台接口,确定提交:
异常消息处理:
如果后台出现异常,则会产生另一种消息格式给前端:
尽管代码出异常,我们也要返回给前端统一的格式,在springMVC中提供的专用的异常处理器:
可以使用ControllerAdvice注解或者RestControllerAdvice注解;
运行处异常之后,被异常拦截器拦截,然后返回一个R对象,所以修改R对象,添加一个msg消息:
R.class
@Data
public class R {
private Boolean flag;
private Object data;
private String msg;
public R(){}
public R(Boolean flag){
this.flag = flag;
}
public R(Boolean flag, Object data) {
this.flag = flag;
this.data = data;
}
public R(Boolean flag, String msg) {
this.flag = flag;
this.msg = msg;
}
public R(String msg) {
this.flag = false;
this.msg = msg;
}
}
现在页面上的消息,有在页面写的,还有在代码上写的,所以需要统一在后端添加:
现在所有的消息统一由后台处理,保存消息前端展示,前端使用res.data.msg使用即可,方便后面进行国际化。
分页功能:
直接在getAll()方法里面改:
点击修改页码,响应换页操作:
某一页只有最后一条数据,删除之后依然在本页面,如何解决?如果要查看页码大于总页码值,重新执行查询操作,使用最大页码值作为当前页码值,修改Controller:
按条件查询:
在执行分页的时候这些条件都得带走:
这几个数据在哪?
这三个数据是跟着分页走的,每次分页查询这三个条件都得带走,所以绑定在分页数据模型中:
修改分页查询操作,
修改Controller接口,但是缺少getPage(currentPage,pageSize, book)接口,所以需要添加
修改service实现类:
对于条件查询,把它作为分页中的一部分数据,当前页码值和size是条件,type和name等也是查询条件。请求的路径参数,直接写参数(对应名称)就能接,如果有对应的实体类,直接封装为实体类。