目录
模块创建
实体类快速开发(lombok)
数据层标准开发(基础CRUD)
分页
数据层标准开发(条件查询)
业务层标准开发(基础CRUD)
业务层快速开发(基于MP构建)
表现层标准开发
表现层数据一致性处理
异常消息处理
现在的企业级开发通常是使用手机或者浏览器去访问前端服务器,然后再由前端服务器去访问后端服务器,当然可以把后端服务器做成各种各样的微服务,这样有前端服务器在访问后端的时候,它是在不同的服务器上做对应的信息处理,得到最终的数据结果,再交给前端服务器展示给用户看。当前案例不做前后端分离,只是看看基于springboot整合的过程。
导入依赖:
com.baomidou
mybatis-plus-boot-starter
3.4.2
com.alibaba
druid-spring-boot-starter
1.2.6
Lombok, 一个Java类库,提供了一组注解,简化P0J0实体类开发。使用lombok先导入坐标。
org.projectlombok
lombok
创建实体类Book,在类名上面添加@Data注解,就会自动帮助我们添加getter,setter,toString,HashCode等方法,但是没有生成构造方法,但是构造方法用相应的注解@AllArgsConstructor @NoArgsConstructor(见名知意)
package com.zqf.domain;
import lombok.*;
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
编写yml配置文件:
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
username: root
password: 123456
server:
port: 80
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
创建Dao接口:
不用MP版
package com.zqf.domain.dao;
import com.zqf.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}")
Book getById(Integer id);
}
测试类:
package com.zqf.dao;
import com.zqf.domain.dao.BookDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookDaoTestCase {
@Autowired
private BookDao bookdao;
@Test
void contextLoads() {
System.out.println(bookdao.getById(1));
}
}
使用MP版
Dao接口
package com.zqf.domain.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zqf.domain.Book;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BookDao extends BaseMapper {
}
测试类:
package com.zqf.dao;
import com.zqf.domain.Book;
import com.zqf.domain.dao.BookDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookDaoTestCase {
@Autowired
private BookDao bookdao;
@Test
void testGetById() {
System.out.println(bookdao.selectById(1));
}
@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(17);
book.setType("测试数据abcdefd");
book.setName("测试数据123");
book.setDescription("测试数据123" );
bookdao.updateById(book);
}
@Test
void testDel(){
bookdao.deleteById(10);
}
@Test
void testGetAll(){
bookdao.selectList(null);
}
@Test
void testGetPage(){
}
@Test
void testGetBy(){
}
}
运行insert的方法会出现一个小问题,不能够设置属性ID,要给ID值设置为另一串数字,参数类型匹配失败:
Could not set property 'id' of 'class com.zqf.domain.Book' with value '1550444022898720770' Cause: java.lang.IllegalArgumentException: argument type mismatch
MP默认生成ID的策略是由自己指定的,用的是雪花算法,那这个地方我们要用的不是雪花算法,我们想要的是数据库里面的ID自增策略,也就是说使用数据库的ID及控制策略来做,在配置文件李设置自增策略即可。
开启MP运行日志
configuration: #输出到控制台 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
对于分页来讲最起码有两条数据,第一个是加载哪一页的数据,第二个是加载几条数据。要想使用分页必须使用MP提供的拦截器制作,在配置类里定义拦截器。
注意,不管做什么,现在做的,其实整体上还是spring程序,你做的所有的东西都得受spring管理,而spring是用来管bean的,所以在这里我们就要使用spring管理第三方bean的方式来把这个bean初始化出来,并且加载给spring的环境。
第一步,加上@Configuration,这样这个类里面的配置信息回头可以被读取到;第二步我们去做这个bean,这个第三方bean的有一个专用的名称,叫做MybatisPlusInterceptor,在这个方法里做一件事儿,就是把拦截器创建出来,并return出去就行了,然后使用addInnerInterceptor方法来添加拦截器,这个拦截器是用来做分页相关功能的,需要在参数列表中添加new PaginationInnerInterceptor()方法。
@Configuration
public class MPconfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
其实这段代码就做了一件事,相当于交给spring管理了一个bean,这个bean是mp的拦截器。而这个拦截器里面有一个分页的具体拦截器,之后还需要添加不同的拦截器。
测试类
@Test
void testGetPage(){
IPage page = new Page(1,5);
bookdao.selectPage(null,null);
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的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现。
前端:
js:
var vue = new Vue({
el: '#app',
data:{
dataList: [],//当前页要展示的列表数据
dialogFormVisible: false,//添加表单是否可见
dialogFormVisible4Edit:false,//编辑表单是否可见
formData: {},//表单数据
rules: {//校验规则
type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
},
pagination: {//分页相关模型数据ss
currentPage: 1,//当前页码
pageSize:10,//每页显示的记录数
total:0,//总记录数
type: "",
name: "",
description: ""
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
//调用查询全部数据的操作
this.getAll();
},
methods: {
//列表
// getAll() {
// //发送异步请求
// axios.get("/books").then((res)=>{
// // console.log(res.data);
// this.dataList = res.data.data;
// });
// },
//分页查询
getAll() {
//组织参数,拼接url请求地址
// console.log(this.pagination.type);
param = "?type="+this.pagination.type;
param +="&name="+this.pagination.name;
param +="&description="+this.pagination.description;
// console.log(param);
//发送异步请求
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res)=>{
this.pagination.pageSize = res.data.data.size;
this.pagination.currentPage = res.data.data.current;
this.pagination.total = res.data.data.total;
this.dataList = res.data.data.records;
});
},
//切换页码
handleCurrentChange(currentPage) {
//修改页码值为当前选中的页码值
this.pagination.currentPage = currentPage;
//执行查询
this.getAll();
}
QueryWrapper就是那个查询条件。需要创建一个QueryWrapper对象泛型里面传入实体类类型。将这个对象放在参数列表当中,用这个对象中的内置方法来添加条件。也就是将所有的查询操作都换成了API操作。
@Test
void testGetBy(){
QueryWrapper qw = new QueryWrapper<>();
qw.like("name","Spring");
bookdao.selectList(qw);
}
如果传过来的参数为null的处理方法,如果为null则不进行like连接,如果不为null则连接。
小tip:
1.创建业务层接口
package com.zqf.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zqf.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 getA1l();
IPage getPage(int currentPage,int pageSize);
}
通过Dao创建业务实现类
package com.zqf.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zqf.dao.BookDao;
import com.zqf.domain.Book;
import com.zqf.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@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 getA1l() {
return bookDao.selectList(null);
}
@Override
public IPage getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
}
}
编写测试类
package com.zqf.service;
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.zqf.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookServiceTestCase {
@Autowired
private BookService bookService;
@Test
void testGetById(){
System.out.println(bookService.getById(4));
}
@Test
void testSave(){
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("测试数据abcdefd");
book.setName("测试数据123");
book.setDescription("测试数据123" );
bookService.update(book);
}
@Test
void testDel(){
bookService.delete(10);
}
@Test
void testGetAll(){
bookService.getA1l();
}
@Test
void testGetPage(){
IPage page = bookService.getPage(2, 5);
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()); //数据
}
}
1.Service接口名称定义成业务名称,并与Dao接口名称进行区分。
2.为了保证业务功能正确,一定要写业务测试用例测试Service功能是否有效。
创建业务层接口
package com.zqf.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zqf.domain.Book;
public interface IBookService extends IService {
}
IService
接口有诸多业务方法,注意该接口的业务方法与Dao中BaseMapper 中的方法名称有较大出入。
创建业务层接口的实现类
package com.zqf.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zqf.dao.BookDao;
import com.zqf.domain.Book;
import com.zqf.service.IBookService;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl extends ServiceImpl implements IBookService {
}
不可能全部实现IService
接口中的诸多业务方法,让该业务实现类继承ServiceImpl ,他有两个泛型,第一个泛型是你用的实现类,第二个泛型是你用的模型类。
测试类:
package com.zqf.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zqf.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookServiceTest {
@Autowired
private IBookService bookService;
@Test
void testGetById(){
System.out.println(bookService.getById(4));
}
@Test
void testSave(){
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("测试数据abcdefd");
book.setName("测试数据123");
book.setDescription("测试数据123" );
bookService.updateById(book);
}
@Test
void testDel(){
bookService.removeById(10);
}
@Test
void testGetAll(){
bookService.list();
}
@Test
void testGetPage(){
IPage page = new Page(2,5);
bookService.page(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()); //数据
}
}
对于我们现在的业务层接口和实现类都是用它提供的通用功能来做的,但是有些时候不一定是这样,比如说现在做的这个Book模块中,假如说有一个时间要求,MP提供好的功能中,可没有处理这些的功能,此时就要求自己手工去编辑创建新的接口方法,注意新的接口方法名称不要和MP自带的业务方法重名(可在方法上添加@Override验证)。自己定义的方法需要自己在实现类当中定义实现,在实现类当中作Dao调用,具体调用的方法和之前相同。
自定义接口:
package com.zqf.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zqf.domain.Book;
public interface IBookService extends IService {
boolean saveBook(Book book);
boolean modify(Book book);
boolean delete(Integer id) ;
}
接口实现类:
package com.zqf.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zqf.dao.BookDao;
import com.zqf.domain.Book;
import com.zqf.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BookServiceImpl extends ServiceImpl implements IBookService {
@Autowired
private BookDao bookDao;
@Override
public boolean saveBook(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public boolean modify(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
}
基于Restful进行表现层接口开发,使用Postman测试表现层接口功能
创建Controller
package com.zqf.controller;
import com.zqf.domain.Book;
import com.zqf.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 bookService;
@GetMapping
public List getAll(){
return bookService.list();
}
@PostMapping //我们用异步提交发送的话,它的参数通过请求体传json数据过来
public Boolean save(@RequestBody Book book){
return bookService.save(book);
}
@PutMapping
public Boolean update(@RequestBody Book book){
return bookService.modify(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);
}
@GetMapping("{currentPage}/{pageSize}")
public IPage getPage(@PathVariable int currentPage,int pageSize){
return bookService.getPage(currentPage,pageSize);
}
}
使用APIPost作测试
对于表现层的Controller返回的数据如下所示:
flag代表查询结果成功还是失败,data代表传输的数据
通过设计统一的返回值结果类型便于前端开发读取数据,读取flag就知道操作成功与否,如果操作成功了在读取一下data就拿到数据了。
设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议。
创建R类
package com.zqf.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;
}
}
创建Controller
package com.zqf.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zqf.controller.utils.R;
import com.zqf.domain.Book;
import com.zqf.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 bookService;
@GetMapping
public R getAll(){
return new R(true,bookService.list());
}
@PostMapping //我们用异步提交发送的话,它的参数通过请求体传json数据过来
public R save(@RequestBody Book book){
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));
}
}
注意:增删改的返回结果是true/false,查询的返回结果是查询的数据,所以在创建R对象传入参数的时候如果是查询操作,还需要传入flag。
假设在getAll方法中遇到一个异常
那么在请求的时候服务器会给前端传送一个异常数据(全新的JSON数据格式)
在业务在进行处理的时候,正常的返回true返回false,这些和前端都已经沟通过了,数据格式就是这样,但是一旦后台代码出了异常,就会发现前端就会拿到一组上面的信息,至于这个信息里边包含什么,目前并不重要,重要的是前端拿到它以后该怎么处理。
尽管代码出异常了,我们也要保证出异常以后返回给前端的数据格式,仍然保持原来的状态,这个时候就提出了一个全新的需求,叫做对所有的异常进行统一的格式处理,而且还要把它处理成和原来一样的格式。
在SpringMvc当中提供了一个专用的异常处理器,通过这个异常处理器就可以把所有的异常给处理掉,SpringMvc属于controller的技术,所以把异常处理器在controller的utils下面。作为异常处理器,它具有什么样的特征呢?要给他定义他是controller的异常处理器,@RestControllerAdvice这个注解点开之后可以看到它实际上还是一个bean。
这个类定义一个方法,需要带一个参数,参数就是要处理的异常种类,写上这个方法以后就用这个方法来处理所有的异常,这里还需要加一个注解@ExceptionHandler,这样所得异常都会进到这个方法里面去,ex就是拦截到的异常对象,最终需要return一个R对象,这样传输的数据格式就统一了。
在R的参数里应该告诉用户一个合理的信息,比如说返回回去以后一定是失败的错误,然后数据肯定是空,除此之外还应该告诉页面的信息,比如说“服务器故障,请稍后再试”。我们这个R已经无法满足我们的需求,因为我们想向前台传递一个消息,那怎么办?没有怎么办,说过了这个R可以随便定义,你缺什么就加什么就好。
修改R:
package com.zqf.controller.utils;
import lombok.Data;
@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;
}
}
异常拦截器:
package com.zqf.controller.utils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有异常信息
@ExceptionHandler
public R doException(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new R(false,"服务器故障,请稍后再试");
}
}
对于这样的一个注解(@ExceptionHandler),那么实际上后面可以书写具体拦截什么样的异常,可以在这儿给他定义出来(@ExceptionHandler(Exception.class)),如果有一些特殊的异常要分门别类的处理的话,可以在那儿多写几个不同的异常做不同的处理就行了。