MyBatisPlus全解

入门

1.导入依赖

       <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>

2.接口继承BaseMapper,注意声明对应的实体类作为泛型
在这里插入图片描述
3.注入

 @Autowired
    private UserMapper userMapper;

4.基础操作
首先,查看数据库结构和实体类
在这里插入图片描述

package com.itheima.mp.domain.po;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class User {

    /**
     * 用户id
     */
    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 注册手机号
     */
    private String phone;

    /**
     * 详细信息
     */
    private String info;

    /**
     * 使用状态(1正常 2冻结)
     */
    private Integer status;

    /**
     * 账户余额
     */
    private Integer balance;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}


  1. 默认为分配id,实现类为DefaultldentifierGenerators雪花算法
    可以自己配置,注解优先级大于全局配置(见下方讲解)
    如果实体类设置id则按照设置的id来
 void testInsert() {
        User user = new User();
        user.setUsername("Lucy");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        userMapper.insert(user);
    }

在这里插入图片描述


  1. 单个查询
    void testSelectById() {
        User user = userMapper.selectById(4L);
        System.out.println("user = " + user);
    }

在这里插入图片描述
在这里插入图片描述
数组查询

  @Test
    void testQueryByIds() {
        List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
        users.forEach(System.out::println);
    }

在这里插入图片描述
在这里插入图片描述

    @Test
    void testUpdateById() {
        User user = new User();
        user.setId(4L);
        user.setBalance(20000);
        userMapper.updateById(user);
    }

在这里插入图片描述
在这里插入图片描述

  @Test
    void testDeleteUser() {
        userMapper.deleteById(4L);
    }

常见注解

在这里插入图片描述

常用配置

在这里插入图片描述

条件构造器

MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。

基于QueryWrapper的查询

需求:
①查询出名字中带o的,存款大于等于1000元的人的id、username、info、 balance字段

    @Test
    void testQueryWrapper() {
        //1.创建查询条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "balance")
                .like("username", "%o%")
                .ge("balance", 1000);

        //2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

②更新用户名为jack的用户的余额

@Test
    void testUpdateByQueryWrapper() {
        User user = new User();
        user.setBalance(666666);
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
               .eq("id", 1L);
        userMapper.update(user, wrapper);
    }

在这里插入图片描述

基于UpdateWrapper的更新

需求:更新id为1,2 4的用户的余额,扣200

    @Test
    void testUpdateWrapper() {
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", List.of(1L, 2L, 3L));
        userMapper.update(null, wrapper);
    }

在这里插入图片描述
在这里插入图片描述
Lambda的区别避免硬编码,和没有Lambda的区别就是把字段名换成get
举个栗子:

 @Test
    void testQueryWrapper() {
        //1.创建查询条件
//        QueryWrapper<User> wrapper = new QueryWrapper<User>()
//                .select("id", "username", "balance")
//                .like("username", "%o%")
//                .ge("balance", 1000);
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getUsername, User::getBalance)
                .like(User::getUsername, "%o%")
                .ge(User::getBalance, 1000);
        //2.查询
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

条件构造器的用法:

●QueryWrapper和LambdaQueryWrapper通常用来构建select
、delete、 update的where条件部分
●UpdateWrapper和LambdaUpdateWrapper通常只有在set语
句比较特殊才使用
●尽量使用LambdaQueryWrapper和LambdaUpdateWrapper
避免硬编码

自定义SQL

MyBatisPlus为什么需要自定义SQL?
举个栗子:对比xml和mp(mp是MyBatisPlus的简称)
需求:将id在指定范围的用户(例如1、2、4 )的余额扣减指定值

java实现

   @Test
    void testUpdateWrapper() {
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", List.of(1L, 2L, 3L));
        userMapper.update(null, wrapper);
    }

xml形式

<update id="updateUserBalance">
    UPDATE user
    SET balance = balance - 200
    WHERE id IN
    <foreach collection="idList" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</update>

对比可得:mp擅长where条件sql语句不得不写在业务代码中不利于维护。

步骤:

  1. 基于Wapper构建where条件
    @Test
    void testCustomSqlUpdate() {
        List<Long> longs = List.of(1L, 2L, 3L);
        int amount=200;
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<User>().in("id", longs);
        userMapper.customSqlUpdate(userQueryWrapper, amount);
    }
  1. 在mapper方法参数中使用Param注解声明wrapper变量名称必须是ew
    void customSqlUpdate(@Param(("ew")) QueryWrapper<User> userQueryWrapper, @Param(("amount")) int amount);
  1. 自定义SQL,并使用Wrapper条件
    <update id="customSqlUpdate">
        UPDATE user SET balance=balance-#{amount} ${ew.customSqlSegment}
    </update>

Service接口

IService 接口在软件开发中,尤其是基于服务架构(如微服务架构)的系统中,扮演着非常重要的角色。
可以减少简单的service层代码,替代简单的增删改查
作用:

  1. 定义服务契约:明确服务层应该提供哪些功能,这对于服务提供者和消费者之间的通信至关重要。
  2. 解耦:通过接口定义服务,可以将服务的实现与服务的调用者解耦。这样,服务的调用者就无需知道服务的具体实现细节,只需要知道接口定义即可。
    使用步骤:

ServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements IUserService {
}

Service

public interface IUserService extends IService<User> {
}

控制层

@Autowired
    private IUserService iUserService;
@Test
    void testSaveUser() {
        User user = new User();
        user.setUsername("Lili");
        user.setPassword("123");
        user.setPhone("18688990011");
        user.setBalance(200);
        user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        System.out.println(iUserService);
        iUserService.save(user);
}

解释IUserService接口实现IService接口(声明泛型),UserServiceImpl实现IUserService接口便可以重写IService的方法,但是由于方法太多显然无法全部实现,可以继承提供的ServiceImpl类(声明自定义的Mapper和DAO)
ServiceImpl中根据提供的Mapper实现对应的代码,实际上和手动实现MyBatisPlus一样。
在这里插入图片描述

在这里插入图片描述

IService基础业务接口示例

package com.itheima.mp.controller;

import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Api(tags = "用户模块")
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
    private final IUserService userService;

    @PostMapping
    @ApiOperation(value = "新增用户信息")
    public void saveUser(@RequestBody UserFormDTO userFormDTO) {
        User user = BeanUtil.copyProperties(userFormDTO, User.class);
        userService.save(user);
    }
    @DeleteMapping("/{id}")
    @ApiOperation(value = "删除用户信息")
    public void deleteUser(@PathVariable("id") Long id) {
        userService.removeById(id);
    }
    @ApiOperation(value = "根据id查询用户信息")
    @GetMapping("/{id}")
    public UserVO getUser(@ApiParam("用户id") @PathVariable("id") Long id) {
        User user = userService.getById(id);
        return BeanUtil.copyProperties(user, UserVO.class);
    }
    @ApiOperation(value = "根据id查询批量用户信息")
    @GetMapping
    public List<UserVO> getUser(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
        List<User> users = userService.listByIds(ids);
        return BeanUtil.copyToList(users, UserVO.class);
    }
    @ApiOperation(value = "扣减用户余额")
    @PutMapping("/{id}/deduct/{money}")
    public void getUser(@ApiParam("用户id") @PathVariable("id") Long id,
                                @ApiParam("扣减金额") @PathVariable("money") Integer money) {
        userService.deductBalance(id, money);
    }
}

IService的Lambda查询

lambdaQuery()

需求:实现一个根据复杂条件查询用户的接口,查询条件如下:
●name:用户名关键字,可以为空
●status:用户状态,可以为空
●minBalance:最小余额,可以为空
●maxBalance:最大余额,可以为空
查询
多个使用list()
一个使用one()
数量使用 count()
是否存在exist()
分页使用page()

在这里插入图片描述
替换了下方代码
在这里插入图片描述

lambdaUpdate()

扣除指定id的余额

    @Override
    @Transactional
    public void deductBalance(Long id, Integer money) {
        User byId = getById(id);
        if(byId==null||byId.getStatus()==2){
            throw new RuntimeException("用户不存在或已被冻结");
        }
        if(byId.getBalance()<money){
            throw new RuntimeException("余额不足");
        }
        lambdaUpdate()
                .set(User::getBalance,byId.getBalance()-money)
                .set(byId.getBalance()-money==0,User::getStatus,2)
                .eq(User::getId,id)
                .eq(User::getStatus,byId.getStatus())//乐观锁
                .update();
    }

IService的批量新增

对比性能
1.使用for循环依次新增用户(和数据库交互了100000次)

private User createUser(Long num) {
    User user = new User();
    user.setUsername("user_"+num);
    user.setPassword("123");
    user.setPhone("18688990011");
    user.setBalance(200);
    user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
    user.setCreateTime(LocalDateTime.now());
    user.setUpdateTime(LocalDateTime.now());
    return user;
}
@Test
    void testSaveUser() {
    long l = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
        iUserService.save(createUser(l+i));
    }
    long end = System.currentTimeMillis();
    System.out.println("耗时:"+(end-l));
}

在这里插入图片描述
2.使用for循环批量插入数据(依次和数据库交互插入一千条数据)
一次new十万条数据过大,并且网络请求数据包有上线

    @Test
    void testBathSaveUser() {
        long l = System.currentTimeMillis();
        ArrayList<User> objects = new ArrayList<>(1000);
        for (int i = 0; i <= 100000; i++) {
            objects.add(createUser(l+i));
            if(i%1000==0){
                iUserService.saveBatch(objects);
                 objects.clear();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-l));
        System.out.println("总数:"+iUserService.count());
    }

在这里插入图片描述
开启批处理
开启rewriteBatchedStatements=true参数
配置jdbc参数,开rewriteBatchedStatements,性能最好
例如:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
注意rewriteBatchedStatements是mysql的配置,由引入的驱动做的,不是mp做到
在这里插入图片描述
实际上是把数据整理成了一条sql,只和数据库进行了一次网络请求
在这里插入图片描述

静态工具

为了避免循环依赖例如user中注入address,address注入user
使用:
在UserServiceImpl中只要为声明字节码就可以使用Db的API

   List<Address> list = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();

注意:Db的Api和Isevice类似
优秀栗子:

    @Override
    public List<UserVO> getUserVOByIds(List<Long> ids) {
        //根据ids查询用户
        List<User> users = listByIds(ids);
        if(CollUtil.isEmpty(users)){
            return Collections.emptyList();
        }
        //根据查询的用户获得准确的ids
        List<Long> collectid = users.stream().map(User::getId).collect(Collectors.toList());
        //使用Db根据ids查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class)
                .in(Address::getUserId, collectid)
                .list();

        List<AddressVO> addressVOS = BeanUtil.copyToList(addresses, AddressVO.class);
        //根据地址的userId分组
        Map<Long, List<AddressVO>> collect = addressVOS.stream().collect(Collectors.groupingBy(AddressVO::getUserId));

        List<UserVO> userVOS = new ArrayList<>(users.size());
        /**
         * 先 add 再 set 可以避免这些风险,原因如下:
         *
         * 确保对象存在:首先将对象添加到集合中,确保对象已经存在于集合中。这样可以避免在后续操作中出现对象未添加到集合中的情况,从而减少潜在的错误。
         *
         * 简化异常处理:如果在 set 属性时发生异常,由于对象已经被添加到集合中,可以更容易地进行异常处理和调试,确保对象不会丢失。
         *
         * 逻辑清晰:先 add 再 set 的顺序使得代码的逻辑流程更加清晰,便于理解和维护。这种顺序符合一般的编程习惯,减少潜在的逻辑错误。
         *
         * 先 set 再 add 可能会导致在设置属性时出现异常或逻辑错误,从而导致对象没有被正确添加到集合中的原因如下:
         *
         * 异常处理:在设置属性时,可能会涉及到一些复杂的逻辑或外部资源的访问(例如数据库查询、网络请求等)。这些操作可能会抛出异常,如果在 set 属性时发生异常,对象可能还没有被添加到集合中,从而导致对象丢失。
         *
         * 逻辑错误:在设置属性时,可能会依赖于某些条件或状态。如果在这些条件或状态不满足的
         */
        for (User user : users) {
            UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
            userVOS.add(userVO);
            userVO.setAddresses(collect.get(user.getId()));
        }
        return userVOS;
    }

逻辑删除
在这里插入图片描述
在这里插入图片描述

JSON处理器

场景:数据库中user表中有一-个json类型的字段:
步骤:
1.
在这里插入图片描述
注解 @TableField(typeHandler = JacksonTypeHandler.class):

这个注解告诉 MyBatis Plus,在处理 info 字段时,使用 JacksonTypeHandler 进行类型转换。
JacksonTypeHandler 会将 UserInfo 对象序列化为 JSON 字符串,存储在数据库中;或者将数据库中的 JSON 字符串反序列化为 UserInfo 对象。
2.
开启自动映射
在这里插入图片描述

插件功能

MyBatisPlus提供的内置拦截器有下面这些:
在这里插入图片描述
分页插件

@Configuration
public class MyBatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybaPlusInterceptor = new MybatisPlusInterceptor();
        //添加分页配置
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInnerInterceptor.setMaxLimit(1000L);
        mybaPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
        return mybaPlusInterceptor;
    }
}
   @Test
    void testQuery(){
        Page<User> userPage = Page.of(1, 2);
        //添加排序条件true表示正序
        userPage.addOrder(new OrderItem("id", true));
        userPage.addOrder(new OrderItem("balance", true));
        Page<User> page = iUserService.page(userPage);
        List<User> records = page.getRecords();
        System.out.println(records);
        System.out.println("******************");
        System.out.println(page);
    }
  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值