SSMP整合案例
一个模块的增删改查
模块创建
- 勾选SpringMVC和Mysql坐标
- 修改配置文件为yml格式
- 设置端口为80方便访问
实体类快速开发(lombok)
-
工具lombok,一个java类库,提供了一组注解,简化pojo实体类开发
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
常用注解:@Data
@Data //@NoArgsConstructor//无参构造方法 public class Book { private Integer id; private String type; private String name; private String description; }
为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等
数据层标准开发
手工导入starter坐标2个
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
MybatisPlus和Druid相关配置
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db
username: root
password: 123456
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
开发Dao接口(继承BaseMapper)
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
测试
@SpringBootTest
class BookDaoTest {
@Autowired
private BookDao bookDao;
@Test
void getById() {
bookDao.selectById(1);
}
@Test
void save() {
Book book = new Book();
book.setType("测试数据1");
book.setName("测试数据1");
book.setDescription("测试数据1");
bookDao.insert(book);
}
@Test
void update() {
Book book = new Book();
book.setId(2);
book.setType("测试数据2");
book.setName("测试数据2");
book.setDescription("测试数据2");
bookDao.updateById(book);
}
@Test
void delete() {
bookDao.deleteById(3);
}
@Test
void getAll() {
bookDao.selectList(null);
}
}
开启MyBatisPlus日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
分页
@Test
void testGetpage() {
//current 当前页
IPage page = new Page(1,5);
bookDao.selectPage(page,null);
System.out.println(page.getCurrent());
System.out.println(page.getPages());
System.out.println(page.getRecords());
System.out.println(page.getSize());
}
-
IPage对象中封装了分页操作中的所有数据
-
数据
-
当前页码值
-
每页数据总量
-
最大页码值
-
数据总量
-
MyBatisPlus拦截器,实现动态拼写SQL语句
@Configuration
public class MPConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
固定格式
条件查询功能
- 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
@Test
void GetBy(){
//select*from tbl_book where(name LIKE ?)
QueryWrapper<Book> qw = new QueryWrapper<>();
qw.like("name","java");
bookDao.selectList(qw);
}
//安全性更强:
@Test
void GetBy2(){
String name = "java";
//select*from tbl_book where(name LIKE ?)
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
// if (name!=null) lqw.like(Book::getName,name);
lqw.like(name!=null,Book::getName,name);
bookDao.selectList(lqw);
}
- 带分页的条件查询,支持动态拼写查询条件
@Test
void testGetByCondition(){
String name = "java";
IPage page=new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(name),Book::getName,name);
bookDao.selectPage(page,lqw);
}
业务层开发
Service层接口定义与数据层接口定义具有较大区别,不要混用
-
selectByUserNameAndPassword (String username,stringpassword);
数据层对应的名称
-
login(String username,String password );
业务层对应的名称
-
接口
public interface BookService {
Boolean save(Book book);
Boolean update(Book book);
Boolean delete(Integer id);
Book getById(Integer id);
List<Book> selectAll();
IPage<Book> getPage(int currentPage,int pageSize);
}
-
实现类
@Service public class BookServiceImpl implements BookService { @Autowired 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<Book> selectAll() { return bookDao.selectList(null); } @Override public IPage<Book> getPage(int currentPage, int pageSize) { IPage page = new Page(currentPage,pageSize); return bookDao.selectPage(page,null); } }
-
测试
@SpringBootTest
class BookServiceImplTest {
@Autowired
private BookService bookService;
@Test
void getById() {
System.out.println(bookService.getById(2));
}
void save() {
Book book = new Book();
book.setType("测试数据1");
book.setName("测试数据1");
book.setDescription("测试数据1");
bookService.save(book);
}
@Test
void update() {
Book book = new Book();
book.setId(2);
book.setType("测试数据2");
book.setName("测试数据2");
book.setDescription("测试数据2");
bookService.update(book);
}
@Test
void delete() {
bookService.delete(3);
}
@Test
void getAll() {
bookService.selectAll();
}
@Test
void testGetPage(){
IPage<Book> page = bookService.getPage(2,5);
System.out.println(page.getCurrent());
System.out.println(page.getPages());
}
}
业务层快速开发
- 快速开发方案
- 使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImpl<N,T>)
- 在通用类基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
public interface IBookService extends IService<Book> {
// @Override
// 当想要添加已有功能以外的业务时,如果担心写的方法名跟已有的重名
// 就添加@Override,如果报错则没有重名
boolean savaBook1(Book book);
}
@Service
//ServiceImpl<?, ?> 第一个是引用的实现类,第二个是对应的实体类
public class IBookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
@Autowired
private BookDao bookDao;
@Override
public boolean savaBook1(Book book) {
return bookDao.insert(book)>0;
}
}
表现层标准开发
- 基于Restful进行表现层接口开发
- 使用Postman测试表现层接口功能
@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.updateById(book);
}
//路径传参
@DeleteMapping("{id}")
public Boolean delete(@PathVariable Integer id){
return iBookService.removeById(id);
}
//http://localhost/books/2
@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);
}
}
- 查询Get(路径传参@PathVarriable)
- 修改Put(请求体传参@RequestBody)
- 新增Post(请求体传参@RequestBody)
- 删除Delete(路径传参@PathVarriable)
消息一致性处理
- 格式A:true/false
- 格式B:json
- 格式C:json数组
统一格式:
用data来封装数据
为避免null不清楚是数据null还是抛出异常null 使用flag来代表查询结果的成功或失败
-
设计表现层返回结果的模型类,用于前后端进行数据格式统一,即前后端数据协议
@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; } }
-
修改表现层,返回值改为R
@RestController @RequestMapping("/books") public class BookController2 { @Autowired private IBookService iBookService; @GetMapping public List<Book> getAll(){ return iBookService.list(); } //请求体传json数据 @PostMapping public R save(@RequestBody Book book){ // R r = new R(); // boolean flag = iBookService.save(book); // r.setFlag(flag); return new R(iBookService.save(book)); } @PutMapping public R update(@RequestBody Book book){ return new R(iBookService.updateById(book)); } //路径传参 @DeleteMapping("{id}") public R delete(@PathVariable Integer id){ return new R(iBookService.removeById(id)); } //http://localhost/books/2 @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)); } }
数据统一为Json格式:
前后端协议联调
- 前后端分离结构设计中页面归属前端服务器
- 单体工程中页面放置在resource目录下的static目录中(建议执行clean)
单体项目中页面放置在resources/static目录下
created钩子函数用于初始化页面时发起调用
页面使用axios发送异步请求获取数据后确认后端是否联通
//钩子函数,VUE对象初始化完成后自动执行
created() {
//调用查询全部数据
this.getAll();
},
methods: {
//列表
getAll() {
//发送异步请求
axios.get("http://localhost:80/books ").then((res)=>{
console.log(res.data);
});
},
//由于idea默认端口是63442端口,而服务器是80端口,导致无法直接访问,需要跨域 @CrossOrigin("http://localhost:63342")
将查询数据返回到页面,利用前端数据双向绑定进行数据展示
getAll() {
//发送异步请求
axios.get("http://localhost:80/books ").then((res)=>{
// console.log(res.data);
this.dataList=res.data.data;
// console.log(this.dataList);
});
},
新增操作
点击新增后打开弹窗
//弹出添加窗口
handleCreate() {
this.dialogFormVisible=true;
this.resetForm();
},
//重置表单
resetForm() {
this.formDate={};
},
//添加
handleAdd () {
axios.post("http://localhost:80/books",this.formData).then((res)=>{
//判断当前操作是否成功
if(res.data.flag){
this.dialogFormVisible=false;
this.$message.success("添加成功!");
}else{
this.$message.error("添加失败!");
}
}).finally(()=>{
this.getAll();
})
},
//取消
cancel(){
this.dialogFormVisible=false;
this.$message.info("当前操作取消!");
},
删除功能
// 删除
handleDelete(row) {
// // console.log(row);
this.$confirm("此操作永久删除,是否继续?","提示",{type:"info"}).then(()=>{
axios.delete("http://localhost:80/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) {
//打开编辑表单
axios.get("http://localhost:80/books/"+row.id).then((res)=>{
// console.log(res.data);
if(res.data.flag && res.data.data !=null){
this.dialogFormVisible4Edit=true;
this.formData=res.data.data;
}else{
this.$message.error("数据同步失败,自动刷新")
}
}).finally(()=>{
this.getAll();
})
},
//修改
handleEdit() {
axios.put("http://localhost:80/books",this.formData).then((res)=>{
//判断当前操作是否成功
if(res.data.flag){
this.dialogFormVisible4Edit=false;
this.$message.success("修改成功!");
}else{
this.$message.error("修改失败!");
}
}).finally(()=>{
this.getAll();
})
},
抛出异常消息:
{
“timestamp”: “2023-08-10T07:52:26.836+00:00”,
“status”: 500,
“error”: “Internal Server Error”,
“path”: “/books”
}
- 后台代码BUG导致数据格式不统一性
异常处理器
//异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler
public R doException(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new R("服务器故障,请稍后再试!");
}
}
业务消息一致性处理
- 修改表现层返回结果的模型类,封装出现异常后对应的信息
-
- flag: false
- Data: null
- 消息(msg):要显示信息
@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,String msg){
this.flag=flag;
this.data=data;
this.msg=msg;
}
public R(boolean flag,String msg){
this.flag=flag;
this.msg=msg;
}
public R(String msg){
this.flag=false;
this.msg=msg;
}
}
-
页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
//添加 handleAdd () { axios.post("http://localhost:80/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(); }) },
-
在表现层Controller中进行消息统一处理
@PostMapping public R save(@RequestBody Book book) throws IOException { if(book.getName().equals("123")) throw new IOException(); boolean flag=iBookService.save(book); return new R(flag,flag?"添加成功":"添加失败!"); }
分页功能
-
页面使用el分页组件添加分页功能
<!--分页组件--> <div class="pagination-container"> <el-pagination class="pagiantion" @current-change="handleCurrentChange" :current-page="pagination.currentPage" :page-size="pagination.pageSize" layout="total, prev, pager, next, jumper" :total="pagination.total"> </el-pagination> </div>
-
定义分页组件需要使用的数据
data:{ pagination: {//分页相关模型数据 currentPage: 1,//当前页码 pageSize:5,//每页显示的记录数 total:0//总记录数 } },
-
替换查询全部功能为分页功能,并加载分页数据
//分页查询 getAll() { //发送异步请求 axios.get("http://localhost:80/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res)=>{ // console.log(res.data); 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; // console.log(this.dataList); }); },
完善切换页码功能
//切换页码 handleCurrentChange(currentPage) { //修改页码值为当前选中的页码值 this.pagination.currentPage=currentPage; //执行查询 this.getAll(); },
Bug:当最后一页只剩一条数据时,删掉该数据但页码并不减少,仍停留在这一页
后台处理:
-
对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询(补救型方案)
@GetMapping("{currentPage}/{pageSize}") public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){ IPage<Book> page = iBookService.getPage(currentPage,pageSize); //如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码 if(currentPage > page.getPages()){ page = iBookService.getPage((int)page.getPages(),pageSize); } return new R(true,page); }
分页+条件查询
-
页面数据模型绑定
-
组织拼接数据成为get请求发送的数据
-
Controller接受参数
//接受条件查询结果 @GetMapping("{currentPage}/{pageSize}") public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){ // System.out.println("参数==》"+book); IPage<Book> page = iBookService.getPage(currentPage,pageSize,book); //如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码 if(currentPage > page.getPages()){ page = iBookService.getPage((int)page.getPages(),pageSize,book); } return new R(true,page); }
-
业务层接口功能开发
public interface IBookService extends IService<Book> { IPage<Book> getPage(int currentPage,int pageSize); IPage<Book> getPage(int currentPage,int pageSize,Book book);//新增接收book }
//条件分页查询 @Override public IPage<Book> getPage(int currentPage, int pageSize, Book book) { LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); lqw.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType()); lqw.like(Strings.isNotEmpty(book.getName()),Book::getName,book.getName()); lqw.like(Strings.isNotEmpty(book.getDescription()),Book::getDescription,book.getDescription()); IPage page = new Page(currentPage,pageSize); return bookDao.selectPage(page,lqw); }
-
业务层接口功能开发
public interface IBookService extends IService<Book> { IPage<Book> getPage(int currentPage,int pageSize); IPage<Book> getPage(int currentPage,int pageSize,Book book);//新增接收book }
//条件分页查询 @Override public IPage<Book> getPage(int currentPage, int pageSize, Book book) { LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); lqw.like(Strings.isNotEmpty(book.getType()),Book::getType,book.getType()); lqw.like(Strings.isNotEmpty(book.getName()),Book::getName,book.getName()); lqw.like(Strings.isNotEmpty(book.getDescription()),Book::getDescription,book.getDescription()); IPage page = new Page(currentPage,pageSize); return bookDao.selectPage(page,lqw); }