MyBatisPlus

发布于:2024-05-07 ⋅ 阅读:(26) ⋅ 点赞:(0)

MyBatisPlus

一、基本使用
1.使用步骤:
①引入MybatisPlus依赖,代替Mybatis依赖

MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。

  <!--MybatisPlus-->
	<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.3.1</version>
  </dependency>				
②引入MybatisPlus依赖,代替Mybatis依赖
public interface UserMapper extends BaseMapper<User> {
}
2.常见注解:

MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息

  • 类名驼峰转下划线作为表名
  • 名为id的字段作为主键
  • 变量名驼峰转下划线作为表的字段名
常见注解
注解 作用
@TableName 用来指定表名
@TableId 用来指定表中的主键字段信息
@TableField 用来指定表中的普通字段信息

使用@TableField的常见场景:

  • 成员变量名与数据库字段名不一致

  • 成员变量名以is开头,且是布尔值

  • 成员变量名与数据库关键字冲突

  • 成员变量不是数据库字段

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

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置

mybatis-plus:
	type-aliases-package: com.itheima.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为雪花算法生成
  		update-strategy: not_null # 更新策略:只更新非空字段

二、核心功能
1.条件构造器
  • QueryWrapper
  • UpdateWrapper
  • LambadaQueryWrapper
@Test
    void testQueryWarpper(){
        // 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);
        users.forEach(System.out::println);

    }

    @Test
    void testUpdateQueryWarpper(){
        // 1.更新后的数据
        User user = new User();
        user.setBalance(2000);
        // 2.更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username","jack");
        // 3.执行更新
        userMapper.update(user,wrapper);
    }

    @Test
    void testUpdateWarpper(){
        // 1.更新后的数据
        List<Long> ids = List.of(1L,2L,4L);
        // 2.更新的条件
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 20")
                        .in("id",ids);
        // 3.执行更新
        userMapper.update(null,wrapper);
    }

    @Test
    void testLambadaQueryWrapper(){
        // 1.构造查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId,User::getUsername,User::getInfo,User::getBalance)
                .like(User::getUsername,"o")
                .ge(User::getBalance,10000);
        //  2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

条件构造器的用法:

QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分

UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用

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

2.自定义 SQL
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

①基于Wrapper构建where条件

		@Test
    void testCustomSqlUpdate(){
        // 1.更新的条件
        List<Long> ids = List.of(1L,2L,4L);
        int amount = 200;
        // 2.定义条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .in("id",ids);
        // 3.调用自定义 SQL 方法
        userMapper.updateBalanceByIds(wrapper,amount);
    }

②在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew

public interface UserMapper extends BaseMapper<User> {
    void updateBalanceByIds
      (@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);
}

③自定义SQL,并使用Wrapper条件

<update id="updateBalanceByIds">
	UPDATE user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>
3. Service接口
  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询
使用步骤
  • 自定义Service接口继承IService接口
public interface IUserService extends IService<User> {
}
  • 自定义Service实现类,实现自定义接口并继承Servicelmpl类
 public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
 }
IService 的 Lambda 查询、修改
		@Override
		@Transactional
    public void deductBalance(Long id, Integer money) {
        // 1.查询用户
        User user = getById(id);
        // 2.校验用户状态
        if (user == null || user.getStatus() == 2){
            throw new RuntimeException("用户状态异常");
        }
        // 3.校验余额是否充足
        if(user.getBalance() < money){
            throw new RuntimeException("用户余额不足");
        }
        // 4.扣减余额
        //baseMapper.deductBalance(id,money);
        int remainBalance = user.getBalance() - money;
        lambdaUpdate()
                .set(User::getBalance,remainBalance)
                .set(remainBalance==0,User::getStatus,2)
                .eq(User::getId,id)
          			.eq(User::getBalance,User.getBalance())//加锁,乐观锁
                .update();
    }

    @Override
    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();
    }
IService 批量新增

批处理方案:

  • 普通for循环逐条插入速度极差,不推荐

  • MP的批量新增,基于预编译的批处理,性能不错

  • 配置jdbc参数,开rewriteBatchedStatements,性能最好 &rewriteBatchedStatements=true

三、扩展功能
MybatisPlus插件
  • 安装插件MybatisPlus插件
  • 在 tools 下的Config Database配置 Database
  • 在 tools 下的Code Generator选择需要配置的项
静态工具Db
避免Service互相注入循环依赖
    @Override
    public UserVO queryUserAndAddressById(Long id) {
        // 1.查询用户
        User user = getById(id);
        if (user == null || user.getStatus() ==2){
            throw new RuntimeException("用户状态异常");
        }
        // 2.查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id).list();
        // 3.封装VO
        // TODO 3.1 将 User的PO转换为VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        // TODO 3.2 转地址VO
        if (CollUtil.isNotEmpty(addresses)){
            userVO.setAddresses(BeanUtil.copyToList(addresses,AddressVO.class));
        }
        return userVO;
    }
逻辑删除

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

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

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

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

删除操作:UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
查询操作:SELECT * FROM user WHERE deleted = 0

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

mybatis-plus:
	global-config:
  	db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名,字段类型可以是boolean、integer  
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

逻辑删除本身也有自己的问题,比如:

  • 会导致数据库表垃圾数据越来越多,影响查询效率
  • SQL中全都需要对逻辑删除字段做判断,影响查询效率

因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。

枚举处理器

在application.yml中配置全局枚举处理器:

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

如何实现PO类中的枚举类型变量与数据库字段的转换?

①给枚举中的与数据库对应value值添加@EnumValue注解

②在配置文件中配置统一的枚举处理器,实现类型转换

public enum UserStatus {
    NORMAL(1,"正常"),
    FROZEN(2,"冻结"),
    ;
    @EnumValue //该注解告知MyBatis枚举类型的值
    private final int value;
    @JsonValue //该注解指明返回的数据值为什么
    private final  String desc;

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

请添加图片描述

请添加图片描述

四、插件功能
序号 拦截器 描述
1 TenantLineInnerInterceptor 多租户插件
2 DynamicTableNameInnerInterceptor 动态表名插件
3 PaginationInnerInterceptor 分页插件
4 OptimisticLockerInnerInterceptor 乐观锁插件
5 IllegalSQLInnerInterceptor SQL性能规范插件,检测并拦截垃圾SQL
6 BlockAttackInnerInterceptor 防止全表更新和删除的插件
分页插件
  • 配置类中注册MyBatisPlus的核心插件,同时添加分页插件
@Configuration
public class MybatisConfig {
  
  @Bean
  public MybatisPlusInterceptor mybatisPlusInterceptor() {
    // 1.初始化核心插件
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 2.添加分页插件
    PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
    pageInterceptor.setMaxLimit(1000L); // 设置分页上限
    interceptor.addInnerInterceptor(pageInterceptor);
    return interceptor;
  }
}
  • Page Api的使用
 		@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::println);
    }