5.数据层开发——条件查询功能制作
除了分页功能,MyBatisPlus还提供有强大的条件查询功能。以往我们写条件查询要自己动态拼写复杂的SQL语句,现在简单了,MyBatisPlus将这些操作都制作成API接口,调用一个又一个的方法就可以实现各种条件的拼装。这里给大家普及一下基本格式,详细的操作还是到MyBatisPlus的课程中查阅吧。
下面的操作就是执行一个模糊匹配对应的操作,由like条件书写变为了like方法的调用。
@Test void testGetBy(){ QueryWrapper<Book> qw = new QueryWrapper<>(); qw.like("name","Spring"); bookDao.selectList(qw); }
其中第一句QueryWrapper对象是一个用于封装查询条件的对象,该对象可以动态使用API调用的方法添加条件,最终转化成对应的SQL语句。第二句就是一个条件了,需要什么条件,使用QueryWapper对象直接调用对应操作即可。比如做大于小于关系,就可以使用lt或gt方法,等于使用eq方法,等等,此处不做更多的解释了。
这组API使用还是比较简单的,但是关于属性字段名的书写存在着安全隐患,比如查询字段name,当前是以字符串的形态书写的,万一写错,编译器还没有办法发现,只能将问题抛到运行器通过异常堆栈告诉开发者,不太友好。
MyBatisPlus针对字段检查进行了功能升级,全面支持Lambda表达式,就有了下面这组API。由QueryWrapper对象升级为LambdaQueryWrapper对象,这下就避免了上述问题的出现。
@Test void testGetBy2(){ String name = "1"; LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); lqw.like(Book::getName,name); bookDao.selectList(lqw); }
为了便于开发者动态拼写SQL,防止将null数据作为条件使用,MyBatisPlus还提供了动态拼装SQL的快捷书写方式。
@Test void testGetBy2(){ String name = "1"; LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); //if(name != null) lqw.like(Book::getName,name); //方式一:JAVA代码控制 lqw.like(name != null,Book::getName,name); //方式二:API接口提供控制开关 bookDao.selectList(lqw); }
其实就是个格式,没有区别。关于MyBatisPlus的基础操作就说到这里吧,如果这一块知识不太熟悉的小伙伴建议还是完整的学习一下MyBatisPlus的知识吧,这里只是蜻蜓点水的用了几个操作而已。
总结
- 使用QueryWrapper对象封装查询条件
- 推荐使用LambdaQueryWrapper对象
- 所有查询操作封装成方法调用
- 查询条件支持动态条件拼装
6.业务层开发
数据层开发告一段落,下面进行业务层开发,其实标准业务层开发很多初学者认为就是调用数据层,怎么说呢?这个理解是没有大问题的,更精准的说法应该是组织业务逻辑功能,并根据业务需求,对数据持久层发起调用。有什么差别呢?目标是为了组织出符合需求的业务逻辑功能,至于调不调用数据层还真不好说,有需求就调用,没有需求就不调用。
一个常识性的知识普及一下,业务层的方法名定义一定要与业务有关,例如登录操作
login(String username,String password);
而数据层的方法名定义一定与业务无关,是一定,不是可能,也不是有可能,例如根据用户名密码查询
selectByUserNameAndPassword(String username,String password);
我们在开发的时候是可以根据完成的工作不同划分成不同职能的开发团队的。比如一个哥们制作数据层,他就可以不知道业务是什么样子,拿到的需求文档要求可能是这样的
接口:传入用户名与密码字段,查询出对应结果,结果是单条数据 接口:传入ID字段,查询出对应结果,结果是单条数据 接口:传入离职字段,查询出对应结果,结果是多条数据
但是进行业务功能开发的哥们,拿到的需求文档要求差别就很大
接口:传入用户名与密码字段,对用户名字段做长度校验,4-15位,对密码字段做长度校验,8到24位,对密码字段做特殊字符校验,不允许存在空格,查询结果为对象。如果为null,返回BusinessException,封装消息码
INFO_LOGON_USERNAME_PASSWORD_ERROR
你比较一下,能是一回事吗?差别太大了,所以说业务层方法定义与数据层方法定义差异化很大,只不过有些入门级的开发者手懒或者没有使用过公司相关的ISO标准化文档而已。
多余的话不说了,咱们做案例就简单制作了,业务层接口定义如下:
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 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<Book> getAll() { return bookDao.selectList(null); } @Override public IPage<Book> getPage(int currentPage, int pageSize) { IPage page = new Page(currentPage,pageSize); bookDao.selectPage(page,null); return page; } }
别忘了对业务层接口进行测试,测试类如下:
@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("-----------------"); book.setName("测试数据123"); book.setDescription("测试数据123"); bookService.updateById(book); } @Test void testDelete(){ bookService.removeById(18); } @Test void testGetAll(){ bookService.list(); } @Test void testGetPage(){ IPage<Book> page = new Page<Book>(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()); } }
总结
- Service接口名称定义成业务名称,并与Dao接口名称进行区分
- 制作测试类测试Service功能是否有效
- 业务层快速开发
- 其实MyBatisPlus技术不仅提供了数据层快速开发方案,业务层MyBatisPlus也给了一个通用接口,个人观点不推荐使用,凑合能用吧,其实就是一个封装+继承的思想,代码给出,实际开发慎用。
业务层接口快速开发
public interface IBookService extends IService<Book> { //添加非通用操作API接口 }
业务层接口实现类快速开发,关注继承的类需要传入两个泛型,一个是数据层接口,另一个是实体类。
@Service public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService { @Autowired private BookDao bookDao; //添加非通用操作API }
如果感觉MyBatisPlus提供的功能不足以支撑你的使用需要(其实是一定不能支撑的,因为需求不可能是通用的),在原始接口基础上接着定义新的API接口就行了,此处不再说太多了,就是自定义自己的操作了,但是不要和已有的API接口名冲突即可。
总结
- 使用通用接口(ISerivce)快速开发Service
- 使用通用实现类(ServiceImpl)快速开发ServiceImpl
- 可以在通用接口基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
7.表现层开发
终于做到表现层了,做了这么多都是基础工作。其实你现在回头看看,哪里还有什么SpringBoot的影子?前面1,2步就搞完了。继续完成表现层制作吧,咱们表现层的开发使用基于Restful的表现层接口开发,功能测试通过Postman工具进行。
表现层接口如下:
@RestController @RequestMapping("/books") public class BookController2 { @Autowired private IBookService bookService; @GetMapping public List<Book> getAll(){ return bookService.list(); } @PostMapping 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<Book> getPage(@PathVariable int currentPage,@PathVariable int pageSize){ return bookService.getPage(currentPage,pageSize, null); } }
在使用Postman测试时关注提交类型,对应上即可,不然就会报405的错误码了。
普通GET请求
PUT请求传递json数据,后台实用@RequestBody接收数据
GET请求传递路径变量,后台实用@PathVariable接收数据
总结
- 基于Restful制作表现层接口
- 新增:POST
- 删除:DELETE
- 修改:PUT
- 查询:GET
- 接收参数
- 实体数据:@RequestBody
- 路径变量:@PathVariable
8.表现层消息一致性处理
目前我们通过Postman测试后业务层接口功能是通的,但是这样的结果给到前端开发者会出现一个小问题。不同的操作结果所展示的数据格式差异化严重。
增删改操作结果
true 查询单个数据操作结果 { "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程" }
查询全部数据操作结果
[ { "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程" }, { "id": 2, "type": "计算机理论", "name": "Spring 5核心原理与30个类手写实战", "description": "十年沉淀之作" } ]
每种不同操作返回的数据格式都不一样,而且还不知道以后还会有什么格式,这样的结果让前端人员看了是很容易让人崩溃的,必须将所有操作的操作结果数据格式统一起来,需要设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
@Data public class R { private Boolean flag; private Object data; }
其中flag用于标识操作是否成功,data用于封装操作数据,现在的数据格式就变了
{ "flag": true, "data":{ "id": 1, "type": "计算机理论", "name": "Spring实战 第5版", "description": "Spring入门经典教程" } }
表现层开发格式也需要转换一下
结果这么一折腾,全格式统一,现在后端发送给前端的数据格式就统一了,免去了不少前端解析数据的烦恼。
总结
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也称为前后端数据协议