SSMP整合案例
实现方案分析
实体类开发————使用Lombok快速制作实体类
Dao开发————整合MyBatisPlus,制作数据层测试类
Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
Controller开发————基于Restful开发,使用PostMan测试接口功能
Controller开发————前后端开发协议制作
页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
列表、新增、修改、删除、分页、查询
项目异常处理
按条件查询————页面功能调整、Controller修正功能、Service修正功能
创建模块:
1.勾选SpringMVC与MySQL坐标
2. 修改配置文件为yml格式
3. 设置端口为80方便访问
实体类开发:
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发,lombok版本由SpringBoot提供,无需指定版本
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
Book.java:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
Dao开发
导入MyBatisPlus与Druid对应的starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
配置数据源和mybatisplus相关的配置
#配置数据源
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.driver
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
username: root
password: root
#配置MyBatisPlus对应的基础配置,id生成策略使用数据库自增策略
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
id-type: auto
bookDao(类上有@Mapper注解,继承BaseMapper):
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
写测试类去测试结果:
@SpringBootTest
class ApplicationTests {
@Autowired
private BookDao bookDao;
@Test
void BookDaoTest() {
Book book = new Book();
book.setId(1);
book.setName("java");
book.setType("语言类");
book.setDescription("java语言");
int result = bookDao.insert(book);
System.out.println(result);
Book book1 = bookDao.selectById(1);
System.out.println(book1);
}
}
控制台输出(数据库插入成功):
1
Book(id=1, type=语言类, name=java, description=java语言)
为了方便调试可以开启MyBatisPlus的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
分页功能:分页操作需要设定分页对象IPage
@Test
void GetPageTest(){
IPage<Book> page = new Page(1,5);//获取第一页数据,一页5条
bookDao.selectPage(page,null);
}
分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能,使用MyBatisPlus拦截器实现
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
根据条件查询—
使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用。
@Test
void SelectConditionTest(){
IPage page = new Page(1,5);
LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>();
wrapper.ge(Book::getId,"3");
bookDao.selectPage(page,wrapper);
}
业务层开发(建议用快速开发的方法)
Service层接口定义与数据层接口定义具有较大区别,不要混用。
selectByUserNameAndPassword(String username,String password);
login(String username,String password);
接口定义:
public interface BookService {
boolean save(Book book);
boolean delete(Integer id);
boolean update(Book book);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getByPage(int currentPage,int pageSize);
}
实现类定义(@Service注解):
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
public boolean save(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
@Override
public boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
@Override
public List<Book> getAll() {
return bookDao.selectList(null);
}
@Override
public IPage<Book> getByPage(int currentPage, int pageSize) {
IPage<Book> iPage = new Page<Book>(currentPage,pageSize);
return bookDao.selectPage(iPage,null);
}
}
测试类:
@Autowired
private BookService bookService;
@Test
void testSave(){
Book book = new Book();
book.setName("微信");
book.setType("工具类");
book.setDescription("用好微信");
bookService.save(book);
}
@Test
void testDelete(){
bookService.delete(9);
}
@Test
void testUpdate(){
Book book = new Book();
book.setId(13);
book.setName("微信");
book.setDescription("使用好微信");
bookService.update(book);
}
@Test
void testGetById(){
bookService.getById(10);
}
@Test
void testGetAll(){
bookService.getAll();
}
@Test
void testGetByPage(){
bookService.getByPage(1,5);
}
业务层开发----快速开发
快速开发方案
使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现(ServiceImpl<M,T>)。
在通用类基础上做功能重载或功能追加,注意重载时不要覆盖原始操作,避免原始提供的功能丢失。
接口定义:
public interface IBookService extends IService<Book> {
//通用方法IService已经提供了,在这里你可以写自己的业务方法
// Book get(Integer id);
IPage<Book> getPage(int currentPage, int pageSize);
}
实现类定义:
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao, Book> implements IBookService {
//继承ServiceImpl,第一个参数是对应的Dao类,第二个参数是对应的实体类
//delete方法变成remove方法,getAll方法变成list方法,getByPage方法变成page方法
//自己定义的方法还是要实现
// @Autowired
// private BookDao bookDao;
// @Override
// public Book get(Integer id) {
// return bookDao.selectById(id);
// }
@Autowired
private BookDao bookDao;
@Override
public IPage<Book> getPage(int currentPage, int pageSize) {
IPage page = new Page(currentPage,pageSize);
return bookDao.selectPage(page,null);
}
}
测试类定义:
@Autowired
private IBookService bookService2;
//service层快速开发的测试
@Test
void testGetById2(){
System.out.println(bookService2.getById(4));
}
@Test
void testSave2(){
Book book = new Book();
book.setType("测试数据123");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService2.save(book);
}
@Test
void testUpdate2(){
Book book = new Book();
book.setId(14);
book.setType("-----------------");
book.setName("测试数据123");
book.setDescription("测试数据123");
bookService2.updateById(book);
}
@Test
void testDelete2(){
bookService2.removeById(13);
}
@Test
void testGetAll2(){
bookService2.list();
}
@Test
void testGetPage2(){
IPage<Book> page = new Page<Book>(2,5);
bookService2.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());
}
表现层开发
基于Restful进行表现层接口开发;使用Postman测试表现层接口功能。
基于Restful制作表现层接口
新增:POST 实体数据:@RequestBody
删除:DELETE 路径变量:@PathVariable
修改:PUT
查询:GET
BookController:
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public List<Book> getAll(){
return bookService.list();
}
@GetMapping("{id}")
public Book getById(@PathVariable Integer id){
return bookService.getById(id);
}
@PostMapping
public boolean save(@RequestBody Book book){
return bookService.save(book);
}
@PutMapping
public boolean update(@RequestBody Book book){//用@RequestBody用于接收json格式的数据
return bookService.updateById(book);
}
@DeleteMapping("{id}")
public boolean delete(@PathVariable Integer id){
return bookService.removeById(id);
}
}
用postman去输入url地址来测试,
GET:localhost:80/books 得到所有的数据
GET:localhost:80/books/15 得到id=15的数据
POST:localhost:80/books Body->raw->JSON 输入新增数据的JSON
PUT:localhost:80/books Body->raw->JSON 输入修改数据的JSON
DELETE:localhost:80/books/15 删除id=15的数据
表现层数据一致性处理
设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议。
新增一个模型类R:
@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;
}
}
表现层接口统一返回值类型结果
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public R getAll(){
// R r = new R();
// List<Book> list = bookService.list();
// if (list!=null){
// r.setFlag(true);
// r.setData(list);
// }else{
// r.setFlag(false);
// r.setData(list);
// }
return new R(true,bookService.list());
}
@GetMapping("{id}")
public R getById(@PathVariable Integer id){
// R r = new R();
// Book book = bookService.getById(id);
// r.setData(book);
// if(book!=null){
// r.setFlag(true);
// }else{
// r.setFlag(false);
// }
return new R(true,bookService.getById(id));
}
@PostMapping
public R save(@RequestBody Book book){
// R r = new R();
// boolean b = bookService.save(book);
// r.setFlag(b);
return new R(bookService.save(book));
}
@PutMapping
public R update(@RequestBody Book book){//用@RequestBody用于接收json格式的数据
// R r = new R();
// boolean b = bookService.updateById(book);
// r.setFlag(b);
return new R(bookService.updateById(book));
}
@DeleteMapping("{id}")
public R delete(@PathVariable Integer id){
// R r = new R();
// boolean b = bookService.removeById(id);
// r.setFlag(b);
return new R(bookService.removeById(id));
}
}
运行结果:CRUD的结果都是JSON格式,flag是操作成功与否,data是数据。
前后端协议联调
前后端分离结构设计中页面归属前端服务器。
单体工程中页面放置在resources目录下的static目录中(建议执行clean)。
前端发送异步请求,调用后端接口(在这之前先用钩子函数created去调用查询所有的功能进行列表展示)
//钩子函数,VUE对象初始化完成后自动执行
created() {
//调用查询全部数据的操作
this.getAll();
},
//列表
getAll() {
//发送异步请求
axios.get("/books").then((res)=>{
//console.log(res.data);
this.dataList = res.data.data;
});
},
总结:
1.单体项目中页面放置在resources/static目录下
2. created钩子函数用于初始化页面时发起调用
3. 页面使用axios发送异步请求获取数据后确认前后端是否联通
弹出添加窗口
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
//这一步是因为添加过一次数据后,再次添加要把之前的数据清空
this.resetForm();
},
//重置表单
resetForm() {
this.formData={};
},
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
//判断当前操作是否成功
if(res.data.flag == true){
//1.关闭弹层
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else{
this.$message.error("添加失败");
}
}).finally(()=>{
//2.重新加载数据
this.getAll();
});
},
//取消
cancel(){
this.dialogFormVisible = false;
this.$message.info("当前操作取消");
},
总结:
- 请求方式使用POST调用后台对应操作
- 添加操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 弹出添加Div时清除表单数据
删除操作
// 删除
handleDelete(row) {
//1.弹出提示框
this.$confirm("是否确定删除该记录","提示",{
type:'info'//这个“是否确定删除该记录”是灰色的
}).then(()=>{
//2.执行删除业务
axios.delete("/books/"+row.id).then((res=>{
if(res.data.flag){
this.$message.success("删除成功");
}else {
this.$message.error("删除失败");
}
})).finally(()=>{
this.getAll();
});
}).catch(()=>{
//3.取消操作
this.$message.info("取消删除操作");
});
},
总结:
- 请求方式使用Delete调用后台对应操作
- 删除操作需要传递当前行数据对应的id值到后台
- 删除操作结束后动态刷新页面加载数据
- 根据操作结果不同,显示对应的提示信息
- 删除操作前弹出提示框避免误操作
弹出修改窗口
//弹出编辑窗口
handleUpdate(row) {
axios.get("/books/"+row.id).then((res)=>{
if(res.data.flag){
//展示弹层,加载数据
this.formData = res.data.data;
//打开编辑表单窗口
this.dialogFormVisible4Edit=true;
}else {
this.$message.error("数据同步失败,自动刷新");
}
});
},
//修改
handleEdit() {
this.$confirm("是否确定修改?","提示",{
type:"info"
}).then(()=>{
axios.put("/books/",this.formData).then((res)=>{
if ((res.data.flag)){
//关闭编辑表单窗口
this.dialogFormVisible4Edit=false;
this.$message.success("编辑成功");
}else {
this.$message.error("编辑失败");
}
}).finally(()=>{
this.getAll();
});
}).catch(()=>{
this.dialogFormVisible4Edit = false;
this.$message.info("取消编辑操作");
});
},
总结:
- 请求方式使用PUT调用后台对应操作
2. 修改操作结束后动态刷新页面加载数据(同新增)
3. 根据操作结果不同,显示对应的提示信息(同新增)
异常消息处理
对异常进行统一处理,出现异常后,返回指定信息
/作为springMVC的异常处理器
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常信息
@ExceptionHandler
public R doException(Exception ex){
//记录日志
//通知运维
//通知开发
ex.printStackTrace();
return new R("服务器故障,请稍后再试!");
}
}
修改模型类R,添加出现异常后要显示的信息
@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;
}
}
控制层修改方法
@PostMapping
public R save(@RequestBody Book book){
// R r = new R();
// boolean b = bookService.save(book);
// r.setFlag(b);
boolean flag = bookService.save(book);
return new R(flag , flag ? "添加成功" : "添加失败");
}
前端添加的函数
//添加
handleAdd () {
axios.post("/books",this.formData).then((res)=>{
//判断当前操作是否成功
if(res.data.flag == true){
//1.关闭弹层
this.dialogFormVisible = false;
this.$message.success(res.data.msg);
}else{
this.$message.error(res.data.msg);
}
}).finally(()=>{
//2.重新加载数据
this.getAll();
});
},
总结:
1.使用注解@RestControllerAdvice定义SpringMVC异常处理器用来处理异常的
2. 异常处理器必须被扫描加载,否则无法生效
3. 表现层返回结果的模型类中添加消息属性用来传递消息到页面
分页功能
页面使用el分页组件添加分页功能。
前端books.html:
<!--分页组件-->
<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:{
dataList: [],//当前页要展示的列表数据
dialogFormVisible: false,//添加表单是否可见
dialogFormVisible4Edit:false,//编辑表单是否可见
formData: {},//表单数据
rules: {//校验规则
type: [{ required: true, message: '图书类别为必填项', trigger: 'blur' }],
name: [{ required: true, message: '图书名称为必填项', trigger: 'blur' }]
},
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize:10,//每页显示的记录数
total:0//总记录数
}
},
加载分页数据
//分页查询
getAll() {
//发送异步请求
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res)=>{
//console.log(res.data);
// this.dataList = res.data.data;
this.pagination.currentPage = res.data.data.current;
this.pagination.pageSize = res.data.data.size;
this.pagination.total = res.data.data.total;
this.dataList = res.data.data.records;
});
},
//切换页码
handleCurrentChange(currentPage) {
this.pagination.currentPage = currentPage;
this.getAll();
},
表现层getAll():
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize){
// R r = new R();
// List<Book> list = bookService.list();
// if (list!=null){
// r.setFlag(true);
// r.setData(list);
// }else{
// r.setFlag(false);
// r.setData(list);
// }
IPage<Book> page = bookService.getPage(currentPage, pageSize);
return new R(null != page, page);
}
总结:
- 使用el分页组件
- 定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
删除功能维护(如果一页10条数据,删除第11条数据,当前页码是第二页,但是最大页码是1页)
对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if(currentPage > page.getPages()){
page = bookService.getPage((int) page.getPages(),pageSize);
}
return new R(null != page, page);
}