目录
快速开始 | MyBatis-Plus (baomidou.com)
1.1 快速上手
1.创建数据库,创建表
- 建表sql
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');
2.新建一个springboot项目
3.添加依赖
<dependencies> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
4.application.properties
# mysql 5.xx spring.datasource.username=root spring.datasource.password=123456 spring.datasource.url=jdbc:mysql://localhost:3306/db2021 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
5.创建实体类pojo
package com.IT.mybatis.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
6.mapper接口
package com.IT.mybatis.mapper; import com.IT.mybatis.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.springframework.stereotype.Repository; /** * 在对应的mapper上面 继承 基本的类 BaseMapper * 所有的CRUD操作都已经编写完成 * 不需要再像mybatis一样编写映射文件 */ @Repository public interface Usermapper extends BaseMapper<User> { }
7.主启动类
package com.IT.mybatis; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; // 在 SpringBoot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹 @MapperScan("com.IT.mybatis.mapper") @SpringBootApplication public class SpringbootMybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(SpringbootMybatisPlusApplication.class, args); } }
8.测试
package com.IT.mybatis; import com.IT.mybatis.mapper.UserMapper; import com.IT.mybatis.pojo.User; import com.baomidou.mybatisplus.core.toolkit.Assert; 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 SpringbootMybatisPlusApplicationTests { // 继承了BaseMapper,父类中的方法都可以使用,当然,我们也可以编写自己的拓展方法 @Autowired private UserMapper userMapper; @Test public void testSelect() { System.out.println(("----- selectAll method test ------")); // UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件 List<User> userList = userMapper.selectList(null); userList.forEach(System.out::println); } @Test void contextLoads() { } }
测试结果:
User(id=1, name=Jone, age=18, email=test1@baomidou.com) User(id=2, name=Jack, age=20, email=test2@baomidou.com) User(id=3, name=Tom, age=28, email=test3@baomidou.com) User(id=4, name=Sandy, age=21, email=test4@baomidou.com) User(id=5, name=Billie, age=24, email=test5@baomidou.com)
通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!
从以上步骤中,我们可以看到集成
MyBatis-Plus
非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可!
1.2 配置日志
1.在application.properties文件中添加
# 配置日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2.控制台输出结果
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter. Property 'mapperLocations' was not specified. _ _ |_ _ _|_. ___ _ | _ | | |\/|_)(_| | |_\ |_)||_|_\ / | 3.3.1.tmp 2021-11-20 14:14:06.157 INFO 3972 --- [ main] .m.SpringbootMybatisPlusApplicationTests : Started SpringbootMybatisPlusApplicationTests in 3.45 seconds (JVM running for 4.783) ----- selectAll method test ------ Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@37c41ec0] was not registered for synchronization because synchronization is not active 2021-11-20 14:14:06.477 INFO 3972 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2021-11-20 14:14:06.778 INFO 3972 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. JDBC Connection [HikariProxyConnection@131532344 wrapping com.mysql.jdbc.JDBC4Connection@732f6050] will not be managed by Spring ==> Preparing: SELECT id,name,age,email FROM user ==> Parameters: <== Columns: id, name, age, email <== Row: 1, Jone, 18, test1@baomidou.com <== Row: 2, Jack, 20, test2@baomidou.com <== Row: 3, Tom, 28, test3@baomidou.com <== Row: 4, Sandy, 21, test4@baomidou.com <== Row: 5, Billie, 24, test5@baomidou.com <== Total: 5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@37c41ec0]
1.3 测试插入数据
1.测试代码
// 测试插入 @Test public void testInsert(){ User user=new User(null,"tom",99,"23424524@qq.com"); int result=userMapper.insert(user); System.out.println(result); System.out.println(user); }
2.插入结果,id自动回填
1 User(id=1461942928325693442, name=tom, age=99, email=23424524@qq.com)
bug修复:在用单元测试的时候,总是向数据库中插入两条数据,如下图
在网上找了解决办法,是这样说的:单元测试时会先build进行编译,而build的时候会执行你项目中的所有加了@Test的方法 并且具体解决步骤如下,将这个勾选上,这是跳过测试的意思
再次用单元测试的时候就可以了
1.4 测试更新数据
1.测试更新代码
// 测试更新 @Test public void testUpdate(){ User user=new User(); user.setId(5L); user.setName("hello,mybatis-plus"); // 参数是一个对象 int i=userMapper.updateById(user); System.out.println(i); }
2.测试结果
雪花算法(插曲)
SnowFlake是Twitter公司采用的一种算法,目的是在分布式系统中产生全局唯一且趋势递增的ID
组成部分(64bit)
1.第一位 占用1bit,其值始终是0,没有实际作用
2.时间戳 占用41bit,精确到毫秒,总共可以容纳约69年的时间
3.工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,做多可以容纳1024个节点
4.序列号 占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID
SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢:: 同一毫秒的ID数量 = 1024 X 4096 = 4194304
2.1 核心功能—代码生成器
我写在下面这篇文章里了Mybatis—Plus代码自动生成器超详细讲解(3.5.1+版本)_****^_^****的博客-CSDN博客
2.2 核心功能—条件构造器
通过条件构造器构造复杂SQL,过滤、筛选数据
简单案例
1.测试代码
@Test public void testSelect() { System.out.println(("----- selectAll method test ------")); // UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件 QueryWrapper<User>wrapper=new QueryWrapper<>(); // 查询条件:name不为空、email不为空、年龄大于12 wrapper.isNotNull("name") .isNotNull("email") .ge("age",12); List<User> userList = userMapper.selectList(wrapper); userList.forEach(System.out::println); }
2.查询结果
Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND (name IS NOT NULL AND email IS NOT NULL AND age >= ?) ==> Parameters: 12(Integer) <== Columns: id, name, age, email, create_time, update_time, version, deleted <== Row: 5, hello,mybatis-plus, 24, test5@baomidou.com, null, null, 1, 0 <== Row: 1461947382575894531, tom, 99, 23424524@qq.com, null, null, 1, 0 <== Row: 1461947382575894532, 测试更新, 99, 23424524@qq.com, 2021-11-20 18:18:03.0, 2021-11-25 15:04:53.0, 1, 0 <== Total: 3
3.1 拓展—配置主键自增
1.实体类的属性上添加注解
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; }
2.在数据库连接工具中修改主键为自增
3.再次测试插入
3.2 拓展—逻辑删除
说明
物理删除:从数据库中直接删除
逻辑删除:实际上只是数据失效,在数据库中没有被删除,当我们查询数据时,如果带上逻辑删除条件,那么被逻辑删除过的数据将不会被查询出来
只对自动注入的sql起效:
- 插入: 不作限制
- 查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
- 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
)- 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示
使用案例
1.数据库中增加字段
2.pojo实体类
@TableLogic // 逻辑删除 private Integer deleted;
3.application.properties文件
# 配置逻辑删除 # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) # mybatis-plus.global-config.db-config.logic-delete-field=deleted # 逻辑已删除值(默认为 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 逻辑未删除值(默认为 0) mybatis-plus.global-config.db-config.logic-not-delete-value=0
4.测试代码
// 测试逻辑删除 @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(3L,4L)); }
5.数据库结果,deleted字段值变为1
6. 逻辑删除下,测试查询操作时会自动拼接查询条件,deleted=1的数据将不会被查询出来
==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 ==> Parameters: <== Columns: id, name, age, email, create_time, update_time, version, deleted <== Row: 5, hello,mybatis-plus, 24, test5@baomidou.com, null, null, 1, 0 <== Row: 1461947382575894531, tom, 99, 23424524@qq.com, null, null, 1, 0 <== Row: 1461947382575894532, 更新了数据, 99, 23424524@qq.com, 2021-11-20 18:18:03.0, 2021-11-20 18:28:47.0, 1, 0
3.3 拓展—自动填充创建时间、修改时间
根据阿里巴巴开发手册,在数据库表中,对数据的创建、修改操作要求记录下创建时间、修改时间,并且由于数据量庞大,手工添加肯定是不行的,肯定需要自动填充,下面我们来看怎么实现这个功能?
1.在表中新增字段:create_time、update_time
2.实体类:注解填充字段
@TableField(.. fill = FieldFill.INSERT)
package com.IT.mybatis.pojo; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; @Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private Integer age; private String email; // 字段添加填充内容 @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }
3.自定义实现类MyMetaObjectHandler并继承MetaObjectHandler
package com.IT.mybatis.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { // 插入时的填充策略 @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用) } // 更新时的填充策略 @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐) } }
4.测试插入操作
@Test public void testInsert() { User user = new User((Long)null, "tom", 99, "23424524@qq.com", (LocalDateTime)null, (LocalDateTime)null); int result = this.userMapper.insert(user); System.out.println(result); System.out.println(user); }
结果:
1 User(id=1461947382575894532, name=tom, age=99, email=23424524@qq.com, createTime=2021-11-20T18:18:03.382, updateTime=null)
刷新数据库
5.测试更新操作
// 测试更新 @Test public void testUpdate(){ User user=new User(); user.setId(1461947382575894532L); user.setName("更新了数据"); // 参数是一个对象 int i=userMapper.updateById(user); System.out.println(i); }
后台输出1,更新成功!然后看一下数据库
注意事项:
- 填充原理是直接给
entity
的属性设置值!!!- 注解则是指定该属性在对应情况下必有值,如果无值则入库会是
null
MetaObjectHandler
提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null
则不填充- 字段必须声明
TableField
注解,属性fill
选择对应策略,该声明告知Mybatis-Plus
需要预留注入SQL
字段- 填充处理器
MyMetaObjectHandler
在 Spring Boot 中需要声明@Component
或@Bean
注入- 要想根据注解
FieldFill.xxx
和字段名
以及字段类型
来区分必须使用父类的strictInsertFill
或者strictUpdateFill
方法- 不需要根据任何来区分可以使用父类的
fillStrategy
方法
4.1 插件—分页
PaginationInnerInterceptor
支持的数据库
mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
1.在配置类中添加组件
// 旧版 @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; }
2.测试代码
// 测试分页查询 @Test public void testPage(){ // 页数:1,每页数据条数:5 Page<User>page=new Page<>(1,5); userMapper.selectPage(page,null); page.getRecords().forEach(System.out::println); }
3.测试结果
<== Row: 1, 22222211, 18, 99999999@qq.com, null, 2021-11-20 19:00:27.0, 4 <== Row: 2, Jack, 20, test2@baomidou.com, null, null, 1 <== Row: 3, Tom, 28, test3@baomidou.com, null, null, 1 <== Row: 4, Sandy, 21, test4@baomidou.com, null, null, 1 <== Row: 5, hello,mybatis-plus, 24, test5@baomidou.com, null, null, 1
查询第二页
// 测试分页查询 @Test public void testPage(){ // 页数:2,每页数据条数:5 Page<User>page=new Page<>(2,5); userMapper.selectPage(page,null); page.getRecords().forEach(System.out::println); }
查询结果
<== Row: 1461947382575894531, tom, 99, 23424524@qq.com, null, null, 1 <== Row: 1461947382575894532, 更新了数据, 99, 23424524@qq.com, 2021-11-20 18:18:03.0, 2021-11-20 18:28:47.0, 1 <== Total: 2
4.2 插件—乐观锁
OptimisticLockerInnerInterceptor
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
乐观锁配置步骤
1.表中增加version字段
2.实体类
@Version // 乐观锁注解 private Integer version;
说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法- 在
update(entity, wrapper)
方法下,wrapper
不能复用!!!3.注册组件
package com.IT.mybatis.config; import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; // 配置类 @Configuration // 在 SpringBoot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹 @MapperScan("com.IT.mybatis.mapper") public class MyBatisPlusConfig { // 注册乐观锁插件 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
4.测试乐观锁
// 测试乐观锁 @Test public void testOptimisticLocker(){ // 1.查询用户信息 User user=userMapper.selectById(1L); // 2.修改用户信息 user.setName("zhangsan"); user.setEmail("99999999@qq.com"); // 3.执行更新操作 userMapper.updateById(user); }
可以看到更新成功
同时我们可以模拟一下乐观锁更新失败的情况
// 测试乐观锁 @Test public void testOptimisticLocker(){ // 线程1 // 查询用户信息 User user1=userMapper.selectById(1L); // 修改用户信息 user1.setName("111111"); user1.setEmail("99999999@qq.com"); // 线程2 // 查询用户信息 User user2=userMapper.selectById(1L); // 修改用户信息 user2.setName("222222"); user2.setEmail("99999999@qq.com"); // 执行更新操作: 本来是1先执行的,但是这时候被2抢先了 userMapper.updateById(user2); userMapper.updateById(user1); }
1执行更新操作时的后台日志
==> Preparing: UPDATE user SET name=?, age=?, email=?, update_time=?, version=? WHERE id=? AND version=? ==> Parameters: 111111(String), 18(Integer), 99999999@qq.com(String), 2021-11-20T19:00:27(LocalDateTime), 3(Integer), 1(Long), 2(Integer) <== Updates: 0
在数据库中可以看到2更新成功了,而1去执行更新操作的时候,由于版本号被修改,版本对应不上,导致更新失败!
4.3 插件—性能分析
性能分析拦截器,用于输出每条SQL语句及其执行时间
新版本中推荐使用第三方拓展 执行SQL分析打印 功能
使用案例
1.引入依赖
2.application.yml
- 关键点1:url需要加上p6spy 固定的是jdbc:p6spy:数据库名…...
- 关键点2:driver-class-name指定的是p6spy的驱动
spring: datasource: username: root password: 123456 url: jdbc:p6spy:mysql://localhost:3306/db2021 driver-class-name: com.p6spy.engine.spy.P6SpyDriver
3.spy.properties
- 关键点,一定要通过driverlist指定真实的JDBC驱动
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系统记录 sql # appender=com.p6spy.engine.spy.appender.Slf4JLogger # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 实际驱动可多个 # driverlist=org.h2.Driver # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2 # 真实JDBC driver driverlist=com.mysql.jdbc.Driver
4.测试
再测试一下更新操作的用时