目录
提示:当前使用的版本为mybatis-plus3.5.1
一、简介
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。简化CRUD操作。
二、快速入门
使用第三方组件步骤:
- 导入对应依赖
- 研究依赖如何配置
- 代码如何编写
- 提高扩展技术能力
步骤基于 spring boot项目
- 创建数据库和表
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)
);
# 添加数据
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');
- 在spring boot工程中导入Maven依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
- 连接数据库
spring:
datasource:
# mysql 8 驱动不同com.mysql.cj.jdbc.Driver 需要增加时区的配置 serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/database?useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: admin
- 创建实体类User.java(必须与表名保持一致)
@Data
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 创建UserMapper接口并继承通用Mapper(BaseMapper)泛型为User型
public interface UserMapper extends BaseMapper<User> {
}
- 在springboot启动类上添加@MapperScan注解
@SpringBootApplication
@MapperScan("com.example.mybatisplus.mapper") //mapper接口所在的包路径
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
- 测试
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//查询全部数据 参数为wrapper条件构造器 暂且为null
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
}
运行输出
```
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)
```
三、配置日志
配置日志便于查看执行的sql语句
在spring boot配置文件中配置日志实现
# 第一种方式
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 第二种方式
logging:
level:
# 指定mapper接口所在的包
com.example.mybatisplus.mapper: debug
测试输出结果
第一种方式
JDBC Connection [HikariProxyConnection@1079733621 wrapping com.mysql.cj.jdbc.ConnectionImpl@682abca7] will not be managed by Spring
==> Preparing: SELECT id,name,age,email FROM user //执行的SQL
==> Parameters: //SQL的参数
//查询的结果
<== 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@66f28a1f]
第二种方式
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Total: 5
四、CRUD和扩展
1.insert
测试单表插入
@Test
public void testInsert() {
User user = User.builder()
.name("zhangsan")
.age(22)
.email("123@qwe").build();
//受影响行数
int result = userMapper.insert(user);
System.out.println(result);
//发现id会自动填充
System.out.println(user);
List<User> users = userMapper.selectList(null);
for (User u : users) {
System.out.println(u);
}
}
运行结果
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 106717185(Integer), zhangsan(String), 22(Integer), 123@qwe(String)
<== Updates: 1
1
User(id=106717185, name=zhangsan, age=22, email=123@qwe)
...
这里MP使用了雪花算法,参考:
雪花算法(snowflake):snowflake算法是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。可以几乎保证全球唯一。
分布式系统唯一ID生成:
分布式系统唯一ID生成方案汇总
主键自增策略,默认是
IdType.NONE
配置主键自增
- 在实体类主键属性上添加@TableId(type=IdType.AUTO)注解
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
}
- 把数据表注解字段设置为自增(auto_increment)
- 测试插入运行结果
User(id=106717186, name=zhangsan, age=22, email=123@qwe)
IdType枚举属性
public enum IdType {
AUTO(0), //ID自增
NONE(1), //不设置主键自增
INPUT(2), //手动输入
ASSIGN_ID(3),
ASSIGN_UUID(4);
}
2.update
单表更新
@Test
public void testUpdate(){
User user = User.builder()
.id(106717186)
.name("lisi123")
//.age(23)
.email("321@qwe").build();
//更新操作会自动拼接sql
int result = userMapper.updateById(user);
System.out.println(result);
}
运行结果
==> Preparing: UPDATE user SET name=?, email=? WHERE id=?
==> Parameters: lisi123(String), 321@qwe(String), 106717186(Integer)
<== Updates: 1
1
3.乐观锁插件
乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁!如果出现问题就再次更新值测试。
悲观锁:顾名思义十分乐观,它总是认为会出现问题,无论干什么都会上锁!再去操作。
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
测试MP的乐观锁插件
-
在数据表中添加version字段
- 同步实体类
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
@Version //乐观锁Version注解
private Integer version;
@TableField(fill = FieldFill.INSERT)
private Date createDate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date modifyDate;
}
- 注册组件,创建MP配置类,向MybatisPlusInterceptor拦截器中添加OptimisticLockerInnerInterceptor拦截器
@Configuration
@MapperScan("com.example.mybatisplus.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
- 测试乐观锁
@Test
public void testOptimismLock(){
//模拟线程A
User user1 = userMapper.selectById(1);
user1.setAge(22);
//模拟线程B 执行了插队操作
User user2 = userMapper.selectById(1);
user2.setAge(18);
//线程B抢先更新
userMapper.updateById(user2);
//由于线程B已经更新完成了,线程A更新失败
userMapper.updateById(user1);
}
- 运行结果
==> Preparing: SELECT id,name,age,email,version,create_date,modify_date FROM user WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, age, email, version, create_date, modify_date
<== Row: 1, Jone, 18, test1@baomidou.com, 1, 2022-02-07 17:39:39, 2022-02-07 17:39:40
<== Total: 1
==> Preparing: SELECT id,name,age,email,version,create_date,modify_date FROM user WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, age, email, version, create_date, modify_date
<== Row: 1, Jone, 18, test1@baomidou.com, 1, 2022-02-07 17:39:39, 2022-02-07 17:39:40
<== Total: 1
==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_date=?, modify_date=? WHERE id=? AND version=?
==> Parameters: Jone(String), 23(Integer), test1@baomidou.com(String), 2(Integer), 2022-02-07 17:39:39.0(Timestamp), 2022-02-07 19:21:30.42(Timestamp), 1(Integer), 1(Integer)
<== Updates: 1
==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_date=?, modify_date=? WHERE id=? AND version=?
==> Parameters: Jone(String), 22(Integer), test1@baomidou.com(String), 2(Integer), 2022-02-07 17:39:39.0(Timestamp), 2022-02-07 19:21:30.441(Timestamp), 1(Integer), 1(Integer)
<== Updates: 0
4.select
@Test
public void testSelect() {
//根据ID查询
User user = userMapper.selectById(1);
System.out.println(user);
//根据多个ID查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3, 4, 5));
System.out.println(users.toString());
//自定义条件查询之一使用Map操作
Map<String, Object> map = new HashMap<>();
map.put("age",18);
map.put("name","Jone");
List<User> userList = userMapper.selectByMap(map);
System.out.println(userList);
}
5.分页查询
配置步骤
- 在MP配置类中配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
- 使用selectPage()测试分页
@Test
public void testPageSelect() {
//Page构造器参数(currentPage,PageSize)
Page<User> page = new Page<>(3, 5);
userMapper.selectPage(page, null);
//获取分页记录
List<User> users = page.getRecords();
for (User user : users) {
System.out.println(user);
}
//获取当前页
System.out.println(page.getCurrent());
//获取总页数
System.out.println(page.getPages());
//获取当前页记录数
System.out.println(page.getSize());
//获取总记录数
System.out.println(page.getTotal());
}
6.delete
@Test
public void testDelete() {
int result = 0;
//根据ID删除
result = userMapper.deleteById(106717187);
System.out.println(result);
//根据多个ID删除
result = userMapper.deleteBatchIds(Arrays.asList(106717188, 106717189));
System.out.println(result);
//自定义删除条件之一使用Map操作
Map<String,Object> map = new HashMap<>();
map.put("name","Tue Feb 08 10:25:31 CST 2022");
result = userMapper.deleteByMap(map);
System.out.println(result);
}
7.逻辑删除
逻辑删除:逻辑删除本质是修改,数据库中没有被删除,而是使用另一个变量(字段),让它失效
物理删除:从数据库中永久删除
作用:管理员可以查看被删除的记录,防止数据丢失,类似于回收站
实现步骤
-
在数据表中添加deleted字段
- 同步实体类
@TableField(fill = FieldFill.INSERT)
@TableLogic //添加逻辑删除注解
private Integer deleted;
- 在springboot配置文件中添加配置
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 测试
@Test
public void testLogicDelete() {
//删除
int result = userMapper.deleteById(106717193);
System.out.println(result);
//查询
User user = userMapper.selectById(106717193);
System.out.println(user);
}
输出日志
```
//更新deleted字段值,并添加deleted=0条件
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 106717193(Integer)
<== Updates: 1
//查询语句也添加deleted=0条件
==> Preparing: SELECT id,name,age,email,deleted,version,create_date,modify_date FROM user WHERE id=? AND deleted=0
==> Parameters: 106717193(Integer)
<== Total: 0
```
8.执行SQL分析打印
该功能依赖
p6spy
组件
- p6spy依赖Maven
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>最新版本</version>
</dependency>
- 在springboot配置文件中配置
spring:
datasource:
# driver-class-name 改为com.p6spy.engine.spy.P6SpyDriver
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
# url前缀为jdbc:p6spy:
url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus
...
- 创建p6spy配置文件spy.properties
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,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=1
-
运行测试输出结果
五、自动填充字段
例如几乎所有的数据表都要有create_date、modify_date字段,有些时候不能直接在数据表上更改
方式一:数据库级别
-
在表中新增字段create_date、modify_date
-
同步实体类
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String email;
private Date createDate;
private Date modifyDate;
}
-
更新测试
方式二:代码级别
- 删除数据表字段的默认值
- 在实体类属性上添加注解@TableField注解
@TableField(fill = FieldFill.INSERT)
private Date createDate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date modifyDate;
- 创建数据填充处理器实现MetaObjectHandler接口
@Slf4j
@Component
public class DataFillHandler implements MetaObjectHandler {
//插入时填充
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill....");
this.setFieldValByName("createDate",new Date(),metaObject);
this.setFieldValByName("modifyDate",new Date(),metaObject);
}
//更新时填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill....");
this.setFieldValByName("modifyDate",new Date(),metaObject);
}
}
-
测试插入
-
测试更新
六、条件构造器
多用于复杂SQL
AbstractWrapper
有两个子类
1.QueryWrapper
示例:
@Test
public void test1() {
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.isNotNull("name") //name字段不等于null
.isNotNull("email")
.ge("age", 20); //age字段大于等于20
userMapper.selectList(wrapper);
}
@Test
public void test2() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "Tom"); //name字段等于‘Tom’的记录
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 25); //age字段在20~25区间的记录
Long count = userMapper.selectCount(wrapper);
System.out.println(count);
}
@Test
public void test4() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name", "a") //模糊查询不包含‘a’的记录
.likeRight("email", "3"); //模糊查询
userMapper.selectList(wrapper);
}
@Test
public void test5() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where age > 22"); //子查询的id与父查询id匹配
userMapper.selectList(wrapper);
}
@Test
public void test6() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id"); //将ID降序排序
userMapper.selectList(wrapper);
}
2.UpdateWrapper
示例:
@Test
public void test7(){
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
//批量更新 ID为2,3 的记录字段name为‘张三
wrapper.set("name","张三")
.in("id", Arrays.asList(2,3));
userMapper.update(new User(),wrapper);
}
七、代码自动生成器
具体参考:
Mybatis-plus最新代码生成器(3.5.1+)的使用
示例:
public class GeneratorCode {
private static String url = "jdbc:mysql://localhost:3306/club?useSSL=false&useUnicode=true&characterEncoding=utf-8";
private static String username = "root";
private static String password = "admin";
public static void main(String[] args) {
List<String> tables = new ArrayList<>();
tables.add("blog");
tables.add("commodity");
tables.add("commodity_type");
tables.add("working");
tables.add("user");
FastAutoGenerator.create(url,username,password)
.globalConfig(builder -> {
builder.author("mr.ck") //作者
.outputDir("E:\\code\\JavaIDEA\\自学\\mybatis-plus\\mybatis-plus-generator\\src\\main\\java") //输出路径(写到java目录)
.enableSwagger() //开启swagger
.commentDate("yyyy-MM-dd")
.fileOverride(); //开启覆盖之前生成的文件
})
.packageConfig(builder -> {
builder.parent("com.cheng.mybatisplusgenerator")
.moduleName("generator")
.entity("entity")
.service("service")
.serviceImpl("serviceImpl")
.controller("controller")
.mapper("mapper")
.xml("mapper")
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,"E:\\code\\JavaIDEA\\自学\\mybatis-plus\\mybatis-plus-generator\\src\\main\\resources\\mapper"));
})
.strategyConfig(builder -> {
builder.addInclude(tables)
.addTablePrefix("t_")
.serviceBuilder()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
.entityBuilder()
.enableLombok()
.logicDeleteColumnName("deleted")
.enableTableFieldAnnotation()
.controllerBuilder()
.formatFileName("%sController")
.enableRestStyle()
.mapperBuilder()
.enableBaseResultMap() //生成通用的resultMap
.superClass(BaseMapper.class)
.formatMapperFileName("%sMapper")
.enableMapperAnnotation()
.formatXmlFileName("%sMapper");
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}