让你会用MyBatis-Plus---项目中必会的持久层框架

 如何在SpringBoot中如何引入MyBatis-Plus如下:

https://blog.csdn.net/iwjijksw/article/details/142063661?spm=1001.2014.3001.5501


1.MyBatis-Plus是怎么自己编写sql的

public interface UserMapper extends BaseMapper<User> {
}
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String phone;
    private String info;
    private Integer status;
    private Integer balance;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

        由上面,我们可以看到继承的BaseMapper有个泛型是User类型的,而MyBatis-Plus就是通过反射这个User得到与数据库相对应的属性值,其中类名就是表名,id值就是主键,而映射的数据库属性名由如下规则:

  • 类名驼峰转下划线作为表名
  • 名weiid的字段为主键
  • 变量名驼峰转下划线作为表的字段名

                                (由此可以看出平时的编码习惯很重要)

但是只有这些规则显然不能满足一些特殊的情况,于是MyBatis-Plus就提供了一些注解。

        1-1 常用注解

@TableName:用来指定表名

@TableId:用来指定表中的主键值字段信息

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

@TableName("tb_user")
public class User {
    @TableId(value = "id", type = IdType.AUTO)  
    private Long ID;
    @TableField("username")
    private String userName;
    //如果是布尔类型,是is开头的,要自己手动转换
    @TableField("is_married")
    private Boolean isMarried;
    //变量名和数据库关键字冲突的
    @TableField("`order`")
    private Integer order;
    //数据库中不存在的
    @TableField(exist = false)
    private String address;
}

MyBatis-Plus其他注解————>注解

        1-2 常用配置

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

2.核心功能

       2-1 条件构造器

Wrapper继承关系图
wrapper继承关系图
  • 2-1-1 QueryWrapper
@Test
    void testUserByQueryWrapper() {
        //构建条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .select("id", "username", "info", "balance")
                .ge("balance",1000)
                .like("username", "o");
        List<User> users = userMapper.selectList(wrapper);
        //遍历集合
        users.forEach(System.out::println);
    }

QueryWrapper比起它的父类,扩展了select方法,可以查询指定字段

  • 2-1-2 UpdateWrapper
//需求:更新id为1,2,4的用户的余额,扣200
    @Test
    void testUserByUpdateWrapper() {
        //方法一:
        QueryWrapper<User> wrapper1 = new QueryWrapper<User>()
                .in("id", 1, 2, 4);
        List<User> users = userMapper.selectList(wrapper1);
        users.forEach(user -> {
            user.setBalance(user.getBalance() - 200);
            userMapper.updateById(user);
        });

        //方法二:
        UpdateWrapper<User> wrapper2 = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", 1, 2, 4);
        userMapper.update(null, wrapper2);
    }

UpdateWrapper也有区别于父类的扩展方法,就是setSql方法,可以自定义sql语句,上面两个方法的对于扣200余额这个需求来说,明显第二个更加简单方便。

  • 2-1-3 LambdaQueryWrapper

但是我们编程最好不要硬编码,所以有了Lambda形式,让其动态的获取字段值

@Test
    void testUserByQueryWrapper() {
        //构建条件
        LambdaQueryWrapper<User> wrapper1 = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .ge(User::getBalance, 1000)
                .like(User::getUsername, "o");
        List<User> users1 = userMapper.selectList(wrapper1);
        //遍历集合
        users1.forEach(System.out::println);
    }

LambdaUpdateWrapper也是一样的


2-2 自定义sql

在开发过程中,大多数的单表的增删改查我们都可以用MyBatis-plus来做,但是也有一些特殊的情况,如下

UpdateWrapper<User> wrapper2 = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", 1, 2, 4);

一般这种mp的代码,我们都在业务层去写,但是企业规范中是不允许sql语句写道业务层的,违反了开发规范,所以又出现了自定义的sql。基于wrapper构建条件,自己写剩下的sql,然后拼装。

2-2-1 自定义sql步骤如下:
        1.基于wrapper构建条件
 List<Long> ids = List.of(1L,2L,4L);
        int amount = 200;
        // 1.构建条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .in(User::getId,ids);
        // 2. 自定义SQL方法调用
        userMapper.updateBlanceByIds(wrapper,amount);
        2.在mapper方法中,用@Param声明wrapper的名称,固定ew
void updateBalanceByIds (@Param("ew") LambdaQueryWrapper <User> Wrapper,
                @Param("amount") int amount);
        3.自定义SQL,并使用wrapper条件
<update id="updateBalanceByIds">
        update user set blance = blance - #{amount} ${ew.customSqlSegment}
</update>

2-3 service接口

        2-3-1 IService提供的相关方法
更新和添加相关方法
删除这里有两个方法需要区分
removeBatchByIds是根据sql的标签语句删除的,效率更好
removeByIds是循环删除的,效率没有上一个好
        2-3-2 service的实现类继承关系:

在service层,mp也提供了相关方法实现增删改查,更加的方便,只不过要我们的接口去继承IService接口,并且实现类继承ServiceImpl类,因为IService内的方法非常多,ServiceImpl是官方提供的实现类。

//service层接口实现IService
public interface UserService extends IService<User> {
}

//service层实现类继承ServiceImpl,再实现我们自己的接口
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}

用法举例:

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;
    
    //根据id查询用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id){
        return userService.getById(id);
    }
}

        由此可以看出,这是一个controller的类,以往我们还得再service层,mapper层写代码,对于这种简单的增删改查,多了很多没必要的事情,但是现在只需要一行代码,就完成简单的查询用户操作,是不是很方便呢

        2-3-3 LambdaQuery方法

        对于有些复杂的条件查询,我们不能把他们全部写在controller层,这样就显得controller层的代码非常臃肿,所以在service层调用自己就可以。如下

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

    //条件:名字中带有name的,状态为status的,大于最小金额以及小于最大金额,且这四个可能为空
    @Override
    public List<User> getUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
        return this.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();
    }
}
2-3-4 LambdaUpdate方法

LambdaUpdate和LambdaQuery基本一样,只不过最后要调用update方法

 this.lambdaUpdate()
                .set(User::getUsername, name)
                .set(minBalance == 0, User::getStatus, status)
                .update();

2-3-5 逐条插入数据与批处理插入的性能对比

首先我在User类中构建了一个静态方法用于创建User对象并插入属性数据

public static User sUserBuilder(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123456");
        user.setPhone("18909800000" + i);
        user.setInfo("{\"age\":24,\"intro\":\"编程老师\",\"gender\":\"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(user.getCreateTime());
        return user;
    }

然后插入数据

@Test
    void createUser() {
        long originTime= System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            userService.save(User.sUserBuilder(i));
        }
        long finallyTime = System.currentTimeMillis();
        System.out.println("10万条数据的创建时间是" + (finallyTime - originTime));
    }

可以看出时间挺长的,有3.5分钟

然后批处理,一次插入100条,共100次,看看效果怎么样,前提给MySQL数据库传入批处理重写为一条的配置属性rewriteBatchedStatements=true

 @Test
    void insertUser(){
        long originTime = System.currentTimeMillis();
        //准备一个容量为1000的集合
        List<User> list = new ArrayList<>(1000);
        for (int i = 1; i <= 100000; i++) {
            list.add(User.sUserBuilder(i));
            if (i%1000 == 0){
                userService.saveBatch(list);
                //清空集合
                list.clear();
            }
        }
        long finallyTime = System.currentTimeMillis();
        System.out.println("10万条数据的创建时间是" + (finallyTime - originTime));
    }

可以看出时间大大缩短,只有不到8秒,性能提高了27倍,这就是批处理的好处


3.扩展功能

        3-1 Db工具类

        我们在开发过程的,会遇到一种情况,就是一个类中需要用到其他不同的类,比如用户service层可能会用到地址的mapper层或者地址的service层,用户类和地址类按照开发规范应该彼此间尽可能的少调用,降低类与类之间的耦合性,提高项目后期可维护性与提高阅读性,于是Db可能在某些情况会解决这一问题。

        Db是一个静态工具类,方法中大多需要传入映射的类的类型,如下

        Db类和IService提供的方法基本上一样,就是多了一个参数,类型参数

举例:

@Override
    public UserVO getUserAndAddressById(Long id) {
        //先查用户
        User user = this.getById(id);
        if (user == null || user.getStatus() == 2){
            throw new RuntimeException("用户不存在或者用户已被冻结");
        }
        //再查询用户的地址
        List<Address> list = Db.lambdaQuery(Address.class)
                .eq(Address::getUserId, id)
                .list();
        //封装VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        if (CollUtil.isNotEmpty(list)){
            userVO.setAddressVOS(BeanUtil.copyToList(list, AddressVO.class));
        }
        return userVO;
    }

        (注意!!!Db静态工具类是MyBatis-Plus较高版本才有的功能,老版本没有的)

                3-2 逻辑删除

        在开发中,订单模块很常见,对于一个用户而言,用户可以选择删除这个订单,但是在商家这里,每一个订单都是非常重要的,不能删除,所以就有了逻辑删除,我们可以给一个表一个字段为deleted,如果deleted是0表示未删除,1表示逻辑删除,我们不能对已经逻辑删除的信息进行修改,于是在每一个接口中,都需要加一个where deleted = 0这样的条件;mybatis-plus解决了这个麻烦的操作,只需要做相关配置。

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

       


        3-3 枚举类型与数据库字段名之间的转换

1.引入配置
  mybatis-plus:
    configuration:
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
2.加入@EnumValue注解在枚举类中与数据库像转换的枚举项上
@Getter
public enum StatusEnum {
    NORMAL(1,"正常"),
    FREEZE(2,"冻结")
    ;

    @EnumValue
    private final int value;
    // mvc默认在枚举转换中返回给前端的是枚举项名称,也就是NORMAL,加入这个注解后,返回的是desc代表的字符串
    @JsonValue  
    private final String desc;

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

        


        3-4 MyBatis-Plus提供的分页插件

首先对Mybatis-plus进行配置

@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;
    }
}

举例:

// 1.查询
int pageNumber = 1, pageSize = 5;
// 2.分页参数
Page<User> page = Page.of(pageNumber, pageSize);
// 3.排序参数,如果第一个满足了,那就再用第二个进行排序
page.addOrder(new OrderItem("username", false));
page.addOrder(new OrderItem("amount", true));
// 4.分页查询
Page<User> p = userService.page(page,
      new LambdaQueryWrapper<User>().like(User::getUsername, "张"));
// 5.查看总条数和总页数
System.out.println("总条数是" + p.getTotal() + "\r\n" + "总页数是" + p.getPages());
// 6. 分页数据
List<User> records = p.getRecords();
records.forEach(System.out::println);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值