项目总体实现技术要求如图所示:
一、springboot整合ssmp
1.1、模块创建
手动添加mybatis-plus坐标依赖:
配置端口号:
1.2、实体类快速开发(基于lombok)
假设项目中所用到的数据库信息如下所示:
我们的实体类Book的属性名要保证和数据库的字段名一致,因为我们的实体类属性就是用来封装数据库中的数据信息的,如果名字都不一样的话,数据库中的数据就封装不到实体类的属性中
1.3、数据层开发
我们前面的springboot整合druid笔记讲过,整合druid的时候只需要在yml配置文件中配置druid数据库信息即可,这样就可以让数据层和数据库相连了。
1.3.1、我们以前使用mybatis的时候,数据层的写法如下所示:
:先在yml配置文件中把druid数据源的信息配置出来(相当于连接数据库,配置完后数据层就可以写sql语句对数据库进行操作了)
数据层:
测试结果:
1.3.2、使用mybatis-plus的话有什么用处呢:(作用:简化数据层的sql语句,直接就不用写数据层的增删改查功能方法了)
注1:使用mybatis-plus方式一定不能忘记在yml配置文件中配置属性:
使用mybatis-plus的形式的话,我们需要继承BaseMapper 并且泛型要是封装数据库数据的POJO实体类 * * 使用mybatis-plus的话,在数据层的增删改查功能方法直接就不用写了,BaseMapper对象中已经封装好了, * 我们数据层继承了BaseMapper就说明继承了BaseMapper的增删改查功能了,直接调用增删改查功能方法就行了
使用mybatis-plus方式数据层如下:
BaseMapper对象中已经写好的增删改查方法:
测试程序:(这个增删改查的方法名就要遵守BaseMapper对象中的增删改查的方法名了,因为毕竟我们数据层增删改查的方法是继承的BaseMapper对象的)
但是使用mybatis-plus方式进行保存功能的时候会有一点问题:
问题1:进行保存功能的时候要在yml配置文件中配置id自动增长
报错信息:
我们知道我们数据库中的id本来设置成的是自动增长的,本来调用保存功能时不写id(不写该setId()方法)是不用应该报错的,
我们这个报错如何解决呢:需要我们在yml配置文件中配置一下信息:
配置好yml属性之后,还用上面的测试保存的代码再测试保存功能:(会发现数据就能保存成功了)
问题2:进行查询所有数据功能的时候,要在调用BaseMapper对象中的查询所有功能的方法中随便传入一个参数
1.3.3、开启mybatis-plus运行日志
配置完运行日志后,假设进行查询所有测试:
1.4、分页
我们知道数据层BookMapper接口继承了BaseMapper对象,BaseMapper对象当中封装好了增删改查,分页查询等等所有的功能方法,不例外的是当然也封装好了分页查询的功能方法
其实分页查询的功能实现就三个步骤:
第一步:new Page对象把想要查询的页数和每页展示的数据量传递进去
第二步:调用数据层的分页查询功能方法,把第一步的对象传递进去,并且第二个参数任意设定(一般都设定为null)
第三步:设定分页查询拦截器(要不设定的话,尽管调用的是分页查询的功能,但是却拿到的是数据库中的全部数据信息)
分页查询功能程序测试如下:
结果:
因此:思考为什么我们明明测试的是分页查询的功能,却还是把数据库中的所有数据都展示给我们了呢,那么我们到底如何才能获取到分页查询出来的数据呢:
解决方法:就需要我们设定一个分页查询的拦截器(配置完之后,就可以拿到分页查询的数据了,而不是调用分页查询功能的时候把所有的数据库数据都拿到)
/**
* 分页查询拦截器类 (该拦截器的写法其实是固定的)
*
*/
@Configuration // 加载器注解 作用:使用该注解后,当运行springboot类的时候会自动加载到该拦截器类
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加个分页查询拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
// 添加完后返回即可
return interceptor;
}
}
当配置好分页查询拦截器后,我们测试程序再次调用数据层分页查询功能,看是否能拿到第一页的五条数据:
补充能量:使用Page对象调用某些方法可以拿到我们获取到的数据的某些信息:
1.5、条件查询 (like)
补充能量:基本上条件构造器有很多条件方法(做项目的时候首先要想到条件查询的这些方法):
也就是说where后面的都是条件查询了,只要客户端访问的一个功能sql需要用到where后面的判断,都需要写一个条件构造器,然后调用一些方法(如是模糊查询、升序降序条件查询、=等于号条件查询等。)
* 条件查询的功能实现步骤: * 第一步:new QueryWrapper对象 * 第二步:用该对象调用like属性(第一个参数对应的是数据库的字段名,第二个参数对应的是对该字段名模糊查询的数据) * 第三步:调用数据层的条件查询功能
问题1:有时候我们有可能会把like属性中的第一个参数(对应的是数据库的字段名)写错,那么我们的sql语句肯定就错误了,数据当然也是拿不出来了:
因此我们有什么方法能够防止第一个参数写的数据库字段名出错呢:
解决方法: 用Lambda对象方式(一定别忘记new对象后泛型要加上实体类)
这里的条件查询的“会计”,也可以不写成静态的,可以写成动态的:
问题2:我们知道like属性后的第二个参数是来自前端条件查询的数据,但是我们想保证前端查询的数据不为null,为null的话就不让他进行条件查询了:
因此为null的时候,我们不想让他进行条件查询 (where xxxx like xxxx),我们该怎么做呢:
1.6、业务层开发
注意:数据层我们就用的是mybatis-plus帮我们封装好到BaseMapper对象当中的增删改查等功能代码,因此我们业务层调用数据层的增删改查等功能方法时,要遵守BaseMapper对象当中的增删改查等功能的方法名
其实业务层开发的方法名和数据层开发的方法名还是有一点区别的(以前我们都是说业务层的代码直接复制数据层的代码就行了,再写点其他的业务逻辑就完成了)
业务层代码:
BookService:
package com.Bivin.service;
import com.Bivin.pojo.Book;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 业务层接口
*/
public interface BookService {
/**
* 增加功能
*
* 数据保存成功的话返回给用户boolean类型
*/
boolean save(Book book);
/**
* 删除功能
*
* 数据删除成功的话返回给用户boolean类型
*/
boolean delete(Integer id);
/**
* 改功能
*
* 数据修改成功的话返回给用户boolean类型
*/
boolean update(Book book);
/**
* 查询功能
*
* 数据查询成功的话返回给用户List集合,把所有的数据以List集合的形式返回给前端用户
*/
List<Book> selectAll();
/**
* 通过id查询功能
*
* 数据查询成功的话返回给用户Book类型,把单条数据封装到Book对象属性中返回给前端用户
*/
Book selectById(Integer id);
/**
* 分页查询功能
*
* int currentPage : 对应的是第几页
* int pageSize : 对应的是每页查询的数据个数
*/
IPage<Book> getByPage(int currentPage,int pageSize);
}
实现接口类:
注意:bean注解放实现接口类当中,要不然扫描不到bean对象
package com.Bivin.service.impl;
import com.Bivin.mapper.BookMapper;
import com.Bivin.pojo.Book;
import com.Bivin.service.BookService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 业务层
*/
@Service // bean 注解 注意:该bean注解不要写在接口上,因为接口不是对象2
public class BookServiceImpl implements BookService {
/**
* 添加业务层和数据层之间的依赖关系(使用自动装配获取到数据层对象,然后在业务层的增删改查方法中调用数据层的增删改查方法)
*/
@Autowired
private BookMapper bookMapper;
// 增
@Override
public boolean save(Book book) {
// 调用数据层的增加功能
return bookMapper.insert(book) >0;
// 注意:数据层的增删改查功能返回的都是int类型:int insert(T entity); 表示的是受影响的行数
// 每保存一条数据,受影响的行数就是1
// 我们这里保存成功的话返回给用户的是 return bookMapper.insert(book) >0; 表示return 1 > 0; 为true 所以就可以返回boolean类型了
// bookMapper.insert(book) 受影响的行数为1
}
// 删
@Override
public boolean delete(Integer id) {
// 调用数据层的删除功能
return bookMapper.deleteById(id) >0;
}
// 改
@Override
public boolean update(Book book) {
// 调用数据层的修改功能
return bookMapper.updateById(book) >0;
}
// 查
@Override
public List<Book> selectAll() {
// 调用数据层的查询功能
return bookMapper.selectList(null);
}
// 通过id查单条数据
@Override
public Book selectById(Integer id) {
// 调用数据层通过id查单条数据功能
return bookMapper.selectById(id);
}
// 分页查询功能
/** // 分页查询功能
*
* int currentPage : 对应的是第几页
* int pageSize : 对应的是每页查询的数据个数
*
* 注:使用mybatis-plus方式的话,因为人家封装好的分页查询功能返回的类型是IPage<实体类>类型,
* 因此我们的分页查询功能的方法返回类型设置成IPage<>形式
*
*
*/
@Override
public IPage<Book> getByPage(int currentPage, int pageSize) {
// 调用数据层的分页查询功能
IPage<Book> page = new Page<>(currentPage,pageSize);
bookMapper.selectPage(page,null);// 把对应的页数和展示数据数传入数据层分页查询功能
return page;
}
}
程序测试:
补充:其实业务层也可以用mybatis-plus进行简化,直接业务层的增删改查方法都不用写了,用到的时候再说把
1.7、表现层标准开发
一定要注意分页查询功能是怎么写的,包括用REST风格是怎么访问路径,并且那当前页码,和当前页展示的数据数的。还有做分页查询逻辑的时候,我们前面讲过要做一个分页查询拦截器,要不然就会查出所有的数据
package com.Bivin.controller;
import com.Bivin.pojo.Book;
import com.Bivin.service.impl.BookServiceImpl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 表现层业务 -- 基于REST风格
*/
@RestController // @Controller和@ResponseBody注解 (表现层bean注解和响应前端数据注解)
@RequestMapping(value = "/books") // 访问路径
public class BookController {
/**
* 表现层和业务层之间的依赖关系
* 通过自动装配获取到业务层对象,然后调用业务层的增删改查等功能
*/
@Autowired
private BookServiceImpl bookService; // 这里注入接口或者实现接口类都可以(习惯注入实现接口类,更好理解)
/**
* 1、查询所有功能访问路径
*/
@GetMapping // REST风格简化method行为(忘记的话看笔记)
public List<Book> selectAll(){
// 调用业务层查询所有功能
List<Book> books = bookService.selectAll();
return books; // 响应给前端数据 (直接return即可)
}
/**
* 2、添加功能
*/
@PostMapping
public boolean save(@RequestBody Book book){ // @RequestBody注解: 接收的是json格式数据 (忘记的话看笔记)
// 调用业务层添加功能
return bookService.save(book);
}
/**
* 3、修改功能
*/
@PutMapping
public boolean update(@RequestBody Book book){
// 调用业务层修改功能
return bookService.update(book);
}
/**
* 4、删除功能 (通过id删除数据)
*/
@DeleteMapping(value = "/{id}")
public boolean delete(@PathVariable Integer id){
// 调用业务层的删除功能
return bookService.delete(id);
}
/**
* 5、通过id查询单条数据功能
*/
@GetMapping(value = "/{id}")
public Book selectById(@PathVariable Integer id){
// 调用业务层的通过id查询单条数据功能
return bookService.selectById(id);
}
/**
* 6、分页查询功能
*
* int currentPage:代表前端想要查询第几页的数据
* int pageSize : 代表该页展示的数据数 (比如:第五页,该页展示5条数据)
*
*
*/
@GetMapping(value = "/{currentPage}/{pageSize}")
public IPage<Book> getByPage(@PathVariable int currentPage, @PathVariable int pageSize){
// 调用业务层分页查询功能
return bookService.getByPage(currentPage,pageSize);
}
}
开启服务器后,前端访问分页查询功能的格式是如下所示格式:
通过日志我们也可以看出确实拿到了数据库中的第一页中的五条数据了:
1.8、把表现层响应给前端的true/false做一些标识码返回给前端用户
我们知道,客户端通过访问表现层BookController的增删改查的方法,
* 最终调用完增删改查方法拿到结果是我们返回的true或者false、要么就是查询出来的数据
*
* 前端人就开始找事了:说你返回给我的true到底是修改功能返回的true还是增加功能返回的true啊
* 所以为了解决事B,我们用一个类专门封装查询出来的数据,并且标记好状态码(比如1:表示成功,2:表示失败)
*
* 注意:该类的写法在实际开发中,是前后端商量出来的协议,就是说前后端程序员商量好后端的数据以什么样的格式返回给前端
Result类:
package com.Bivin.controller;
import lombok.Data;
/**
* 分析该类的作用:
*
* 我们知道,客户端通过访问表现层BookController的增删改查的方法,
* 最终调用完增删改查方法拿到结果是我们返回的true或者false、要么就是查询出来的数据
*
* 前端人就开始找事了:说你返回给我的true到底是修改功能返回的true还是增加功能返回的true啊
* 所以为了解决事B,我们用一个类专门封装查询出来的数据,并且标记好状态码(比如1:表示成功,2:表示失败)
*
* 注意:该类的写法在实际开发中,是前后端商量出来的协议,就是说前后端程序员商量好后端的数据以什么样的格式返回给前端
**/
@Data // lombok快速开发实体类( 自动帮我们写好setter getter toString方法了 )
public class Result {
private Object data; // 封装结果数据 (用Object万能,返回给前端数据的时候,可以把表现层获取到的所有类型的数据,如Book类型、查询所有功能返回的List集合类型的数据,都可以封装到Object类型的属性当中,然后返回响应给前端人员)
private Integer code; // 标记状态码(比如1:表示成功,2:表示失败)
private String msg; // 消息(比如说:调用增删改查功能失败了,返回给前端一句话让前端知道失败了)
// 把构造方法的几种情况都写出来(因为表现层响应给前端数据的时候,不一定这三个属性都用得到,有可能就用两个参数的构造方法,有可能用三个参数的构造方法)
public Result(Object data, Integer code) {
this.data = data;
this.code = code;
}
public Result(Object data, Integer code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
public Result(Integer code) {
this.code = code;
}
}
Code类:
package com.Bivin.controller;
/**
* 分析该类的作用:
* 标记状态码(比如1:表示成功,2:表示失败)的类,( Result里面的 )
*
* 注意:这些状态码不是固定的,是实际开发中前端程序员和后端程序员商量好规定的一种状态信息,
* 通过这些商量的状态码,就可以知道拿没拿到数据了
*
*/
public class Code {
/**
* 调用增删改查功能成功的状态码标记
*
* 注:因为是static静态修饰的,所以可以直接用类名Code调用属性名
* public修饰:代表公共的(也就是说,任何包下的只要通过类名. 的方式都可以调取到Code类下的属性值)
*
*/
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer SELECT_OK = 20041;
public static final Integer SELECTById_OK = 20051; // 通过id查询成功标识码
public static final Integer SELECTPage_OK = 20061; // 分页查询成功标识码
/**
* 调用增删改查功能失败的状态码标记
*/
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer SELECT_ERR = 20040;
public static final Integer SELECTById_ERR = 20050; // 通过id查询失败标识码
public static final Integer SELECTPage_ERR = 20060; // 分页查询失败标识码
}
表现层:
package com.Bivin.controller;
import com.Bivin.pojo.Book;
import com.Bivin.service.impl.BookServiceImpl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 表现层业务 -- 基于REST风格
*/
@RestController // @Controller和@ResponseBody注解 (表现层bean注解和响应前端数据注解)
@RequestMapping(value = "/books") // 访问路径
public class BookController {
/**
* 表现层和业务层之间的依赖关系
* 通过自动装配获取到业务层对象,然后调用业务层的增删改查等功能
*/
@Autowired
private BookServiceImpl bookService; // 这里注入接口或者实现接口类都可以(习惯注入实现接口类,更好理解)
/**
* 1、查询所有功能访问路径
*/
@GetMapping // REST风格简化method行为(忘记的话看笔记)
public Result selectAll(){
// 调用业务层查询所有功能
List<Book> books = bookService.selectAll();
/**
* 直接判断调用查询所有功能查询出来的是否有数据,有的话封装到Result对象属性中返回给前端数据和成功标识码,还可以再返回一点信息
*/
// 如果查询出来的数据不为null,就把Code.SELECT_OK查询成功标识码赋给integer变量,然后通过Result对象响应给前端,为null的话就是Code.SELECT_ERR查询失败标识码
Integer integer = books != null ? Code.SELECT_OK : Code.SELECT_ERR; // books不为null说明查询出来数据了
return new Result(books,integer); // 把查询出来的数据和上面的成功标识码封装到Result对象属性中响应返回给前端人员
}
/**
* 2、添加功能
*/
@PostMapping
public Result save(@RequestBody Book book){ // @RequestBody注解: 接收的是json格式数据 (忘记的话看笔记)
// 调用业务层添加功能
boolean b = bookService.save(book);
return new Result(b ? Code.SAVE_OK : Code.SAVE_ERR); // 添加如果成功的话(b为true)响应给前端 Code.SAVE_OK 保存成功标识码即可,否则后者
}
/**
* 3、修改功能
*/
@PutMapping
public Result update(@RequestBody Book book){
// 调用业务层修改功能
boolean b = bookService.update(book);
return new Result(b ? Code.UPDATE_OK : Code.UPDATE_ERR);
}
/**
* 4、删除功能 (通过id删除数据)
*/
@DeleteMapping(value = "/{id}")
public Result delete(@PathVariable Integer id){
// 调用业务层的删除功能
boolean b = bookService.delete(id);
return new Result(b ? Code.DELETE_OK : Code.DELETE_ERR);
}
/**
* 5、通过id查询单条数据功能
*/
@GetMapping(value = "/{id}")
public Result selectById(@PathVariable Integer id){
// 调用业务层的通过id查询单条数据功能
Book book = bookService.selectById(id);
Integer integer = book != null ? Code.SELECTById_OK : Code.SELECTById_ERR; // 判断查询的是否为null,不为null的话拿到查询成功状态码
String s = book != null ? "查询成功~" : "查询失败~"; // 还可以做一些信息
return new Result(book,integer,s);
}
/**
* 6、分页查询功能
*
* int currentPage:代表前端想要查询第几页的数据
* int pageSize : 代表该页展示的数据数 (比如:第五页,该页展示5条数据)
*
*
*/
@GetMapping(value = "/{currentPage}/{pageSize}")
public Result getByPage(@PathVariable int currentPage, @PathVariable int pageSize){
// 调用业务层分页查询功能
IPage<Book> page = bookService.getByPage(currentPage, pageSize);
Integer integer = page != null ? Code.SELECTPage_OK : Code.SELECTPage_ERR;
String s = page != null ? "分页查询成功~" : "分页查询失败~";
return new Result(page,integer,s);
}
}
开启服务器后,假设前端访问查询所有的功能:
1.9、项目中的异常处理
我们在前面ssm异常笔记中讲过(忘记的话看前面笔记),项目中的异常分为业务异常、系统异常、其他异常。
总的来说就是当客户端访问数据资源的时候出现的一些异常情况,比如下面用户在访问查询所有功能的时候,出现了异常的情况(我们假定在查询所有功能中模拟了异常的存在),也就是说出现异常情况后客户调用该查询所有功能的时候拿不到数据,而是拿到了一些异常出错的信息比如500等,那么客户就说了我tm的访问的这明明是查询所有的功能,这尼玛给我返回的都是什么玩意
代码演示如下所示:
前端访问该查询所有功能:(前端看到返回的信息后,直接气的骂街,前端说我们本来商量好的通过标识码返回给我信息和数据的,成功的话就把数据和成功标识码发给我,失败的话就把失败标识码发个我,这尼玛发给我的什么东西,啥也看不懂,也不是我们商量好的成功/失败的状态码)
因此我们后端需要把一些异常给处理了,毕竟异常大多数都是出于我们后端项目中的,我们总不能明明和前端都商量好了格式:访问资源成功的话就响应给前端相对应的数据和成功的标识码,访问资源失败的话就响应给前端相对应的提示失败信息和失败的标识码,而我们总不能不处理异常当前端访问资源有异常的时候,响应给前端的格式前端人压根都不知道是什么了,因此我们需要把异常处理掉,并且项目中如果真的有异常的情况下,也要把异常的信息通过前端和后端本来商量好的通过标识码的格式响应给前端,毕竟商量过,前端能知道这是怎么回事了(还是需要用到Result封装返回结果的类,和Code :标识状态码的类)
下面演示的是其他异常的处理方式:
思考:我们后端该怎么处理上面这个异常呢:
第一步:写异常处理器 (忘记的话看前面异常的笔记)
注1:因为不管是数据层出现的异常还是业务层出现的异常最终都会上抛到表现层中,所以异常处理器基本上都写在表现层中
处理完异常后前端在访问查询所有功能的时候(此功能中有异常的存在):
补充能量:再理解:
上面我们处理的异常类型是其他异常,我们知道还有两个异常种类:业务异常和系统异常,
这两个异常的处理方式其实就是写个业务异常类或者系统异常类,把异常的信息封装到业务异常类属性中或者系统异常类属性中,然后再通过异常处理器的拦截后返回响应给前端业务异常类或者系统异常类属性中封装的那些异常数据即可,其实三种处理方式都是一样的,只不过业务异常和系统异常有个专门的类用来做桥梁
2.0、条件查询(重)
表现层代码逻辑:
业务层的代码逻辑:
数据的访问方式:
2.1、前端页面调用
注:前端代码不再展示