MyBatis-Plus 教程中的天花板,还有谁?
一.Mybatis-plus概述
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
二愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
三特性
1.无侵入: 只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
2.损耗小: 启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
3.强大的 CRUD 操作: 内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
4.支持 Lambda 形式调用: 通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
5.支持主键自动生成: 支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
6.支持 ActiveRecord 模式: 支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
7.支持自定义全局通用操作: 支持全局通用方法注入( Write once, use anywhere )
8.内置代码生成器: 采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
9.内置分页插件: 基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
10.分页插件支持多种数据库: 支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
11.内置性能分析插件: 可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
12.内置全局拦截插件: 提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
四.开始撸代码
1.使用数据库test,创建user表
USE test;
DROP TABLE IF EXISTS USER;
CREATE TABLE USER
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
2.向user表中添加数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
3.添加mybatis-plus的坐标
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
4.整体依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
5.springboot核心配置文件
application.properties文件内容
server.port=8081
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=true&useUnicode=true&characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
6.创建包pojo,添加实体类User.java
package cn.kgc.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
7.编写UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
/*所有的CRUD已经编写完成*/
}
8.配置文件中需要配置日志
server.port=8081
#datasource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=true&useUnicode=true&characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis-plus 日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
9.插入操作
//测试插入
@Test
public void testInsert(){
User user = new User();
user.setName("派大星学Java");
user.setAge(16);
user.setEmail("none-j@qq.com");
int insert = userMapper.insert(user);//帮我们自动生成id
System.out.println(insert);//受影响的行数
System.out.println(user);//发现,id会自动回填
}
数据库插入的id的默认值为:全局的唯一id。
主键生成策略
1.默认 ID_WORKER 全局唯一
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后有一个符号,永远是0。可以保证几乎是全球唯一!
2.主键自增
我们需要配置主键自增:
1、实体类字段上@TableId(type = IdType.AUTO)
@TableId(type=IdType.AUTO)
private Long id;
2、数据库字段一定要是自增!
其他的源码解释
public enum IdType{
AUTO(0),//数据库id自增
NONE(1),//未设置主键
INPUT(2),//手动输入 一旦手动输入id之后,就需要自己设置id了
ID_WORKER(3),//默认的全局唯一id
UUID(4),//全局唯一id UUID
ID_WORKER_STR(5),//ID_WORKER 字符串表示法
}
10.添加和更新信息时间需要自动填充
创建时间、修改时间!这些个操作一般都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create、gmt_modified几乎所有的表都要配置上!而且需要自动化!
方式一:数据库级别(工作中不允许更改数据库)
1、在表中新增字段create_time、update_time
private Date createTime;
private Date updateTime;
方式二:代码级别
1、删除数据库中的默认值、更新操作!
2、实体类字段属性需要增加注解
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
3、编写处理器来处理这个注解即可!
@Slf4j//日志
@Component
public class MyDateObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
4、测试插入
5、测试更新,观察时间即可!
11.乐观锁
在面试过程中,我们经常会被问到乐观锁,悲观锁!
乐观锁: 故名思议十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出了问题,再次更新值测试
悲观锁: 故名思议十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
乐观锁实现方式:
1.取出记录时,获取当前version
2.更新时,带上这个version
3.执行更新时, set version = newVersion where version = oldVersion
4.如果version不对,就更新失败
--A
update user set name = "wumao",version = version + 1
where id = 2 and version = 1
--B 线程抢先完成,这个时候 version = 2,会导致A修改失败
update user set name = "wumao",version = version + 1
where id = 2 and version = 1
测试一下MP的乐观锁插件
1、给数据库中加入version字段
2、给实体类加上对应的字段!
@Version//乐观锁version的注解
private Integer version;
3、注册组件
@MapperScan("com.aaa.mapper")//扫描mapper文件夹
@EnableTransactionManagement
@Configuration//配置类
public class MyBatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
4、测试乐观锁
//测试乐观锁
//成功案例
@Test
public void versionTest(){
User user = userMapper.selectById(1L);
user.setName("派大星");
user.setEmail("admin@qq.com");
userMapper.updateById(user);
}
//乐观锁失败案例----多线程
@Test
public void OptimisticLockerTest(){
User user = userMapper.selectById(1L);
user.setName("派大星111");
user.setEmail("admin@qq.com");
//模拟另外一个线程执行了插队操作
User user2 = userMapper.selectById(1L);
user.setName("派大星222");
user.setEmail("admin@qq.com");
userMapper.updateById(user2);
userMapper.updateById(user);
}
12.MP配置类的完整代码
/**
* 乐观锁和配置分页插件拦截器
*/
@MapperScan("cn.kgc.mapper")
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
/**
* 配置乐观锁
* @return
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor(){
return new OptimisticLockerInnerInterceptor();
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor( optimisticLockerInnerInterceptor());
mybatisPlusInterceptor.addInnerInterceptor( paginationInnerInterceptor());
return mybatisPlusInterceptor;
}
/**
* 配置分页插件
*/
public PaginationInnerInterceptor paginationInnerInterceptor(){
return new PaginationInnerInterceptor(DbType.MYSQL);
}
}
13.分页查询
分页在网站使用的十分之多!
1.原始的limit 进行分页
2.pageHelper 第三方插件
3.MP内置了分页插件
如何使用!
1、配置拦截器
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2、分页查询
//分页查询
@Test
public void PageTest(){
//参数一:当前页
//参数二:页的大小
//使用了分页插件之后,所有的分页操作也变得简单!
Page<User> page = new Page<>(1,5);
IPage<User> pages = userMapper.selectPage(page, null);
pages.getRecords().forEach(System.out::println);
}
14.删除操作
基本的删除操作
//删除操作
@Test
public void deletTest(){
userMapper.deleteById(1398260764485095426L);
}
//批量删除
@Test
public void deletBatchTest(){
userMapper.deleteBatchIds(Arrays.asList(1398201429172178946L,5));
}
@Test
public void deletByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","泰裤辣");
userMapper.deleteByMap(map);
}
我们在工作中会遇到一些问题:逻辑删除!
物理删除: 从数据库中直接移除
逻辑删除: 在数据库中没有被移除,而是通过一个变量让他无效!delete = 0 —> delete = 1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
测试一下:
1、在数据库中增加一个deleted字段,默认值是0
2、在pojo中增加新的属性
//value = “未删除的值,默认值为0”;若设置为2,则查询时 where 后面自动拼接 is_del = 2
//delval = “删除后的值,默认值为1”
@TableLogic(value = "0",delval = "1")//逻辑删除
private Integer deleted;
3、配置组件 (这个我没有配置,因为我的MP版本中LogicSqlInjector找不到)
@Bean//逻辑删除组件
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
#配置标记逻辑删除的字段
mybatis-plus.global-config.db-config.logic-delete-field=deleted
#配置逻辑删除 1 删除 0 未删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
4、测试一下:
//逻辑删操作
@Test
public void deleteLogic(){
int i = userMapper.deleteById(1L);
System.out.println(i);
}
记录依旧在数据库中!
以上的所有CRUD操作及其扩展操作,我们必须精通掌握!
15.service层接口UserService.java
public interface UserService {
//查询所有用户
List<User> findAllUser();
//根据id查询一个用户
User findUserById(Integer id);
//添加用户
boolean saveUser(User user);
//根据id更新用户
boolean modifyUserById(User user);
//根据id删除用户
boolean removeUserById(Integer id);
//多条件查询用户
List<User> findUserByCon(String userName,String email);
}
16.service层实现类UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
/**
* 添加用户
* @param user
* @return
*/
@Override
public boolean saveUser(User user) {
int id=userMapper.insert(user);
return id>0?true:false;
}
/**
* 根据id更新用户
* @param user
* @return
*/
@Override
public boolean modifyUserById(User user) {
int modifyResult=userMapper.updateById(user);
return modifyResult>0?true:false;
}
/**
* 根据id删除用户
* @param id
* @return
*/
@Override
public boolean removeUserById(Integer id) {
int delResult=userMapper.deleteById(id);
return delResult>0?true:false;
}
/**
* 多条件分页查询用户
* @param userName
* @param email
* @return
*/
@Override
public List<User> findUserByCon(String userName, String email) {
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.like("name",userName);
queryWrapper.like("email",email);
Page<User> page=new Page<>(1,5);
IPage<User> pages =userMapper.selectPage(page,queryWrapper);
pages.getRecords().forEach(System.out::println);
return pages.getRecords();
}
/**
* 分页查询所有用户
* @return
*/
@Override
public List<User> findAllUser() {
Page<User> page=new Page<>(1,5);
IPage<User> pages =userMapper.selectPage(page,null);
return pages.getRecords();
}
/**
* 根据id查询一个用户
* @param id
* @return
*/
@Override
public User findUserById(Integer id) {
QueryWrapper wrapper=new QueryWrapper<>();
wrapper.eq("id",id);
User user= userMapper.selectOne(wrapper);
return user;
}
}
17. 控制层 IndexController.java
@Controller
public class IndexController {
@RequestMapping("/")
public String init(){
return "redirect:/user/queryAllUser";
}
}
18.UserController.java
package cn.kgc.controller;
import cn.kgc.pojo.User;
import cn.kgc.service.UserService;
import cn.kgc.utils.ResultDto;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.jws.WebParam;
import javax.xml.ws.RequestWrapper;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@GetMapping(value = {"queryAllUser"})
public String queryAllUser(Model model){
List<User> userList=userService.findAllUser();
model.addAttribute("userList",userList);
return "userList";
}
/**
* 去增加页面
* @return
*/
@GetMapping("toAddUser")
public String toAddUser(){
return "addUser";
}
/**
* 添加用户
* @param user
* @return
*/
@RequestMapping("addUser")
public String addUser(User user){
boolean addResult=userService.saveUser(user);
if (addResult) {
return "redirect:/user/queryAllUser";
}else{
return "addUser";
}
}
/**
* 去修改页面
* @return
*/
@GetMapping("toModifyUser")
public String toModifyUser(Integer id,Model model){
User user = userService.findUserById(id);
System.out.println(user);
model.addAttribute("user",user);
return "modifyUser";
}
/**
* 修改用户
* @return
*/
@PostMapping("doModifyUser")
@ResponseBody
public ResultDto doModifyUser(@RequestBody User user){
System.out.println(user);
boolean modifyResult = userService.modifyUserById(user);
ResultDto resultDto=new ResultDto();
if (modifyResult) {
resultDto.setCode(200);
resultDto.setMessage("修改成功");
}else{
resultDto.setCode(222);
resultDto.setMessage("修改失败");
}
return resultDto;
}
/**
* 查看用户详情
* @param model
* @param id
* @return
*/
@RequestMapping("goDetailsUser")
public String goDetailsUser(Model model,Integer id){
User user = userService.findUserById(id);
model.addAttribute("user",user);
return "detailsUser";
}
/**
* 删除用户
* @param id
* @return
*/
@RequestMapping("delUser")
@ResponseBody
public ResultDto delUser(Integer id){
System.out.println(id);
boolean delResult=userService.removeUserById(id);
ResultDto resultDto=new ResultDto();
if (delResult){
resultDto.setCode(200);
resultDto.setMessage("删除成功");
}else{
resultDto.setCode(222);
resultDto.setMessage("删除失败!");
}
return resultDto;
}
/**
* 多条件查询所有用户
* @param model
* @param name
* @param email
* @return
*/
@RequestMapping("searchUserInfo")
public String searchUserInfo(Model model,String name,String email){
System.out.println(name+"\t"+email);
List<User> userList=userService.findUserByCon(name,email);
model.addAttribute("userList",userList);
model.addAttribute("queryEmail",email);
model.addAttribute("queryName",name);
return "userList";
}
}
20. resources目录下template目录下的页面
1.首页userlist.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link type="text/css" th:href="@{/css/common.css}" rel="stylesheet"/>
<link type="text/css" th:href="@{/css/userList.css}" rel="stylesheet" />
</head>
<body>
<div align="center" id="userCon">
<h2>用户信息</h2>
<div class="addUserBtnCon" style="text-align: left;width: 900px">
<button class="addUserBtn" th:onclick="addUser()">添 加</button>
</div>
<div class="queryCon">
姓名:<input th:placeholder="请输入姓名" name="queryName" class="queryUserName" th:value="${queryName}"/> 邮箱:<input name="queryEmail" class="queryUserEmail" th:value="${queryEmail}"/><button type="button" class="queryInfoBtn" th:onclick="queryUser()">搜索</button>
</div>
<table width="1000">
<tr align="center">
<th>编号</th>
<th>名字</th>
<th>年龄</th>
<th>邮箱</th>
<th>操作</th>
</tr>
<tr align="center" th:each="u:${userList}">
<td th:text="${u.id}"></td>
<td th:text="${u.name}"></td>
<td th:text="${u.age}"></td>
<td th:text="${u.email}"></td>
<td>
<button type="button" class="detailsUserBtn" th:onclick="detailsUser([[${u.id}]])">详情</button>
<button type="button" class="modifyUserBtn" th:onclick="modifyUser([[${u.id}]])">修改</button>
<button type="button" class="delUserBtn" th:onclick="delUser(this,[[${u.id}]])">删除</button>
</td>
</tr>
</table>
</div>
<script type="text/javascript" th:src="@{/js/jquery-3.6.1.js}"></script>
<script type="text/javascript">
//去添加页面
function addUser(){
location.href="/user/toAddUser";
}
//去修改页面
function modifyUser(id){
location.href="/user/toModifyUser?id="+id;
}
//去用户详情页面
function detailsUser(id){
location.href="/user/goDetailsUser?id="+id;
}
//删除用户
function delUser(dom,id){
console.log(id);
let result=confirm("确认要删除吗?");
if (result){
$.get("/user/delUser","id="+id,function(res){
console.log(res);
if(res.code==200){
alert("删除成功!");
$(dom).parent().parent().remove();
}else{
alert("删除失败!")
}
},"json");
}else{
alert("您取消了删除操作!");
}
}
//搜索
function queryUser(){
let uName=$(".queryUserName").val();
let uEmail=$(".queryUserEmail").val();
console.log(uName+"==="+uEmail);
location.href="/user/searchUserInfo?name="+uName+"&email="+uEmail;
}
</script>
</body>
</html>
21.添加用户addUser.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link type="text/css" th:href="@{/css/common.css}" rel="stylesheet"/>
<link type="text/css" th:href="@{/css/userList.css}" rel="stylesheet" />
</head>
<body>
<div>
<h2>添加用户信息</h2>
<form th:action="@{/user/addUser}" th:method="post">
<p>姓名:<input name="name" class="name" /></p>
<p>年龄:<input name="age" class="age" /></p>
<p>邮箱:<input name="email" class="email" /></p>
<button class="addUserBtn">添加</button>
<button type="reset" class="addUserBtn">重置</button>
</form>
</div>
</body>
</html>
22.用户详情detailsUser.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="detailsUserCon">
<h2>用户详细信息</h2>
<form id="detailsForm">
<p>姓名:<input name="name" class="name" th:value="${user.name}" /></p>
<p>年龄:<input name="age" class="age" th:value="${user.age}" /></p>
<p>邮箱:<input name="email" class="email" th:value="${user.email}" /></p>
</form>
</div>
</body>
</html>
23.修改用户页面modifyUser.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="modifyUserCon">
<h2>修改用户信息</h2>
<form id="modifyForm">
<input type="hidden" name="version" class="version" th:value="${user.version}" />
<input type="hidden" name="id" class="id" th:value="${user.id}" />
<p>姓名:<input name="name" class="name" th:value="${user.name}" /></p>
<p>年龄:<input name="age" class="age" th:value="${user.age}" /></p>
<p>邮箱:<input name="email" class="email" th:value="${user.email}" /></p>
<button class="addUserBtn" type="button" th:onclick="doModifyUser()">修改</button>
<button type="reset" class="addUserBtn">重置</button>
</form>
</div>
<script type="text/javascript" th:src="@{/js/jquery-3.6.1.js}"></script>
<script type="text/javascript">
function doModifyUser(){
var id=$(".id").val();
var userName=$(".name").val();
var age=$(".age").val();
var email=$(".email").val();
var version=$(".version").val();
let obj={"id":id,"name":userName,"age":age,"email":email,"version":version};
$.ajax({
url:"/user/doModifyUser",
type:"post",
data:JSON.stringify(obj),
dataType:"json",
contentType:"application/json;charset=UTF-8",
success:function(res){
if (res.code==200){
alert("修改成功");
location.href="/user/queryAllUser";
}else {
alert("修改失败");
}
},
error:function (){
alert("系统发生异常,请联系管理员!");
}
});
}
</script>
</body>
</html>
24.页面效果
需要源码的评论联系即可!