Mybatis-Plus学习
Mybatis-Plus概述
为什么要学习他?MybatisPlus可以节省我们大量的工作时间,所有的CRUD代码它都可以自动化完成!
简介
MyBatis-Plus简称(MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速开始
步骤(来自官网)
-
创建数据库
-
创建user表
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) );
-
插入数据
DELETE FROM 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');
-
创建项目,并初始化。
-
添加依赖
<!--mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
说明:mybatis-plus可以帮我们节省大量代码的编写,尽量不要同时引入mybatis和mybatis-plus
-
连接数据库(注意文件后缀名为yml)
spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver
-
编写User实体类
package com.fmxu.pojo; import org.springframework.context.annotation.Configuration; /** * @author Dell * @projectName mybatis-plus * @description: user实体类 * @date 2021/3/24/16:12 */ public class User { private Long id; private String name; private Integer age; private String email; public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", email='" + email + '\'' + '}'; } }
-
编写UserMapper接口
package com.fmxu.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.fmxu.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; /** * @author Dell * @projectName mybatis-plus * @description: user的mapper接口 * @date 2021/3/24/16:14 */ //继承基本的接口BaseMapper @Repository public interface UserMapper extends BaseMapper<User> { }
-
主启动类需要添加
MapperScan
注解,去扫描所有的mapper接口(包路径要写正确)@MapperScan("com.fmxu.mapper") @SpringBootApplication public class MybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusApplication.class, args); } }
-
测试类
@SpringBootTest(classes = MybatisPlusApplication.class) class MybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List<User> users = userMapper.selectList(null); for (User user : users) { System.out.println(user); } } }
配置日志
我们所有的sql现在是不可见的,如果我们希望知道它是怎么运行的,我们就需要去配置日志!
在配置文件中配置
#日志配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
CRUD扩展
测试插入
@Test
public void testInsert(){
User user=new User();
user.setName("fmxu");
user.setAge(24);
user.setEmail("fmxu75@stu.xidian.edu.cn");
userMapper.insert(user); //帮我们自动生成id
}
主键生成策略
UUID,主键自增, 雪花算法…
主键自增
在实体类的主键上添加注解@TableId(type = IdType.AUTO)
其他的源码解释
public enum IdType {
AUTO(0), //自增
NONE(1), //无操作
INPUT(2), //手动输入
ID_WORKER(3), //默认的全局唯一id
UUID(4), //全局唯一id uuid
ID_WORKER_STR(5); //字符串表示法
}
测试更新
@Test
public void testUpdate(){
User user=new User();
user.setName("xiaobai");
user.setId(6L);
userMapper.updateById(user);
}
自动填充
创建时间、修改时间。这些操作一般都是自动化完成的,我们不希望手动更新。
方式一:数据库级别(不推荐使用)
表中新增字段,然后勾上更新选项
方式二:代码级别
-
字段上添加注解
@TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
-
自定义实现类 MyMetaObjectHandler
//将组件加入到IOC容器中 @Component public class MyMetaObjectHandler 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); } }
乐观锁、悲观锁
乐观锁:它总是认为不会出现问题,无论干什么都不会加锁!如果出现问题,再次更新值测试
悲观锁:它认为干什么都会出现问题,无论干什么都会上锁!再去操作
乐观锁机制
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁测试
-
首先数据库要添加一个乐观锁字段
-
实体类中添加乐观锁属性,并添加
@Version
注解@Version private Integer version;
-
注册组件
//扫描我们的mapper文件夹 @MapperScan("com.fmxu.mapper") @EnableTransactionManagement @Configuration public class MyBatisPlusConfig{ //注册乐观锁 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor(){ return new OptimisticLockerInterceptor(); } }
查询操作
@Test
public void testSelectById(){
User user=userMapper.selectById(1L);
System.out.println(user);
}
//测试批量查询
@Test
public void testSelectByBatchId(){
List<User> users=userMapper.selectBatchIds(Arrays.asList(1,2,3));
for (User user : users) {
System.out.println(user);
}
}
//按条件查询之一使用map操作
@Test
public void testSelectByMap(){
HashMap<String,Object> map=new HashMap<>();
map.put("name","fmxu");
map.put("age","24");
List<User> users = userMapper.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
分页查询
分页在网站的使用非常多!
- 原始的limit进行分页
- pageHelper第三方插件
- MP其实也内置了分页插件
如何使用!
-
配置拦截器组件
// 分页插件 @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); }
-
测试
//测试分页查询 @Test public void testPage(){ //参数1 当前页 参数2 页面大小 Page<User> page = new Page<>(1, 5); userMapper.selectPage(page, null); for (User record : page.getRecords()) { System.out.println(record); } }
删除操作
//测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1L);
}
//通过id批量删除
public void testDeleteBatchIds(){
userMapper.deleteBatchIds(Arrays.asList(1,2,3));
}
//通过map删除
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","fmxu");
userMapper.deleteByMap(map);
}
逻辑删除
物理删除:从数据库中删除
逻辑删除:数据库中并没有被删除,而是通过一个变量将其失效。类似于回收站。
-
数据库中添加一个deleted字段
-
实体类中添加属性
//逻辑删除 @TableLogic private int deleted;
-
在自定义的配置类中添加组件
//逻辑删除组件 @Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); }
-
配置主配置文件
application.yml
mybatis-plus: # 配置逻辑删除 global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0
-
测试
删除后相应记录的deleted字段值由0变为1。假性删除。
条件构造器
参数说明
-
eq 等于 =
eq(R column, Object val) eq(boolean condition, R column, Object val)
-
allEq 全部eq(或个别isNull)
allEq(Map<R, V> params) allEq(Map<R, V> params, boolean null2IsNull) allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
个别参数说明:
params
:key
为数据库字段名,value
为字段值
null2IsNull
: 为true
则在map
的value
为null
时调用 isNull 方法,为false
时则忽略value
为null
的- 例1:
allEq({id:1,name:"老王",age:null})
—>id = 1 and name = '老王' and age is null
- 例2:
allEq({id:1,name:"老王",age:null}, false)
—>id = 1 and name = '老王'
- 例1:
-
ne 不等于
ne(R column, Object val) ne(boolean condition, R column, Object val)
-
gt 大于>
- 例:
gt("age", 18)
—>age > 18
- 例:
-
lt 小于<
- 例:
lt("age", 18)
—>age < 18
- 例:
-
le 小于等于<=
- 例:
le("age", 18)
—>age <= 18
- 例:
-
between BETWEEN 值1 AND 值2
- 例:
between("age", 18, 30)
—>age between 18 and 30
- 例:
-
notBetween NOT BETWEEN 值1 AND 值2
- 例:
notBetween("age", 18, 30)
—>age not between 18 and 30
- 例:
-
like LIKE '%值%
- 例:
like("name", "王")
—>name like '%王%'
- 例:
-
notLike NOT LIKE '%值%
- 例:
notLike("name", "王")
—>name not like '%王%'
- 例:
-
likeLeft LIKE ‘%值’
- 例:
likeLeft("name", "王")
—>name like '%王'
- 例:
-
likeRight LIKE ‘值%’
- 例:
likeRight("name", "王")
—>name like '王%'
- 例:
-
isNull 字段 IS NULL
- 例:
isNull("name")
—>name is null
- 例:
-
isNotNull 字段 IS NOT NULL
- 例:
isNotNull("name")
—>name is not null
- 例:
-
in
in(R column, Collection<?> value) in(boolean condition, R column, Collection<?> value)
-
字段 IN (value.get(0), value.get(1), …)
-
例:
in("age",{1,2,3})
—>age in (1,2,3)
in(R column, Object... values) in(boolean condition, R column, Object... values)
- 字段 IN (v0, v1, …)
- 例:
in("age", 1, 2, 3)
—>age in (1,2,3)
-
-
notIn
notIn(R column, Collection<?> value) notIn(boolean condition, R column, Collection<?> value)
- 字段 NOT IN (value.get(0), value.get(1), …)
- 例:
notIn("age",{1,2,3})
—>age not in (1,2,3)
notIn(R column, Object... values) notIn(boolean condition, R column, Object... values)
- 字段 NOT IN (v0, v1, …)
- 例:
notIn("age", 1, 2, 3)
—>age not in (1,2,3)
-
inSql
- 字段 IN ( sql语句 )
- 例:
inSql("age", "1,2,3,4,5,6")
—>age in (1,2,3,4,5,6)
- 例:
inSql("id", "select id from table where id < 3")
—>id in (select id from table where id < 3)
-
notInSql
- 字段 NOT IN ( sql语句 )
- 例:
notInSql("age", "1,2,3,4,5,6")
—>age not in (1,2,3,4,5,6)
- 例:
notInSql("id", "select id from table where id < 3")
—>id not in (select id from table where id < 3)
-
groupBy
- 分组:GROUP BY 字段, …
- 例:
groupBy("id", "name")
—>group by id,name
-
orderByAsc //正序
- 排序:ORDER BY 字段, … ASC
- 例:
orderByAsc("id", "name")
—>order by id ASC,name ASC
-
orderByDesc //逆序
- 排序:ORDER BY 字段, … DESC
- 例:
orderByDesc("id", "name")
—>order by id DESC,name DESC
-
orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
- 排序:ORDER BY 字段, …
- 例:
orderBy(true, true, "id", "name")
—>order by id ASC,name ASC
-
having
- HAVING ( sql语句 )
- 例:
having("sum(age) > 10")
—>having sum(age) > 10
- 例:
having("sum(age) > {0}", 11)
—>having sum(age) > 11
-
func
- unc 方法(主要方便在出现if…else下调用不同方法能不断链)
- 例:
func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
-
or
-
拼接 OR
注意事项:
主动调用
or
表示紧接着下一个方法不是用and
连接!(不调用or
则默认为使用and
连接) -
例:
eq("id",1).or().eq("name","老王")
—>id = 1 or name = '老王'
-
OR 嵌套
-
例:
or(i -> i.eq("name", "李白").ne("status", "活着"))
—>or (name = '李白' and status <> '活着')
-
-
and
- AND 嵌套
- 例:
and(i -> i.eq("name", "李白").ne("status", "活着"))
—>and (name = '李白' and status <> '活着')
-
nested
- 正常嵌套 不带 AND 或者 OR
- 例:
nested(i -> i.eq("name", "李白").ne("status", "活着"))
—>(name = '李白' and status <> '活着')
-
apply
- 拼接 sql
-
last
-
无视优化规则直接拼接到 sql 的最后
注意事项:
只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
-
-
exists
- 拼接 EXISTS ( sql语句 )
- 例:
exists("select id from table where age = 1")
—>exists (select id from table where age = 1)
-
notExists
- 拼接 NOT EXISTS ( sql语句 )
- 例:
notExists("select id from table where age = 1")
—>not exists (select id from table where age = 1)
测试例子
package com;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fmxu.MybatisPlusApplication;
import com.fmxu.mapper.UserMapper;
import com.fmxu.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Queue;
@SpringBootTest(classes = MybatisPlusApplication.class)
class WrapperTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
QueryWrapper<User> wrapper=new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",24);
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
@Test
public void test2(){
//查询名为fmxu的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "fmxu");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
@Test
public void test3(){
//查询年龄在20~30中间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
//模糊查询
@Test
public void test4(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name","J")
.likeRight("email","t");
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
//排序
@Test
public void test5(){
//通过id排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
}