MybatisPlus学习笔记
MybatisPlus
官网:MyBatis-Plus (baomidou.com)
=简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
#特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速入门
地址:快速开始 | MyBatis-Plus (baomidou.com)
步骤
- 创建数据库mybatis_plus
- 创建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)
)ENGINE=INNODB DEFAULT CHARSET=utf8 ;
-- 真实开发中,version(乐观锁)、deleted(逻辑删除),gmt_create(创建时间)、gmt_modifed(修改时间)
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初始化!
-
导入依赖
org.projectlombok lombok mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter 3.0.5 说明:不要同时导入mybatis和mybatisplus,,可能会有版本的冲突;
-
连接数据库,这一步和mybatis相同
#mysql8 驱动与mysql5不同 需要添加时区 spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.password=onmyown123 spring.datasource.username=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
6、传统方式pojo-dao(连接数据库,配置mapper.xml)-service-controller
6、使用了mybatis-plus之后
-
pojo
@Data @AllArgsConstructor @NoArgsConstructor @ToString public class User { private Long id ; private String name ; private int age ; private String email; };
-
mapper
//在对应的mapper上继承基本的类BaseMapper @Mapper public interface UserMapper extends BaseMapper
{ //Dao配置好了,直接调用父类方法就好了 } -
测试
@SpringBootTest class MybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { List
users = userMapper.selectList(null); users.forEach(System.out::println); } }
sql和方法mybatisPlus帮我们写好了
上述入门案例很鲜明地吐出了内置mapper的优点,强大的CRUD操作
配置日志
我们现在的sql和方法都不可见,我们希望知道它是怎么执行的,所以我们要看日志!
#配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
CRUD扩展
插入操作
insert插入
@Test
void testInsert(){
User user = new User();
user.setName("冯宝宝");
user.setAge(100);
user.setEmail("123456@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
System.out.println(user);
}
测试:
(这里需要注意User实体类的id参数类型是Long(long不是包装类,MybatisPlus会自动补0))
我们发现id被自动补了一个很长的数字
数据库插入的id的默认值为:全局的唯一id
主键生成策略
分布式系统唯一id生成:
雪花算法:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
主键自增
我们需要配置主键自增
-
数据库表种的id改为自增
-
在实体类id上添加@TableId(type = IdType.AUTO)
-
再次测试插入
这样设置后,不需要设置主键id,mybatisPlus也能帮助我们自增id
其它源码解释
public enum IdType {
AUTO, //数据库id自增
NONE, //未设置主键
INPUT, //手动输入
ID_WORKER, //默认的全局唯一id 数字表示
UUID, //全局唯一id uuid
ID_WORKER_STR;//默认的全局唯一id 字符串表示
private int key;
private IdType(int key) { /* compiled code */ }
public int getKey() { /* compiled code */ }
}
更新操作
update
@Test
void testUpdate(){
User user = new User();
user.setId(6L);
user.setName("冯宝宝");
user.setAge(100);
int i = userMapper.updateById(user);
}
测试结果:
自动完成动态sql设置!
自动填充
创建时间、修改时间,这些个操作一律都是自动完成的,我们不希望手动更新
阿里巴巴开发手册:所有的数据库表:gtm_create,gtm_modified几乎所有的表都要配置上!而且需要自动化!
数据库级别(工作中不允许修改数据库的)
- 在user表种新增create_time,update_time字段
-
再次测试修改方法,先把实体类同步
@Test void testUpdate(){ User user = new User(); user.setId(6L); user.setName("阮丰"); user.setAge(80); int i = userMapper.updateById(user); System.out.println(i); }
然后update_time在修改代码执行后自动更新了!
代码级别
- 先将数据库create_time,update_time的默认值去掉,再将update_time的更新去掉
- 在实体类的字段上增加注解@TableFiled
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
- 编辑处理器来处理这些个注解即可~
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入操作的自动填充
@Override
public void insertFill(MetaObject metaObject) {
//default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
log.info("start insertFill");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新操作的自动填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("start updateFill");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
-
测试插入
-
测试更新,观察时间!
乐观锁
乐观锁:做什么事都不会出现问题,无论干什么都不会去上锁,等到出现问题了再去更新值测试
悲观锁:做什么事都会出现问题,无论干什么都会去上锁,
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁理解:
-- 假设有两个线程
-- A:
update user set name="abc",set version=2
where id=2 and version=1;
-- B:
update user set name="abc",set version=2
where id=2 and version=1;
-- 假设B线程抢先完成,version=2,那么A线程就会失败
乐观锁实现
-
数据库添加version字段,设置默认值为1
-
实体类添加version字段
@Version private Integer version;
-
注册组件
//扫描Mapper包 @MapperScan("com.yfm.mapper") @EnableTransactionManagement @Configuration public class MybatisPlusConfig { //注册乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
-
测试一下!
//测试乐观锁成功 @Test void testOptimisticLocker(){ User user = userMapper.selectById(1L); user.setName("通天箓"); userMapper.updateById(user); } //测试乐观锁失败,多线程 @Test void testOptimisticLocker2(){ //线程A User user2 = userMapper.selectById(1L); user2.setName("通天箓22"); //线程B,插队更新 User user3 = userMapper.selectById(1L); user3.setName("通天箓33"); userMapper.updateById(user3); //当userMapper.updateById(user3);执行完以后,version=3,而查询出来的version=2,所以下面会失败 userMapper.updateById(user2); }
5.测试结果
==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=? ==> Parameters: 通天箓33(String), 18(Integer), test1@baomidou.com(String), 3(Integer), 2021-11-21 17:04:33.0(Timestamp), 2021-11-22 09:22:57.102(Timestamp), 1(Long), 2(Integer) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7afb9c93] Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@f4f843f] was not registered for synchronization because synchronization is not active 2021-11-22 09:22:57.116 INFO 17600 --- [ main] com.yfm.handler.MyMetaObjectHandler : start updateFill JDBC Connection [HikariProxyConnection@2069378030 wrapping com.mysql.cj.jdbc.ConnectionImpl@6eed46e9] will not be managed by Spring ==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=? ==> Parameters: 通天箓22(String), 18(Integer), test1@baomidou.com(String), 3(Integer), 2021-11-21 17:04:33.0(Timestamp), 2021-11-22 09:22:57.116(Timestamp), 1(Long), 2(Integer) <== Updates: 0
查询操作
@Test
void testSelectById(){
User user = userMapper.selectById("1L");
System.out.println(user);
}
//测试批量查询
@Test
void testSelectByBatchIds(){
List users = userMapper.selectBatchIds(Arrays.asList(1, 6));
users.forEach(System.out::println);
}
//测试Map多条件查询
@Test
void testSelectByMap(){
HashMap map = new HashMap();
map.put("name","无根生");
map.put("age",70);
List users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
分页查询
分页在网站上用的非常多
- 原始limit
- PageHelper第三方分页插件-
- MP其实也内置了分页插件!
如何使用
-
配置拦截器组件即可、
@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; }
-
直接使用Page对象即可!
//测试分页
@Test
void testPage(){
//参数1:当前页
//参数2:每页大小
Page userPage = new Page<>(1,5);
userMapper.selectPage(userPage,null);
//打印输出
userPage.getRecords().forEach(System.out::println);
//提示:分页操作之前都会先查询表中记录总数
}
删除操作
基本的删除操作:
deleteById
deleteBatchIds
deleteByMap
逻辑删除
物理删除:从数据库中直接删除
逻辑删除:在数据库中没有被移除,而是通过一个变量让他失效!deleted=0 => deleted=1
管理员可以查看被删除的记录!防止数据的丢失,相当于回收站的作用。如果真要删除,再通过deleted=1删除数据
测试一下:
1、在数据库中添加deleted字段
2、在实体类中新增deleted属性
@TableLogic //逻辑删除
private Integer deleted;
3、逻辑删除配置,注册组件
//从MP3.1.1版本起就不需要注入了
//逻辑删除组件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
#配置逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
4、测试删除
@Test
void testDelete(){
int i = userMapper.deleteById(1462326539445100546L);
System.out.println(i);
}
结果:删除操作转变为了更新操作,修改deleted字段!
此时再查询就查不到了,在查询时会加入deleted=0条件!
性能分析插件
我们在平时的开发中,会遇到一些慢sql。
MP也提供性能分析插件,如果超过这个时间就停止
步骤:
1、导入组件
//该组件在新版本被移除了
@Bean
@Profile({"dev","test"}) //设置dev test环境开启,保证我们的效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor=new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//ms 设置sql执行的最大时间,如果超过了就不执行
performanceInterceptor.setFormat(true); //sql格式化打印
return performanceInterceptor;
}
注意在application.properties中添加开发环境设置
#设置为开发环境
spring.profiles.active=dev
2、测试使用!
条件构造器
十分重要:Wrapper
我们写一些复杂的sql就可以用它来替代!
测试:
测试1:
@Test
void contextLoads() {
//注意要加泛型,不加就不能链式增加条件了!
QueryWrapper wrapper = new QueryWrapper<>();
//测试查询年龄大于30,邮箱,姓名不为空的记录
wrapper
.isNotNull("email")
.isNotNull("name")
//ge:大于等于。
.ge("age",30); //和我们刚才学的Map对比一下
userMapper.selectList(wrapper).forEach(System.out::println);
}
测试2:
@Test
void test2() {
//查询年龄等于18的记录
QueryWrapper wrapper = new QueryWrapper<>();
//注意要加泛型,不加就不能链式增加条件了!
wrapper
.eq("age",18);
User user = userMapper.selectOne(wrapper);//查询一个数据。出现多个结果使用List或Map
System.out.println(user);
}
测试3:
@Test
void test3() {
//查询年龄在18——50之间的结果数
QueryWrapper wrapper = new QueryWrapper<>();
//注意要加泛型,不加就不能链式增加条件了!
wrapper
.between("age",18,50);
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
测试4:记住,查看输出的SQL进行分析
//模糊查询
@Test
void test4() {
//注意要加泛型,不加就不能链式增加条件了!
QueryWrapper wrapper = new QueryWrapper<>();
//这里like的right、left的意思是'%'在right,还是left
wrapper.likeRight("email","t") //查询邮箱以t开头的记录
.notLike("name","t"); //名字不含t
List
-- Execute SQL:
SELECT
id,
name,
age,
email,
version,
deleted,
create_time,
update_time
FROM
user
WHERE
deleted=0
AND email LIKE 't%'
AND name NOT LIKE '%t%'
测试5:
//子查询
@Test
void test5() {
//注意要加泛型,不加就不能链式增加条件了!
QueryWrapper wrapper = new QueryWrapper<>();
//id在子查询中查出来
wrapper.inSql("id","select id from user where id>3");
List
结果:
Execute SQL:
SELECT
id,
name,
age,
email,
version,
deleted,
create_time,
update_time
FROM
user
WHERE
deleted=0
AND id IN (
select
id
from
user
where
id>3
)
测试6:
//排序
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age");
关于条件构造器的内容还有很多,比如分组等等,作为课后练习
代码生成器
mapper ,pojo,service,controller都帮你写!
使用下面代码前提:
导入MybatisPlus依赖,狂神视频用的是3.0.5版本的
初次之外,还要导入模板引擎依赖
com.baomidou
mybatis-plus-boot-starter
3.0.5
org.apache.velocity
velocity-engine-core
2.3
public static void main(String[] args) {
//需要构建一个 代码自动生成器 对象
AutoGenerator mpg = new AutoGenerator();
//配置策略
//1 全局配置
GlobalConfig gc = new GlobalConfig();
//D:\java\idea\IntelliJ IDEA 2020.1\IDEACode\mybatisPlus\mybatis_plus
String projectPath = System.getProperty("user.dir");//代表项目的当前路径
gc.setOutputDir(projectPath+"/src/main/java");
gc.setAuthor("yfm");
gc.setOpen(false);//是否打开资源管理器
gc.setFileOverride(false); //是否覆盖原来的代码
gc.setServiceName("%sService"); //去Service的I前缀
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
//2 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setUsername("root");
dsc.setPassword("onmyown123");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//3 包的设置
PackageConfig pc= new PackageConfig();
pc.setModuleName("blog");//磨块命
pc.setParent("com.parent");//包名 结果就是 com.parent.blog
pc.setEntity("entity");//实体类包名
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//4 策略配置 ,逻辑删除,乐观锁等等
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user");//设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); //自动lombok
//逻辑删除
strategy.setLogicDeleteFieldName("deleted");
//乐观锁
strategy.setVersionFieldName("version");
//自动填充
TableFill createTime =new TableFill("createTime", FieldFill.INSERT);
TableFill updateTime =new TableFill("updateTime", FieldFill.INSERT_UPDATE);
ArrayList tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute();//执行
}