文章目录
- MyBatis-Plus 实现 CRUD 操作
- 一、什么是MyBatis-Plus
- 1.概述
- 2.特点
- 3.插件功能展示
- 二、整合MyBatis-Plus
- 1.创建数据库环境
- 2.导入mybatis-plus依赖
- 3.创建数据库表实体类Users
- 4.创建UsersMapper接口
- 5.测试
- 补充
- 三、CRUD
- 3.1 查询所有 list()
- 1.UserMapper
- 2.UserService
- 3.UserServiceImpl
- 4.前端控制器实现查询所有展示
- 3.2 分页 page()
- 1.分页框架构建
- 2.修改后端控制器
- 3.修改前端分页
- 4.配置分页插件
- 5.完善分页组件
- 5.1 页码列表
- 5.2 当前页高亮显示
- 5.3 对应页页跳转
- 3.3 删除 remove()
- 1.修改前端操作功能
- 2.修改后端控制器 – 添加删除
- 3.结果展示
MyBatis-Plus 实现 CRUD 操作
一、什么是MyBatis-Plus
1.概述
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis plus 官网 ,开发过程中建议安装 MybatisX 插件
返回顶部
2.特点
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
返回顶部
3.插件功能展示
效果:
可以实现xml的跳转:
生成代码:
重置模板:
JAP提示:
返回顶部
二、整合MyBatis-Plus
1.创建数据库环境
/*
Navicat MySQL Data Transfer
Source Server : test
Source Server Type : MySQL
Source Server Version : 80017
Source Host : localhost:3306
Source Schema : dormitory
Target Server Type : MySQL
Target Server Version : 80017
File Encoding : 65001
Date: 27/07/2021 21:38:52
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `users` VALUES (1, 'Jone', 18, 'test1@baomidou.com');
INSERT INTO `users` VALUES (2, 'Jack', 20, 'test2@baomidou.com');
INSERT INTO `users` VALUES (3, 'Tom', 28, 'test3@baomidou.com');
INSERT INTO `users` VALUES (4, 'Sandy', 21, 'test4@baomidou.com');
INSERT INTO `users` VALUES (5, 'Billie', 24, 'test5@baomidou.com');
SET FOREIGN_KEY_CHECKS = 1;
返回顶部
2.导入mybatis-plus依赖
<!-- 整合mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
自动配置:
- MybatisPlusAutoConfiguration 配置类,MybatisPlusProperties 配置项绑定。mybatis-plus:xxx 就是对****mybatis-plus的定制
- SqlSessionFactory 自动配置好。底层是容器中默认的数据源
- mapperLocations 自动配置好的。有默认值
classpath:/mapper/**/*.xml
;任意包的类路径下的所有mapper文件夹下任意路径下的所有xml都是sql映射文件。
- 建议以后sql映射文件,放在 mapper文件夹下
- 容器中也自动配置好了 SqlSessionTemplate
- @Mapper 标注的接口也会被自动扫描;建议直接
@MapperScan("com.atguigu.admin.mapper")
批量扫描就行
优点:
- 只需要我们的Mapper继承BaseMapper就可以拥有crud能
返回顶部
3.创建数据库表实体类Users
package com.zyx.core.admin.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @author 35192
* @date 2021-07-27 22:28
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Users {
private Integer id;
private String name;
private Integer age;
private String email;
}
返回顶部
4.创建UsersMapper接口
- UsersMapper接口继承BaseMapper<~>,并指定泛型为Users,也就是要操作的数据类型。
package com.zyx.core.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zyx.core.admin.bean.Users;
/**
* @author 35192
* @date 2021-07-27 22:31
*/
public interface UsersMapper extends BaseMapper<Users> {
}
在BaseMapper中,已经封装了一些常用的增加、查询、修改、删除操作方法,提高开发效率~
返回顶部
5.测试
package com.zyx.core.admin.test;
import com.zyx.core.admin.bean.Users;
import com.zyx.core.admin.mapper.UsersMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* @author 35192
* @date 2021-07-23 16:44
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MybatisTest {
@Autowired
UsersMapper usersMapper;
@Test
public void selectById() {
Users user = usersMapper.selectById(1);
System.out.println(user);
}
}
返回顶部
补充
如图所示,在创建实体类的时候:User、Users类我们可以进行合并,但是要注意的是:封装的类中应该是所有的属性都在数据库表结构中存在,否则会报错:
针对此种情况,mybatis-plus提供了注解来解决(将其标记为临时属性 — 不是表的字段): @TableField(exist = false)
package com.zyx.core.admin.bean;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* 所有的属性都应该在数据库表结构中
*/
@TableField(exist = false)
private String username;
@TableField(exist = false)
private String password;
// 以下是数据库users表中的字段
private Integer id;
private String name;
private Integer age;
private String email;
}
然后我们再将前面的代码进行局部修改后,再进行测试:
- 这里为什么要修改表名呢?
- mybatis-plus默认规则是根据类名User去数据库中找到匹配的user表,如果数据库中的表名和实体类名称不一致,我们还可以使用注解:
@TanleName(“user”)
返回顶部
三、CRUD
3.1 查询所有 list()
1.UserMapper
- 继承BaseMapper< T >接口
package com.zyx.core.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zyx.core.admin.bean.User;
import com.zyx.core.admin.bean.Users;
/**
* @author 35192
* @date 2021-07-27 22:31
*/
public interface UserMapper extends BaseMapper<User> {
}
返回顶部
2.UserService
- 继承 IService< T > 接口
- IService接口 — 顶级 Service
IService是对BaseMapper的扩展,IService 的默认实现 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
就是调用BaseMapper 来操作数据库。
IService 依赖于 Spring 容器,而 BaseMapper 不依赖;BaseMapper可以继承并添加新的数据库操作,IService 要扩展的话还是得调用 Mapper。
package com.zyx.core.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zyx.core.admin.bean.User;
import org.springframework.stereotype.Service;
/**
* @author 35192
* @date 2021-07-28 11:07
*/
@Service
public interface UserService extends IService<User> {
}
返回顶部
3.UserServiceImpl
- 继承 ServiceImpl<M extends BaseMapper< T >, T>类
package com.zyx.core.admin.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zyx.core.admin.bean.User;
import com.zyx.core.admin.mapper.UserMapper;
import com.zyx.core.admin.service.UserService;
import org.springframework.stereotype.Service;
/**
* @author 35192
* @date 2021-07-28 11:09
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
Mybatis-plus提供了2个接口1个类:
- BaseMapper 针对dao层的方法封装 CRUD
- IService<M,T> 针对业务逻辑层的封装 需要指定Dao层类和对应的实体类 是在BaseMapper基础上的加强
- ServiceImpl 针对业务逻辑层的实现
- 两者提供的方法略有不同
对比这两个接口,操作都差不多,名字有一点点改变,比如 BaseMapper 里面叫 insert() 的方法,在 IService 里面叫 save()。但是两者确实有区别,就是 IService 提供批处理操作,BaseMapper 没有。
返回顶部
4.前端控制器实现查询所有展示
后端控制器查询数据传值:
@GetMapping(value = "/dynamic_table")
public String dynamic_table(Model model) {
// 表格内容的遍历,首先创建用户集合
// List<User> users = Arrays.asList(
// new User("a", "123456"),
// new User("b", "123456"),
// new User("c", "123456"),
// new User("d", "123456"),
// new User("e", "123456")
// );
// model.addAttribute("users",users);
// 从数据库中查询user用户的信息进行展示
List<User> userList = userService.list(); // 查询所有
model.addAttribute("userList",userList); // 利用Model将数据放在请求域中
return "table/dynamic_table";
}
前端遍历获取Model中的数据:
<div class="panel-body">
<div class="adv-table">
<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>id</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody role="alert" aria-live="polite" aria-relevant="all">
<tr class="gradeX odd" th:each="user,stat:${userList}" >
<td th:text="${stat.count}"></td>
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td >[[${user.email}]]</td>
<td >刪除/修改</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
</div>
</div>
返回顶部
3.2 分页 page()
1.分页框架构建
<!--body wrapper start-->
<div class="wrapper">
<div class="row">
<div class="col-sm-12">
<section class="panel">
<header class="panel-heading">
Dynamic Table
<span class="tools pull-right">
<a href="javascript:;" class="fa fa-chevron-down"></a>
<a href="javascript:;" class="fa fa-times"></a>
</span>
</header>
<div class="panel-body">
<div class="adv-table">
<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>id</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody role="alert" aria-live="polite" aria-relevant="all">
<tr class="gradeX odd" th:each="user,stat:${userList}">
<td th:text="${stat.count}"></td>
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td>[[${user.email}]]</td>
<td>刪除/修改</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
<!-- 分页展示信息 -->
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 n 页 总计 m 页 共 z 条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← Previous</a></li>
<li class="active"><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li class="next"><a href="#">Next → </a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<!--body wrapper end-->
返回顶部
2.修改后端控制器
MyBatis-Plus中封装有专门的分页方法,通过继承IPage接口实现对分页对象Page的操作,来完成定制的分页功能。
@GetMapping(value = "/dynamic_table")
public String dynamic_table(@RequestParam(value = "page",defaultValue = "1")Integer page,
@RequestParam(value = "size",defaultValue = "5")Integer size,
Model model) {
// 从数据库中查询user用户的信息进行展示
// List<User> userList = userService.list(); // 查询所有
// model.addAttribute("userList",userList);
// 分页查询
// current 当前页 size 每页展示的记录数
// public Page(long current, long size) {this(current, size);}
Page<User> userPage = new Page<>(page,5); // 我们默认展示第1页,每一页5条记录
Page<User> result = userService.page(userPage,null);
model.addAttribute("userList",result);
return "table/dynamic_table";
}
返回顶部
3.修改前端分页
- ${userList.records} 获取Page对象中的查询结果
- 当前第
[[${result.current}]]
页 总计 [[${result.pages}]]
页 共 [[${result.total}]]
条记录 — 使用thymeleaf行内式分别获取当前页数、总分页数、总记录数
<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>id</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody role="alert" aria-live="polite" aria-relevant="all">
<!-- 展示信息此时需要从Page对象的record中获取 -->
<tr class="gradeX odd" th:each="user,stat:${userList.records}">
<td th:text="${stat.count}"></td>
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td>[[${user.email}]]</td>
<td>刪除/修改</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
<!-- 分页展示信息 -->
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${result.current}]] 页 总计 [[${result.pages}]] 页 共 [[${result.total}]] 条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← Previous</a></li>
<li class="active"><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li class="next"><a href="#">Next → </a></li>
</ul>
</div>
</div>
</div>
返回顶部
4.配置分页插件
- 如上一节图片所示,最终的结果总分页数、总记录数并未能够实现,原因是我们少了一个mybatis分页插件的配置,接下来我们就进行分页插件的配置。
package com.zyx.core.admin.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 35192
* @date 2021-07-28 17:30
*/
//Spring boot方式
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
// 旧版
// @Bean
// public PaginationInterceptor paginationInterceptor() {
// PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// // paginationInterceptor.setOverflow(false);
// // 设置最大单页限制数量,默认 500 条,-1 不受限制
// // paginationInterceptor.setLimit(500);
// // 开启 count 的 join 优化,只针对部分 left join
// paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
// return paginationInterceptor;
// }
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.H2);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(500L);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
完成分页插件配置后效果图:
返回顶部
5.完善分页组件
5.1 页码列表
<!-- 分页展示信息 -->
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${userList.current}]] 页 总计 [[${userList.pages}]] 页 共 [[${userList.total}]] 条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 前一页</a></li>
<li class="active" th:each="num:${#numbers.sequence(1,userList.pages)}">
<a href="#" th:text="${num}"></a>
</li>
<li class="next"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
-
th:each="num:${#numbers.sequence(1,userList.pages)}"
我们通过 ${#numbers.sequence(from,to)}
生成一个从第一页到最后一页的页码选择项列表。
返回顶部
5.2 当前页高亮显示
- 使用三元运算判断当前页是否为页码(是:高亮;否:不高亮)
th:class="${num==userList.current?'active':''}"
<!-- 分页展示信息 -->
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${userList.current}]] 页 总计 [[${userList.pages}]] 页 共 [[${userList.total}]] 条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 前一页</a></li>
<!-- 使用三元运算判断当前页是否为页码(是:高亮;否:不高亮) -->
<!-- 使用${#numbers.sequence(from,to)生成序列 -->
<li th:class="${num==userList.current?'active':''}" th:each="num:${#numbers.sequence(1,userList.pages)}">
<a href="#" th:text="${num}"></a>
</li>
<li class="next"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
修改测试结果:
返回顶部
5.3 对应页页跳转
- 如上图所示,我们要向跳转到对应页面,只需要发送请求时指定参数
page=?
即可,也就是说我们只要给指定页码列表元素添加请求链接时添加对应的page即可!
<!-- 分页展示信息 -->
<div class="row-fluid">
<div class="span6">
<div class="dataTables_info" id="dynamic-table_info">
当前第 [[${userList.current}]] 页 总计 [[${userList.pages}]] 页 共 [[${userList.total}]] 条记录
</div>
</div>
<div class="span6">
<div class="dataTables_paginate paging_bootstrap pagination">
<ul>
<li class="prev disabled"><a href="#">← 前一页</a></li>
<!-- 使用三元运算判断当前页是否为页码(是:高亮;否:不高亮) -->
<!-- 使用${#numbers.sequence(from,to)生成序列 -->
<li th:class="${num==userList.current?'active':''}" th:each="num:${#numbers.sequence(1,userList.pages)}">
<!-- 添加跳转参数指定跳转的页码 -->
<a th:href="@{/dynamic_table(page=${num})}" th:text="${num}"></a>
</li>
<li class="next"><a href="#">下一页 → </a></li>
</ul>
</div>
</div>
</div>
返回顶部
3.3 删除 remove()
1.修改前端操作功能
- 列表中的操作功能模块,我们新增删除。
- 在功能实现逻辑上,我们点击删除按钮后,按照当前选中删除记录的id进行删除,并且删除记录成功后返回的是删除时所在的页面。
-
th:href="@{/user/delete/{id}(id=${user.id},page=${userList.current})}"
在指定跳转路径的时候,我们添加id参数代表当前删除操作所在记录的id值,同时为了还跳转在当前操作所在页,我们还要传递参数page,值为当前操作所在的页码。
<table class="display table table-bordered table-striped" id="dynamic-table">
<thead>
<tr>
<th>#</th>
<th>id</th>
<th>name</th>
<th>age</th>
<th>email</th>
<th>操作</th>
</tr>
</thead>
<tbody role="alert" aria-live="polite" aria-relevant="all">
<tr class="gradeX odd" th:each="user,stat:${userList.records}">
<td th:text="${stat.count}"></td>
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td>[[${user.email}]]</td>
<td>
<a th:href="@{/user/delete/{id}(id=${user.id},page=${userList.current})}" class="btn btn-danger btn-sm" type="button">删除</a>
<a class="btn btn-default btn-sm" type="button">修改</a>
</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
返回顶部
2.修改后端控制器 – 添加删除
- 根据前端我们分析的路径,在这里URL:
/user/delete/{id}
,然后指定@PathVariable值为id,取出路径传过来的id;@RequestParam,获取额外参数page的值,代表删除操作时的页码数。 - @RedirectAttributes作用是在我们重定向URL的时候,添加参数。这里我们完成删除操作后,需要返回的是查询数据页面,此时的页码数要和删除操作时的页码数保持一致,这也就是我们为什么在前端的时候需要传回操作时当前页page参数的原因。
@GetMapping("/user/delete/{id}")
public String basic_table_delete(@PathVariable("id") Integer id,
@RequestParam(value = "page",defaultValue = "1") Integer page,
RedirectAttributes redirectAttributes) {
// 按照id删除user
userService.removeById(id);
// 获取删除操作所在页码,并且在重定向查询的时候跳转到删除操作所在页
redirectAttributes.addAttribute("page",page);
return "redirect:/dynamic_table";
}
返回顶部
3.结果展示
返回顶部