mybatis-plus

MybatisPlus入门

使用步骤

  1. 引入MybatisPlus的起步依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    </dependency>
  2. 定义Mapper继承MybatisPlus提供的BaseMapper接口,需要提供泛型为你的泛型

    1
    2
    3
    public interface UserMapper extends BaseMapper<User>{

    }

常见注解

MybatisPlus的约定

  1. 类名驼峰转下划线作为表名
  2. 名为id的字段作为主键
  3. 变量名驼峰转下划线作为表的字段名

如果表不满足当前的约定的话,MybatisPlus还提供了注解

  1. @TableName:用来指定表名,类名和表明不一致的时候使用,如果不使用的话会出现,找不到表的情况
  2. @Tableld(value=“主键名”, type=“IdType.AUTO, INPUT, ASSIGN_ID”):用来指定表中的主键字段信息
    • 表的主键名不是id,需要我们自己使用该注解来表明id,一定要给给出表的id,否则MybatisPlus无法根据主键来进行增删改查
    • type中AUTO:自增长ID, INPUT:通过set方法自行输入,ASSIGN_ID:通过雪花算法生成ID
  3. @TableField:用来指定表中的普通字段信息,属性名和表中的名字不同的时候使用
    • 成员变最名与数据库字段名不一致
    • 成员变最名以is开头“且是布尔值
    • 成员变最名与数据库关键字冲突
    • 成员变量不是数据库字段

MybatisPlus配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mybatis-plus:
# 别名扫描包
type-aliases-package: com.nuyoah.domain.entity
# mapper文件映射路径
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
# 是否开启下划线和驼峰映射
map-underscore-to-camel-case: true
# 是否开启二级缓存
cache-enabled: true
# 日志文件
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-not-delete-value: 0
logic-delete-field: deFlag
logic-delete-value: 1
# 生成id的方式 auto input assign_id
id-type: auto
# 更新策略:只更新非空字段
update-strategy: not_null

MybatisPlus核心

条件构造器

wrapper自定义构造语句,通过wapper对象来构造语句

1
2
3
SELECT id, username, info, balance
FROM user
WHERE username LIKE ? AND balance >= ?

MybatisPlus

ge:>=

eq:=

QueryWapper:执行查询条件

1
2
3
4
5
6
7
8
9
10
void testWapper(){
// 构建查询条件
QueryWapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println)
}

更新id为1,2,4的用户的余额,口200

UpdateWrapper:执行更新语句

1
2
3
4
5
6
7
8
9
10
void testWapper(){
List<Long> ids = List.of(1L, 2L, 4L);

// 构建查询条件
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance-200")
.in("id", ids)
// 查询
userMapper.update(wrapper);
}

LambdaQueryWrapper使用

QueryWrapper中使用了字符串作为参数,Lambda使用了变量来作为查询参数

1
2
3
4
5
6
7
8
9
10
void testWapper(){
// 构建查询条件
QueryWapper<User> wrapper = new QueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println)
}

自定义SQL

使用MyBatisPlus的Wrapper来构建Sql中的Where条件,然后自己定义Sql中剩下的语句

  1. 基于Wrapper构建的where条件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void testWapper(){
    List<Long> ids = List.of(1L, 2L, 4L);
    int amount = 200

    // 构建条件
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getid, ids)
    // 自定义Sql方法调用
    userMapper.updateBalanceByIds(wrapper, amount);
    }
  2. 在Mapper方法参数名中使用Param注解声明wrapper变量名称,必须是ew

    1
    void updateBalanceByIds(@Param("ew") LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);
  3. 自定义sql,并使用wrapper条件,自己不用写复杂的where语句

    1
    2
    3
    <update id="updateBalanceByIds">
    UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
    </update>

Service接口

先继承

我们自己的service类和serviceImpl类,可以继承IService和和IServiceImpl

使用方法

  1. 自定义Service接口继承IService接口

    1
    2
    3
    public interface IUserService extends IService<User>{

    }
  2. 自定义Service实现类,实现自定义接口,并继承ServiceImpl类

    1
    2
    3
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    }

当我们实现自己的Controller的时候,自动注入Service组件的时候,SpingBoot会报错说,不支持这样注入

这时候可以使用注解

1
2
3
4
5
6
7
@RequireArgsConstructor

public class UserController {
// 加上final就会自动注入
private final IUserService userService;

}

Service简单接口

  1. 新增:userService.save(user);
  2. 删除:userService.removeById(id)
  3. 查询:
    • 查询单个:userService.getById(id)
    • 查询多个:userService.list(ids)

Service复杂接口

对指定id的对象进行工资+200,需要我们手写SQL

UserService

1
2
3
public void deductBalance(Long id, Integer money) {
baseMapper.deductBalance(id, money);
}

UserMapper

1
2
@Update("update tb_user set balance = balance-#{money} where id =#{id}")
void deductBalance(@Param("id") Long id, @Param("money") Integer money);

Lambda查询

可以实现复杂查询

不适用lambda时候的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="queryUsers" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM tb_user
<where>
<if test="name != null">
AND username LIKE #{name}
</if>
<if test="status != null">
AND `status` = #{status}
</if>
<if test="minBalance != null and MaxBalance != null">
AND balance BETWEEN #{minBalance} AND #{maxBalance}
</if>
</where>
</select>

使用Lambda时候的代码

1
2
3
4
5
6
7
8
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list()
}

扣减余额,当余额为零的时候将用户status设置为2

1
2
3
4
5
6
7
8
public List<User> deduceBalance(Long id, Integer money) {
int reaminBalance = user.getBalance() - money
return lambdaUpdate()
.set(User::getBalance, reaminBalance)
.set(remainBalance == 0, User::getStatus, 2)
.eq(User::getId, id)
.update()// 执行语句
}

批量新增

MybatisPlus扩展功能

代码生成

MybatisPlus插件

image-20240308175512138

静态工具

逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。思路如下

在表中添加一个字段标记数据是否被删除

  • 当删除数据时把标记置为1

  • 查询时只查询标记为0的数据

MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mybatis-plus:
# 匿名域
type-aliases-package: com.nuyoah.domain.entity
# mapper文件映射路径
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
# 日志文件
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
cache-enabled: true
global-config:
db-config:
# 配置逻辑删除字段名
logic-delete-field: deFlag
logic-not-delete-value: 0
logic-delete-value: 1
id-type: auto

枚举处理器

当我们在数据库中存在例如status状态标记的时候,可以使用enum来代替0和1,通过枚举处理器来将enum对应到数据库中的项

枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FROZEN(2, "冻结"),
;
// 设置枚举注解,对应到数据库
@EnumValue
private final int value;
private final String desc;

UserStatus(int value, String desc){
this.value = value;
this.desc = desc;
}
}

配置枚举处理器的配置

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

实体类配置

1
2
3
4
public class User {
// private Integer status 替换为下面的
private UserStatus status;
}

status使用

1
user.getStatus() === UserStatus.FROZEN

使用@JsonValue来确定请求返回值是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FROZEN(2, "冻结"),
;
// 设置枚举注解,对应到数据库
@EnumValue
private final int value;
@JsonValue
private final String desc;

UserStatus(int value, String desc){
this.value = value;
this.desc = desc;
}
}

JSON处理器

将数据库中的JSON字符串转成对象

1
2
3
4
5
6
7
8
@Data
@TableName(value="user", autoResultMap=true)
public class User {
private Long id;
private String username;
@TableFiled(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
}

MybatisPlus插件

  1. TenantLineInnerInterceptor:多租户插件
  2. DynamicTableNameInnerInterceptor:动态表名插件
  3. PaginationInnerInterceptor:分页插件
  4. OptimisticLockerInnerInterceptor:乐观锁插件
  5. IllegalSQLInnerInterceptor:SQL性能规范插件,检测并拦截垃圾SOL
  6. BlockAttackInnerInterceptor:防止全表更新和删除的插件

分页插件

创建分页插件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置分页上限
pageInterceptor.setMaxLimit(1000L);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}

分页API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
*翻页查询
Params:page-翻页对象
queryWrapper-实体对象封装操作类 com.baomidou.
mybatisplus.core.conditions.query .
Querywrapper
*/
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectPage(page, queryWrapper);
}



/**
无条件翻页查询
Params; page-翻页对象
See Also: Wrappers.emptywrapper()
*/
default <E extends IPage<T>> E page(E page){
return page(page, Wrappers.emptyWrapper)
}

使用分页API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
void testPageQuery() {
// 1.查询
int pageNo = 1, pageSize = 5;
// 1.1.分页参数
Page<User>page= Page.of(pageNo,pageSize);
// 1.2.排序参数,通过OrderItem来指定
page.addOrder(new OrderItem("balance", false));
// 1.3.分页查询
Page<User> p = userservice.page(page);
// 2.总条数
System.out.println("total = " + p.getTotal());
//3.总页数
System.out.println("pages = " + p.getPages());
//4.分页数据
List<User> records = p.getRecords();
records.forEach(System.out::printin);
}

1000