# 前言

代码生成器插件选择去这里:https://www.cnblogs.com/zixq/p/16726534.html

相关插件在那里面已经提到了

# 上手

MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生

PS:要打开官网需要将浏览器的广告拦截插件添加白名单,具体操作访问官网即可看到

官网入手示例:https://www.baomidou.com/getting-started/

1、依赖

<!-- 这个starter包含对mybatis的自动装配,完全可以替换掉Mybatis的starter -->

<!-- Spring Boot2 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.5.7</version>
</dependency> <!-- Spring Boot3 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>

2、接口 extends BaseMapper<T>。该接口定义了单表CRUD的一些常用API

// 泛型 User 是与数据库对应的实体类
public interface UserMapper extends BaseMapper<User> {
}

Mybatis-Plus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:

  • MybatisPlus会把PO实体的类名驼峰转下划线作为表名

  • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型

  • MybatisPlus会把名为id的字段作为主键

但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息

3、然后就可以像调用自定义mapper接口方法一样调用了

# 注解

更多注解看官网:https://www.baomidou.com/reference/annotation/

@TableName:用来指定表名

@TableId:用来指定表中的主键字段信息。IdType枚举如下

  • AUTO:数据库自增长
  • INPUT:通过set方法自行输入
  • ASSIGN_ID:分配 ID(默认方式),接口IdentifierGenerator的方法nextId来生成id,默认实现类为DefaultIdentifierGenerator雪花算法

@TableField:用来指定表中的普通字段信息

使用@TableField的常见场景:

  • 成员变量名与数据库字段名不一致
  • 成员变量名以is开头,且是布尔值
  • 成员变量名与数据库关键字冲突
  • 成员变量不是数据库字段

# 配置

兼容MyBatis的配置

另外更多配置,查看官网:https://www.baomidou.com/reference/

mybatis-plus:
type-aliases-package: com.zixq.mp.domain.po # 别名扫描包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
configuration: map-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id为雪花算法生成 这个优先级没有使用 @TableId 注解高
update-strategy: not_null # 更新策略:只更新非空字段
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-field: deleted
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

逻辑删除进行上面配置后mp就可自动实现delete变update操作了,但逻辑删除会占用空间,影响性能,所以可采用删除前将数据迁移到另一张表中

# 条件构造器

1、QueryWrapper 和 LambdaQueryWrapper一般用来构建select、delete、update的where条件部分

2、UpdateWrapper 和 LambdaUpdateWrapper通常只有在set语句比较特殊才使用,如set的是 money = money - 1000

3、尽量使用LambdaQueryWrapper 和 LambdaUpdateWrapper,避免硬编码

BaseMapper包含的Wrapper构建起

AbstractWrapper

QueryWrapper

UpdateWrapper

# 示例

1、QueryWrapper根据指定字段和条件查询

SQL

SELECT id, username, info, balance
FROM `user`
WHERE username LIKE ? AND balance >= ?;

mp构建:

// 1、构建查询条件
Querywrapper<User> wrapper = new QueryWrapper<User()
.select("id", "username", "info","balance")
.like("username", "o")
·ge("balance", 1000); // 2、查询
List<User> users = userMapper.selectList(wrapper); // 使用 LambdaWrapper 的方式
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User()
.select(User::getId, User::getUsername, User::getInfo,User::getBalance)
.like(User::getUsername, "o")
·ge(User::getBalance, 1000); // 2、查询
List<User> users = userMapper.selectList(wrapper);

2、QueryWrapper更新构建

SQL

UPDATE `user`
SET balance = 2000
WHERE username = "jack"

mp构建:

// 1、要更新的数据
User user = new User();
user.setBalance(2000); // 2、更新的条件
QueryWrapper<User> wrapper = new Querywrapper<User>()
.eq("username", "jack"); // 3、执行更新
userMapper.update(user, wrapper);

3、UpdateWrapper构建set的特殊情况

SQL:

UPDATE user
SET balance = balance - 200
WHERE id in (1, 2, 4)

mp构建:

UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", List.of(1L, 2L, 4L)); userMapper.update(null, wrapper) ;

# 自定义SQL

适用场景:要构建的SQL除了where条件之外的语句很复杂,那么就让mp帮我们构建where条件部分,其他部分由我们自己构建

上手

SQL:

UPDATE user
SET balance = balance - 200
WHERE id in (1, 2, 4)

mp构建:

UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", List.of(1L, 2L, 4L)); userMapper.update(null, wrapper) ;

setSql("balance = balance -2oo") 这部分应该在mapper层,而不是在业务层,所以需要进行传递,然后在mapper层进行SQL拼接

1、业务层

// 1、准备自定义查询条件
List<Long> ids = List.of(1L, 2L, 4L);
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("id", ids); // 2、调用mapper的自定义方法,直接传递Wrapper
userMapper.deductBalanceByIds(200, wrapper);

2、mapper层

public interface UserMapper extends BaseMapper<User> {

    /**
* <p>
* @Param("ew") 参数名必须是这个 也可以使用 @Param(Constants.WRAPPER)
* ${ew.customSqlSegment} 是mp自动解析自定义SQL片段
* </p>
*/
@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money,
@Param("ew") QueryWrapper<User> wrapper);
}

# 多表关联

MyBatis构建的方式:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user u
INNER JOIN address a ON u.id = a.user_id
WHERE u.id
<foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
AND a.city = #{city}
</select>

利用Wrapper来构建查询条件,然后手写SELECT及FROM部分,实现多表查询

业务层:

// 1、准备自定义查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京"); // 2、调用mapper的自定义方法
List<User> users = userMapper.queryUserByWrapper(wrapper);

mapper层:

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

# IService接口

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法

通用接口为IService,默认实现为ServiceImpl

# 上手

如果直接继承 IService 接口,则需要实现里面的方法,因此mp提供的一个默认实现 ServiceImp

1、自定义Service接口

public interface IUserService extends IService<User> {}

2、自定义Service接口实现类:继承mp的 ServiceImpl<M, T>

/**
* ServiceImpl<UserMapper, User> UserMapper指定对应的mapper User指定对应的实体类
*/
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements IUserService {}

3、正常使用上面那些增删改、单个查、多个查之类的API即可

# Lambda

IService中还提供了Lambda功能来简化我们的复杂查询(LambdaQuery)及更新功能(LambdaUpdate)

# LambdaQuery

根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空
  • status:用户状态,可以为空
  • minBalance:最小余额,可以为空
  • maxBalance:最大余额,可以为空
GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){
// 1、组织条件
String username = query.getName();
Integer status = query.getStatus();
Integer minBalance = query.getMinBalance();
Integer maxBalance = query.getMaxBalance(); // 2、查询用户
List<User> users = userService.lambdaQuery()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list(); // 告诉MP我们的调用结果需要的是一个list集合 上面是构建条件 // 3、处理vo
return BeanUtil.copyToList(users, UserVO.class);
}

UserQuery实体

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}

除了list(),还可选的方法有:

  • one():最多1个结果
  • list():返回集合结果
  • count():返回计数结果

# LambdaUpdate

根据id修改用户余额,如果扣减后余额为0,则将用户status修改为冻结状态2

public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements IUserService { @Override
@Transactional(rollbackFor = Exception.class)
public void deductBalance(Long id, Integer money) { // 1、查询用户
User user = this.getById(id);
// 2.校验用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
} // 3、校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
} // 4.扣减余额 update tb_user set balance = balance - ?
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance) // 更新余额
.set(remainBalance == 0, User::getStatus, 2) // 动态判断,是否更新status
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance()) // 乐观锁
.update(); // 上面为构建条件,这一步才为真正去修改
}
}

# saveBatch 批量新增 说明

YAML中datesource的url后添加参数 rewriteBatchedStatements=true

private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (18688190000L + i));
user.setBalance(2000);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
} @Test
void testSaveBatch() {
// 准备10万条数据
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000条批量插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗时:" + (e - b));
}

saveBatch() 源码

@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) { String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
} // ...SqlHelper
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) { Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
int size = list.size();
int idxLimit = Math.min(batchSize, size);
int i = 1;
for (E element : list) {
consumer.accept(sqlSession, element);
if (i == idxLimit) {
sqlSession.flushStatements();
idxLimit = Math.min(idxLimit + batchSize, size);
}
i++;
}
});
}

可见Mybatis-Plus的批处理是基于PrepareStatement的预编译模式,形成的SQL是如下样式:

Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01

而我们要的是合并成一条SQL,从而提高性能

INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);

这就需要修改SQL的配置,添加 &rewriteBatchedStatements=true: 参数

spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

这样配置之后,性能可以得到极大地提升

[#](#Db 静态工具类) Db 静态工具类

官网示例:https://gitee.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/toolkit/DbTest.java

Db这个类里面的静态方法就是IService中的方法,实现方式有点区别(提供Class对象)

适用场景:A类中注入B类来进行调用问题。使用该静态工具就不需要注入,直接使用Class就可进行调用了,从而减少循环依赖风险

注意

  • 使用 Db Kit 前,需要确保项目中已注入对应实体的 BaseMapper。
  • 当参数为 Wrapper 时,需要在 Wrapper 中传入 Entity 或者 EntityClass,以便寻找对应的 Mapper。
  • 不建议在循环中频繁调用 Db Kit 的方法,如果是批量操作,建议先将数据构造好,然后使用 Db.saveBatch(数据) 等批量方法进行保存

示例:

@Override
public UserVO queryUserAndAddressById(Long userId) { // 1、查询用户
User user = getById(userId);
if (user == null) {
return null;
} // 2、查询收货地址 不需要注入 AddressSerivce 了
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, userId)
.list(); // 3、处理vo
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class)); return userVO;
}

# 枚举处理器

解决Java枚举类型和数据库类型转换

@Data
public class UserEntity {
// ...................... // 0 正常 1禁用
private Integer status;
}

上面这种和数据库int类型转换方便,但是不符合编码,因为0和1得手动输入,代码多了很麻烦且容易弄错甚至混乱不堪(不信邪的可以去看若依项目:RuoYi-Vue),因此我们需要统一状态,即枚举

@Getter
public enum UserStatusEnum {
NORMAL(1, "正常"),
FREEZE(2, "冻结")
;
private final int value;
private final String desc; UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}

实体类

@Data
public class UserEntity {
// ...................... // 0 正常 1禁用
private UserStatusEnum status;
}

但此时实体是 UserStatusEnum 而数据库是int,涉及类型转换,这就需要mp来做了

1、YAML配置

mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

2、@EnumValue 标记枚举中哪个值为数据库字段值

@Getter
public enum UserStatusEnum {
NORMAL(0, "正常"),
FREEZE(1, "冻结") ; @EnumValue
private final int value;
@JsonValue // Jackson的,返回前端的值是正常或冻结 而不是 NORMAL 这种 SpringMVC底层使用的是 Jackson
private final String desc; UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}

# JSON类型处理器

官网:https://www.baomidou.com/guides/type-handler/

数据库中字段是JSON格式,和Java实体类对应字段进行适配,方便操作JSON格式

mp提供的JSON处理器默认是JacksonHandler,也推荐使用它,因为安全

Java定义JSON对应的类

@Data
public class Userlnfo {
privateIntegerage;
privateString intro;
privateString gender;
}

Java实体类

@Data@TableName(value="user", autoResultMap = true)	// 操作1
public class User{
private Long id;
private String username;
@TableField(typeHandler = JacksonTypeHandler.class) // 操作2
private String UserInfoinfo

# 分页插件

官网:https://www.baomidou.com/plugins/pagination/

# 上手

1、注册插件

@Configuration
public class MybatisConfig { @Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor pgInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置分页最大条数
pgInterceptor.setMaxLimit(1000);
// 注册分页插件
interceptor.addInnerInterceptor(); return interceptor;
}
}

使用分页:

int pageNo = 1, pageSize = 2;

// 1、准备分页条件
// 1.1、分页条件
Page<User> page = Page.of(pageNo,pageSize);
// 1.2、排序条件 true 为升序 false 为降序
page.addorder(new OrderItem("balance", true));
page.addorder(new OrderItem("id", true)); // balance 相同下,以 id 排序 // 2、分页查询
Page<User> p = userservice.page(page); // 3、解析
long total = p·getTotal(); // 总条数
System.out.println("total = " + total);
long pages = p.getPages(); // 总页数
System.out.println("pages = " ++ pages); List<User> users = p·getRecords(); // 分页后的数据

# 企业级mp相关设计

直接去看这个项目:RuoYi企业级改造版:AgileBoot-Back-End-Basic

对应md文档

链接:MyBatis-Plus。文件格式:md

MyBatis-Plus 整理的更多相关文章

  1. MYBATIS 简单整理与回顾

    这两天简单整理了一下MyBatis 相关api和jar包这里提供一个下载地址,免得找了 链接:http://pan.baidu.com/s/1jIl1KaE 密码:d2yl A.简单搭建跑项目 2.进 ...

  2. mybatis 一点整理

    mapper指定对应的接口 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper ...

  3. Mybatis面试整理

    #{}和${}的区别 #{}是预编译处理,${}是字符串替换. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值: Mybatis ...

  4. 一:MyBatis知识整理(1)

    一:MyBatis的架构 1.mybatis配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息. mapper.xml文件即sql映射文 ...

  5. 三:Mybatis知识整理(3)

    一:mybatis中模糊查询的方法: 1.直接传参法:在java传参时进行拼接 -- %keyword% 2.mysql内置函数:concart('%',#{keyword},'%') -- 拼接sq ...

  6. MyBatis知识点整理

    1.MyBatis一般使用步骤 1.1获取Configuration实例或编写配置文件 //获取Configuration实例的样例 TransactionFactory transactionFac ...

  7. mybatis问题整理

    // List<String> findBuildByProject(String prjName); //单参数时使用<if></if>标签判断采用"_ ...

  8. 【转】spring boot mybatis 读取配置文件

    spring boot mybatis 配置整理 一.加载mybatis的配置 1.手写配置,写死在代码里 import java.io.IOException; import java.util.P ...

  9. (转)MyBatis & MyBatis Plus

    (二期)3.mybatis与mybatis plus [课程三]mybatis ...运用.xmind0.1MB [课程三]mybatis...机制.xmind0.2MB [课程三]mybatis与j ...

  10. 数据库持久化框架——MyBatis(1)

    叨叨时刻:内容根据阿里云大学的MyBatis视频教程整理而成,有需要最好跟着视频教程走一遍 MyBatis 是支持定制化SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 ...

随机推荐

  1. 推荐一款微信公众平台Python开发神器!

    1.引言 在当今数字化时代,微信公众平台成为了企业和个人连接用户的重要渠道.为了高效地管理和与用户互动,开发者需要一个强大而灵活的工具. Weixin-Python 正是这样一个为微信公众平台量身打造 ...

  2. echarts(数据可视化图表)

    echarts饼图详细 echarts下载 https://echarts.apache.org/zh/index.html  echarts官网 http://www.isqqw.com/#/hom ...

  3. Unicode 14 标准发布

    本文为翻译,原文地址Unicode 博客: 宣布 Unicode 标准, 版本 14.0 Unicode 标准版本 14.0 现已可用,包括核心规范.附件和数据文件.此版本增加了 838 个字符,总共 ...

  4. vue-cli 中使用 Axios

    安装 axios: 1 npm install axios --save-dev 接着在src目录下创建一个http.js脚本中,导入axios并通过create方法实例化一个http请求对象,这样我 ...

  5. 理解Vue 3响应式系统原理

    title: 理解Vue 3响应式系统原理 date: 2024/5/28 15:44:47 updated: 2024/5/28 15:44:47 categories: 前端开发 tags: Vu ...

  6. sourceTree .gitignore文件处理

    最近犯了个低级错误,在sourceTree中通过右键,直接将 工程文件加入到 ignore列表中.提交之后,别人拉下来代码一直显示报错 在意识到上面这个错误之后,一直想恢复却恢复不了,尝试从Sorce ...

  7. RTMP 直播 H265 推流适配总结

    1.在iOS11的系统之上,苹果逐渐放开H265硬编硬解的能力,硬解码的能力只要升级到iOS11之后,iPhone6+以上的机型就支持了(印象中): H265硬编码的能力对设备要求较高,不仅要求系统版 ...

  8. C++笔记(6) 指针

    1.指针和数组 指针和数组基本等价的原因在于指针算数和C++内部处理数组的方式.在很多情况下,可以用相同的方式使用数组名和指针名. 在多数情况下,C++将数组名视为数组的第一个元素的地址.指针p的值为 ...

  9. Redisson 限流器源码分析

    Redisson 限流器源码分析 对上篇文章网友评论给出问题进行解答:redis 的key 是否会过期 可以先阅读上篇文章: redis + AOP + 自定义注解实现接口限流 - 古渡蓝按 - 博客 ...

  10. 【Java面试题-基础知识02】Java抽象类和接口六连问?

    1.抽象类和接口分别是什么? 抽象类是一种类,可以包含抽象方法和非抽象方法,抽象方法是没有具体实现的方法,需要在子类中被具体实现. 接口是一种完全抽象的类,其中的所有方法都是抽象方法,没有方法体,它只 ...