总结源自:B站遇见狂神说 MyBatisPlus教程地址
Mybatis—plus特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求,BaseMapper
- 支持 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速入门
地址:https://baomidou.com/guide/quick-start.html
导入数据库脚本:
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');
创建一个SpringBoot工程
创建一个空的 Spring Boot 工程
可以使用 Spring Initializer (opens new window)快速初始化一个 Spring Boot 工程
添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>undefined</version>
<relativePath/>
</parent>
创建pojo、mapper(不需要mapper.xml文件)
User类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {
//只需要继承BaseMapper<T> 就可以使用BaseMapper中CRUD方法
}
测试
@SpringBootTest
class MybatisplusdemoApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}
输出结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zCZ6aHx4-1628931210293)(MyBatis—plus.assets/image-20210807162248764.png)]
测试Insert
@Test
public void testInsert() {
User user = new User();
user.setAge(4);
user.setName("zyh");
user.setEmail("1111111@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4ofAEEN-1628931210296)(MyBatis—plus.assets/image-20210807162845523.png)]
思考:没有设置id,为什么会插入id呢?
主键生成策略:雪花算法
开源的分布式ID生成ID算法,类型为Long
核心思想:snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是∶使用41bit作为毫秒数,10bit作为机器的ID (5个bit是数据中心,9个bit的机器ID ),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。
主键自增:
@TableId(type = IdType.AUTO)//主键自增,需要数据库同步设置自增
private Long id;
//其他属性
public enum IdType {
AUTO(0),//自增
NONE(1),//未设置主键
INPUT(2),//手动输入
ID_WORKER(3),//默认全局Id
UUID(4),//全局唯一ID
ID_WORKER_STR(5);//截取字符串表示
}
测试update
@Test
public void testUpdate() {
User user = new User();
user.setName("test");
user.setAge(18);
user.setId(5L);
userMapper.updateById(user);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7azc10GH-1628931210298)(MyBatis—plus.assets/image-20210807165415631.png)]
会自动帮我们完成Sql拼接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R5O88kM7-1628931210300)(MyBatis—plus.assets/image-20210807165620882.png)]
自动填充
方式一:数据库级别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ld4BIgg7-1628931210303)(MyBatis—plus.assets/image-20210807165946535.png)]
再次insert测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRd470xF-1628931210304)(MyBatis—plus.assets/image-20210807170452623.png)]
发现会自动帮我们创建时间并更新时间
方式二:代码级别
1、删除数据库中属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEKpq0AN-1628931210305)(MyBatis—plus.assets/image-20210807171918298.png)]
2、在实体类中的属性加注解
@TableField(fill = FieldFill.INSERT)//插入时填充属性
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//插入更新时填充属性
private Date updateTime;
3、编写处理器处理注解
@Component
@Slf4j
public class AutoFillHandler implements MetaObjectHandler {
//插入时的处理策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("开始填充属性");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时的处理策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("开始填充属性");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
乐观锁
乐观锁:总是认为所有操作不会出现问题,无论什么操作都不会加锁!如果出现了问题,就去更新值测试
悲观锁:总是认为所有操作都会出问题,无论什么操作都会加锁!再去操作!
乐观锁插件
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
1、给数据库加上version字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9zykG0Av-1628931210305)(MyBatis—plus.assets/image-20210807175437388.png)]
2、实体类添加属性,加上@Version注解
@Version//Mybatis_plus乐观锁注解
private Integer verison;
3、注册乐观锁拦截器插件
@Configuration
@MapperScan("com.zyh.mybatisplusdemo.mapper")
@EnableTransactionManagement//自动开始事务管理
public class MyBatisPlusConfig {
/**
* 注册乐观锁插件
* @return OptimisticLockerInterceptor
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
4、测试
乐观锁成功
@Test
public void testOptimisticLocker() {
//1、查询用户信息
User user = userMapper.selectById(1L);
//2、修改用户先息
user.setName("testOptimisticLock");
user.setEmail("testOptimisticLock@qq.com");
//3、更新用户信息
userMapper.updateById(user);
}
乐观锁失败
@Test
public void testOptimiticLockerFail() {
//模拟线程1修改属性
User user1 = userMapper.selectById(1L);
user1.setName("testOptimisticLock111");
user1.setEmail("testOptimisticLock@qq11.com");
//模拟线程2修改属性
User user2 = userMapper.selectById(1L);
user2.setName("testOptimisticLock222");
user2.setEmail("testOptimisticLock@qq22.com");
userMapper.updateById(user1);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e1ZYpYRM-1628931210306)(MyBatis—plus.assets/image-20210807183509696.png)]
查询操作
selectById:单个查询
@Test
public void testSelectById() {
// User user = new User();
// user.setEmail("test@test.com");
// user.setName("testSelectById");
// user.setAge(2);
User user1 = userMapper.selectById(3L);
System.out.println(user1);
}
selectByBatchIds:批量查询
public void testSelectByBatchIds() {
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
userList.forEach(System.out::println);
}
条件查询map:selectByMap
@Test
public void testSelectByMap() {
HashMap<String, Object> map = new HashMap<>();
//自定义查询
//key是数据库对应字段,value是数据库字段的值
//Mybatis_plus会去数据库匹配符合map中所有条件的数据并返回一个List
map.put("name","Tom");
map.put("id",3L);
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}
执行的sql及结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a6SDl9o7-1628931210307)(MyBatis—plus.assets/image-20210807194004190.png)]
分页查询
1、在MyBatisPlusConfig中加入分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2、使用Page对象即可
@Test
public void testPageSelect() {
//Page<当前页,页面大小>
Page<User> page = new Page<>(1,6);
userMapper.selectPage(page,null);
//拿到当前页的记录
page.getRecords().forEach(System.out::println);
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uRPvv53A-1628931210307)(MyBatis—plus.assets/image-20210807200356976.png)]
删除操作
根据id删除数据
// 根据id删除数据@Testpublic void testDeleteById() { userMapper.deleteById(1423923643572228098L);}
根据ids列表删除
//根据ids删除@Testpublic void testDeleteByIds() { userMapper.deleteBatchIds(Arrays.asList(1423923972787326978L,1423923972787326979L, 1423923972787326980L));
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rb2WeZre-1628931210311)(MyBatis—plus.assets/image-20210807201322187.png)]
根据map条件查询
//根据map条件查询@Testpublic void testDeleteMap() { HashMap<String, Object> map = new HashMap<>(); map.put("name","test"); map.put("age",23); userMapper.deleteByMap(map);}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KFuFkAQv-1628931210312)(MyBatis—plus.assets/image-20210807201727641.png)]
逻辑删除
物理删除:在数据库中直接移除数据
逻辑删除:在数据库中没有移除,而是通过一个变量使数据失效,类似于回收站的功能!
测试:
在数据库中加入deleted字段,在实体类加入属性
@TableLogicprivate Integer deleted;
在配置类中加入逻辑删除插件
/*** 逻辑删除插件*/@Beanpublic ISqlInjector sqlInjector() { return new LogicSqlInjector();}
yml中加入配置
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置日志 global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0
测试删除
//逻辑删除@Testpublic void logicDelete() { userMapper.deleteById(1L);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1tQfmIyd-1628931210313)(MyBatis—plus.assets/image-20210808231214843.png)]
执行后可以看到并没有直接删除,而是将数据库中deleted字段值更改为1,执行的是update操作
再来测试一下select操作,看看数据是否还能被正常查询到
@Testpublic void testLogic() { User user = userMapper.selectById(1L); System.out.println(user);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IhiHXD7G-1628931210314)(MyBatis—plus.assets/image-20210808231642215.png)]
查询不到这个用户,逻辑删除插件会帮我们过滤deleted字段值为1的数据,但是在数据库中这个数据仍然存在。
性能分析插件
在平时开发中,会遇到一些慢查询,MP提供了性能分析插件
1、导入插件
//sql性能分析工具@Bean@Profile({"dev", "test"})public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); //sql执行最大时间 performanceInterceptor.setMaxTime(100); performanceInterceptor.setFormat(true); return performanceInterceptor;}
2、在配置yml
spring: profiles: active: dev
条件构造器Wrapper
使用wrapper可以替代一些复杂的sql,使用wrapper中的方法来加入条件执行sql
测试1:查询name不为空、email不为空,年龄大于12的用户信息
@Testpublic void testWrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name") .isNotNull("email") .ge("age", 12);//g:大于 e:equals userMapper.selectList(wrapper).forEach(System.out::println);}
测试2:查询单个数据使用selectOne,多个数据可以使用map或list
@Testpublic void test1Wrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name","Tom"); System.out.println(userMapper.selectOne(wrapper));}
测试3:Between and ,年龄在20-30之间
@Testpublic void test1Wrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between("age",20,30); Integer count = userMapper.selectCount(wrapper);//查询的区间结果数 System.out.println(count);}
测试4:模糊查询
//模糊查询,名字中不包含y的@Testpublic void test2Wrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.notLike("name","y"); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println);}
测试5:内查询
//内查询@Testpublic void test3Wrapper() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.inSql("id","select id from user where id > 1"); userMapper.selectList(wrapper).forEach(System.out::println);}
测试6:降序升序
@Testpublic void test6() { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.orderByAsc("id"); userMapper.selectObjs(wrapper).forEach(System.out::println);}