MybatisPlus
入门新建模块,导入依赖坐标
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.itheimagroupId>
<artifactId>mybatisplus_01_quickstartartifactId>
<version>0.0.1-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
properties>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.0version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.16version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
DROP TABLE IF EXISTS user;
CREATE TABLE user (
id bigint(20) primary key auto_increment,
name varchar(32) not null,
password varchar(32) not null,
age int(3) not null ,
tel varchar(32) not null
);
insert into user values(null,'tom','123456',12,'12345678910');
insert into user values(null,'jack','123456',8,'12345678910');
insert into user values(null,'jerry','123456',15,'12345678910');
insert into user values(null,'tom','123456',9,'12345678910');
insert into user values(null,'snake','123456',28,'12345678910');
insert into user values(null,'张益达','123456',22,'12345678910');
insert into user values(null,'张大炮','123456',16,'12345678910');
package com.itheima.domain;
import lombok.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
application.yml
中配置Jdbc
参数spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
BaseMapper
package com.itheima.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseMapper<User> {
}
dao
接口,测试功能package com.itheima;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
MP
简介MyBatisPlus
介绍MyBatisPlus
(简称MP)是基于MyBatis
框架基础上开发的增强型工具,旨在简化开发、提高效率
官网:https://mybatis-plus.com / https://mp.baomidou.com
MyBatisPlus
特性MP
后也可以使用原生Mybatis
。CRUD
操作:内置通用 Mapper
,少量配置即可实现单表CRUD
操作Lambda
:编写查询条件无需担心字段写错已提供的通用Mapper
内置的CRUD通用方法
功能 | BaseMapper 中提供方法 |
备注 |
---|---|---|
新增 | int insert(T entity) |
|
根据id删除 | int deleteById(Serializable id) |
id 类型不确定,但是都要实现序列化接口 |
根据id修改 | int updateById(@Param("et") T entity) |
按需修改,动态SQL |
根据id查询 | T selectById(Serializable id) |
|
查询所有 | List |
|
分页查询 | Ipage |
|
条件查询 | Ipage |
演示代码
package com.itheima;
import com.itheima.dao.UserDao;
import com.itheima.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave() {
User user = new User();
user.setName("黑马程序员");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
@Test
void testDelete() {
userDao.deleteById(1401856123725713409L);
}
@Test // 已经实现了按需更新
void testUpdate() {
User user = new User();
user.setId(1L);
user.setName("Tom888");
user.setPassword("tom888");
userDao.updateById(user);
}
@Test
void testGetById() {
User user = userDao.selectById(2L);
System.out.println(user);
}
@Test
void testGetAll() {
List<User> userList = userDao.selectList(null);
System.out.println(userList);
}
}
已提供的通用Mapper
内置的分页查询方法
功能 | BaseMapper 中提供方法 |
备注 |
---|---|---|
分页查询 | Ipage |
|
条件查询 | Ipage |
演示代码
装配分页拦截器作为Spring管理的bean
package com.itheima.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
//2 添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
执行分页查询
//分页查询
@Test
void testSelectPage(){
//1 创建IPage分页对象,设置分页参数
IPage<User> page=new Page<>(1,3);
//2 执行分页查询
userDao.selectPage(page,null);
//3 获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("总页数:"+page.getPages());
System.out.println("总条数:"+page.getTotal());
System.out.println("当前页数据:"+page.getRecords());
}
问题描述
使用该插件过程中,常见的问题是总记录数为0。
为0是最容易发现的,其实还有其他问题
SQL
语句中没有添加分页条件等等。
原因
分页拦截器,未配置或者配置错误。
解决方法
见3.1.1中装配分页拦截器作为Spring管理的bean
MyBatisPlus
日志# 开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring
日志打印取消做法
在resources
下新建一个logback.xml
文件,名称固定,内容为空,如下:
<configuration>
configuration>
关于logback参考播客:https://www.jianshu.com/p/75f9d11ae011
SpringBoot
启动banner
图标原日志显示如下:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.0)
取消做法
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
MybatisPlus
启动banner
图标原日志显示如下:
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.4.1
取消做法
mybatis-plus:
global-config:
banner: off # 关闭mybatisplus启动图标
QueryWrapper
MyBatisPlus
将书写复杂的SQL
条件进行了封装,使用编程的方式完成查询条件的组合QueryWrapper
对象中,并且在调用通用方法时传递该对象简单的查询,建议使用QueryWrapper
;复杂的SQL
语句,不推荐使用;
按条件查询
手写字段名,字段名容易写错
//方式一:按条件查询
QueryWrapper<User> qw=new QueryWrapper<>();
// 设置条件,参数分为为字段名和值
// lt : less then 小于
qw.lt("age", 18);
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
lambda
格式按条件查询
可以避免字段名写错
//方式二:lambda格式按条件查询
QueryWrapper<User> qw = new QueryWrapper<User>();
User user /* = 获取一个user对象*/;
qw.lambda().lt(User::getName,user.getName());
List<User> userList = userDao.selectList(qw);
System.out.println(userList);
lambda
格式按条件查询(推荐)
//方式三:lambda格式按条件查询
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
// LambdaQueryWrapper支持链式编程
lqw.lt(User::getAge, 10);
// qw.lambda().lt(User::getName,user.getName());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
并且关系(and)
设置条件时默认是并且关系
// 并且关系,默认就是并且关系
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//10到30岁之间
lqw.lt(User::getAge, 30).gt(User::getAge, 10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
或者关系(or)
// 或者关系,需要通过or()方法连接
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
/ 小于10岁或者大于30岁
lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
QueryWrapper
方法与功能常用方法
方法 | 功能 | 备注 |
---|---|---|
eq | 指定列的值相同 | equals的简写 |
ne | 指定列的值不相同 | not equals的简写 |
lt | 指定列的值小于 | less then的简写 |
le | 指定列的值小于等于 | less then and equals的简写 |
gt | 指定列的值大于 | great then 的简写 |
ge | 指定列的值大于等于 | great then and equals的简写 |
between | 指定列的值在…和…之间 | |
notBetween | 指定列的值不在…和…之间 | |
like | 指定列的值等值模糊查询 | |
notLIke | 指定列的值不等值模糊查询 | |
orderByAsc | 按照指定的列升序排序 | |
orderByDesc | 按照指定的列降序排序 | |
groupBy | 按照指定的列分组 | |
having | 按照指定的列在分组后条件过滤 | |
in | 单条件多值查询 | |
isNull | 指定列的值为空 | |
isNOtNull | 指定列的值不为空 |
用户登录(eq匹配)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//等同于=
lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
User loginUser = userDao.selectOne(lqw);
System.out.println(loginUser);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//范围查询 lt le gt ge eq between
lqw.between(User::getAge, 10, 30);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//模糊匹配 like
lqw.likeLeft(User::getName, "J");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
QueryWrapper<User> qw = new QueryWrapper<User>();
qw.select("gender","count(*) as nums");
qw.groupBy("gender");
List<Map<String, Object>> maps = userDao.selectMaps(qw);
System.out.println(maps);
官方API
文档
https://mybatis.plus/guide/wrapper.html#abstractwrapper
手动判空
Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了
Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
if(minAge!=null){
lqw.gt(User::getAge, minAge);
}
if(maxAge!=null){
lqw.lt(User::getAge, maxAge);
}
List<User> userList = userDao.selectList(lqw);
userList.forEach(System.out::println);
QueryWrapper
判空准备查询参数封装对象
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
private Integer online;
}
@Data
public class UserQuery extends User {
private Integer age2;
}
测试类中,使用QueryWrapper
判空
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
//先判定第一个参数是否为true,如果为true连接当前条件
//lqw.lt(null != uq.getAge2(),User::getAge, uq.getAge2());
//lqw.gt(null != uq.getAge(),User::getAge, uq.getAge());
// 链式编程
lqw.lt(null != uq.getAge2(),User::getAge, uq.getAge2())
.gt(null != uq.getAge(),User::getAge, uq.getAge());
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
查询投影,就是要查询的列的问题。可能存在
复杂查询不推荐使用QueryWrapper
,使用Mybatis
原生方式,自己编写SQL。
/*LambdaQueryWrapper lqw = new LambdaQueryWrapper();
lqw.select(User::getId, User::getName, User::getAge);*/
//或者
QueryWrapper<User> lqw = new QueryWrapper<User>();
// 只指定部分类
lqw.select("id", "name", "age", "tel");
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
QueryWrapper<User> lqw = new QueryWrapper<User>();
// count(*)不存在于表结构中国的字段
lqw.select("count(*) as count, tel");
lqw.groupBy("tel");
List<Map<String, Object>> userList = userDao.selectMaps(lqw);
System.out.println(userList);
在模型类属性上方,使用@TableField
注解,通过**exist**属性,设置属性在数据库表字段中是否存在,默认为true。此属性无法与value合并使用。
查询和增删改的时候,都不会参与数据库的操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNvE2GaD-1655171079951)(07-MybatisPlus-课堂笔记59.assets/image-20210801182809131.png)]
@Data
@TableName("tbl_user")
public class User {
/*
id为Long类型,因为数据库中id为bigint类型,
并且mybatis有自己的一套id生成方案,生成出来的id必须是Long类型
*/
private Long id;
private String name;
@TableField(value = "pwd",select = false) // 仅仅是不参与查询
private String password;
private Integer age;
private String tel;
@TableField(exist = false) //表示online字段不参与CRUD操作
private Boolean online;
}
DML
MybatisPlus
支持的生成策略MybatisPlus
支持8种生成策略五种可用,三种已过期。详情如下:
public enum IdType {
/**
* 数据库ID自增
* 该类型请确保数据库设置了 ID自增 否则无效
*/
AUTO(0),
/**
* 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 分配ID (主键类型为number或string),
* 默认使用com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator(雪花算法)
*
* @since 3.3.0
*/
ASSIGN_ID(3),
/**
* 分配UUID (主键类型为 string)
* 默认使用 com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
*/
ASSIGN_UUID(4),
/**
* @deprecated 3.3.0 please use {@link #ASSIGN_ID}
*/
@Deprecated
ID_WORKER(3),
/**
* @deprecated 3.3.0 please use {@link #ASSIGN_ID}
*/
@Deprecated
ID_WORKER_STR(3),
/**
* @deprecated 3.3.0 please use {@link #ASSIGN_UUID}
*/
@Deprecated
UUID(4);
}
使用注解@TableId
名称:@TableId
类型:属性注解
位置:模型类中用于表示主键的属性定义上方
作用:设置当前类中主键属性的生成策略
相关属性
type:设置主键属性的生成策略,值参照IdType
枚举值
application.yaml
mybatis-plus:
global-config:
db-config:
id-type: assign_id # 全局使用雪花算法生成主键
table-prefix: tbl_ # 全局指定表名前缀,实体类上就不需要添加@TableName()注解了
//删除指定多条数据
List<Long> list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);
userDao.deleteBatchIds(list);
//查询指定多条数据
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);
在实际环境中,如果想删除一条数据,是否会真的从数据库中删除该条数据?
实体类中添加对应成员变量,并(逐个指定)标记对应属性
package com.itheima.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data
public class User {
private Long id;
//逻辑删除字段,标记当前记录是否被删除
// value表示未删除,delval表示已删除
@TableLogic(value="0", delval="1")
private Integer deleted;
}
实体类中添加成员变量后,也可以全局配置
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
逻辑删除本质:逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
概念
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不用上锁。只在更新的时候会判断一下在此期间别人有没有去更新这个数据。
实现思路
使用数据版本(Version)记录:
version
字段来实现。version
字段的值一同读出,数据每更新一次,对此version
值加一。version
值相等,则予以更新,否则认为是过期数据。CAS:Compare And Swap
实体类中添加对应字段,并设定当前字段为版本字段
package com.itheima.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class User {
private Long id;
@Version
private Integer version;
}
配置乐观锁拦截器实现锁机制对应的动态SQL
语句拼装
package com.itheima.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
使用乐观锁机制在修改前必须先获取到对应数据的verion
方可正常进行
@Test
public void testUpdate() {
/*User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);*/
//1.先通过要修改的数据id将当前数据查询出来
//User user = userDao.selectById(3L);
//2.将要修改的属性逐一设置进去
//user.setName("Jock888");
//userDao.updateById(user);
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗?
}
or = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
4. **使用乐观锁机制在修改前必须先获取到对应数据的`verion`方可正常进行**
```java
@Test
public void testUpdate() {
/*User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);*/
//1.先通过要修改的数据id将当前数据查询出来
//User user = userDao.selectById(3L);
//2.将要修改的属性逐一设置进去
//user.setName("Jock888");
//userDao.updateById(user);
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗?
}