【02】手把手教你0基础部署SpringCloud微服务商城教学-Mybatis篇(下)

上期回顾:【01】手把手教你0基础部署SpringCloud微服务商城教学-Mybatis篇(上)

Part1.续接上文Mybatis-plus的批处理功能

接下来我们学习一下IService的批量查询,我们用以往的for循环做一个对比

这是for循环部分的代码

    private User builderUser(int i){
        User user=new User();
        user.setUsername("user_"+i);
        user.setPassword("123456");
        user.setPhone(""+(100000000L+i));
        user.setBalance(2000);
        user.setInfo("{\"age\":24,\"info\":\"英文老师\",\"gender\":\"female\"}");
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(user.getCreateTime());
        return user;
    }
    @Test
    void testSaveOneByOne(){
        long b=System.currentTimeMillis();
        for(int i=0;i<100000;i++){
            userService.save(builderUser(i));
        }
        long e=System.currentTimeMillis();
        System.out.println("耗时:"+(e-b));
    }

这是我们的耗时达到了十九万毫秒,可以看到效率还是非常差的,现在查看数据库会发现插入了非常多条的数据。

我们现在把id>9的数据都删掉。

ok,我们删除完数据后,用IService的批处理来尝试一下,看看性能会不会好一点。

如下是新的测试代码:

 @Test
    void testSaveBatch(){
        //我们每次批量插入1000条,插入100次即插入10万条数据
        //1.我们准备一个容量为1000的集合,创建1000个User对象
        List<User> list=new ArrayList<>(1000);
        long b=System.currentTimeMillis();
        for(int i=0;i<=100000;i++){
            //2.添加一个user
            list.add(builderUser(i));
            //3.每次插入1000条,执行一次批量插入
            if(i%1000==0){
                userService.saveBatch(list);
                //4.清空集合,准备下一批数据
                list.clear();
            }
        }
        long e=System.currentTimeMillis();
        System.out.println("耗时:"+(e-b));
    }

我们可以看到耗时从十九万骤降到一万八,差不多十倍的效率。

但是我们如果仍然觉得慢,我们就开始思考,通过查看底层代码

通俗的讲,老旧的for循环相当于是一次次插入数据,插入了一万次,而利用IService的方法,我们把一万次拆分成了一百个一千次。

而通过查看底层代码发现其实mp的批处理是基于PrepareStatement的预编译模式,然后批量提交,最终在数据库执行时还是会有多条insert语句,逐条插入数据。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);

那该如何做到呢?

我们需要开启rewriteBatchedStatements=true参数。

这个配置Mybatis3.13之后都是有的,不是mp做的。

自己拼一个数据,完整代码如下:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

可以看到这个时候我们的耗时就变成了7700多,相比于开始的十九万是极其巨大的提升。

Part2.代码生成功能

我们以往的开发业务中,一般是先定义一个实体类如User类,然后去写它的mapper,然后写service接口,serviceImpl类。

我们在业务开发中的代码大部分是固定的,只有类的名字发生变化。

这个时候我们可以引入一个同名叫MybatisPlus的插件。

然后注意,新版的idea是把other这个功能栏改进到了Tools里

写到这里,我们打开config Database会出现一个时区的错误。

The server time zone value 'йʱ' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.

我在网上进行了无数的搜索,有让改my.ini的。

也有说需要去下载时区补丁的,总之杂七杂八非常混乱。

在这我分说一下几个问题,如果这个时候我们问AI,它会有显示sh语句和sql语句,sh语句就是我们在cmd命令行里输入的,sql语句就是在数据库里执行的。

而且my.ini的位置非常不好找,

如图我的mySQL是在这两个里面分别有一个,但是包括我们在系统服务等功能里去寻找定位的时候,全部都是定位到Program Files下的mySQL,而最逆天的就是这个文件目录下的MySQL是没有my.ini的,无论是MySQL的根目录还是bin目录全都没有,这个时候你就要注意了。

你可能和我一样把文件和配置文件分开保管了,也不要去去跟着网上的教程搞什么自己新建一个ini,包括停止重启数据库这些操作,因为我已经把雷都趟完了,根本没用,下图可证我是真搞了一遍。

那么实际的解决办法居然简单的出乎意料,那就是你直接把yaml文件里的url地址拷贝一份直接复制到config database中,这时候你再进行测试,就直接显示成功了,后续插件的功能也能正常使用。

然后我们打开Code Generator

检查一下是否生成成功

咱们所有的配置都生成好了,不用自己写了,包括service继承什么都写好了。

Part3.静态工具Db

静态工具类是没有泛型的,我们就需要传id和实体类,那其他的地方都和前文讲过的IService接口是一样的,那我们为啥要用Db呢?

我们通过一个案例来解答这个疑惑。

案例需求:

①改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址

②改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,所以我们要用到Db。

循环依赖类似于死锁,如果现在有两个实例化的bean都在等对方的实例化,就导致对方看不到自己初始化的bean而不停等待实例化的bean,但三级缓存使初始化也能被看到从而解决了问题。

首先我们导入准备好的AdressVO。

在UserVO里新增一个收货地址

然后在Controller里直接重新写方法

Service里定义新方法

ServiceImpl里实现新方法

代码如下:

 @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
        //3.1转User的PO为VO
        UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
        //3.2转Address的PO为VO
        if (CollUtil.isNotEmpty(addresses)){
            userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
        }
        return userVO;
    }

然后我们启动SpringBoot测试一下

功能是正常的。

我们再来写第二个,因为前面的各个包里建的东西完全一样,所以我在这块只贴上实现类中的代码。

    @Override
    public List<UserVO> queryUserAndAddressByIds(List<Long> ids) {
        //1.查询用户
        List<User> users = listByIds(ids);
        if(CollUtil.isEmpty(users)){
            return Collections.emptyList();
        }
        //2.查询用户地址
        //2.1获取用户id集合
        List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
        //2.2根据用户id集合查询地址
        List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();
        //2.3转换地址VO
        List<AddressVO> addressVOList = BeanUtil.copyToList(addresses, AddressVO.class);
        //2.4梳理地址集合,分类整理,相同用户放入一个集合
        Map<Long,List<AddressVO>> adressMap=new HashMap<>();
        if (!CollUtil.isEmpty(addressVOList)) {
            adressMap = addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));
        }
        //3.转换VO返回
        List<UserVO> list=new ArrayList<>(users.size());
        for (User user : users){
            //3.1转换用户PO为VO
            UserVO vo = BeanUtil.copyProperties(user, UserVO.class);
            list.add(vo);
            //3.2转换地址PO为VO
            vo.setAddresses(adressMap.get(user.getId()));
        }
        return list;
    }

然后我们现在测试一下!

启动SpringBoot,打开网页。

然后出现了一个问题就是地址全是NULL???

然后仔细排查代码发现:

修改之后我们测试一下就发现完全OK了。

Part4.逻辑删除

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

那什么情况下会用到逻辑删除呢?

答:比如淘宝店的订单数据,我们做数据分析都是很宝贵的,那删除不可能真的从物理层面上删除,只能是逻辑上删除。

思路如下:

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

②当删除数据时把标设为1

③查询时只查询标记为0的数据。

例如逻辑删除字段为deleted:

删除操作:

UPDATE user SET deleted=1 where id=1 AND deleted=0

查询操作:

SELECT*FROM user WHERE deleted=0

当然如果我们一条一条改那也太麻烦了,mp已经帮我们提供了逻辑删除的功能,我们无需改变方法调用的方式,而是底层帮我们自动修改CRUD语句。

我们只需要在application.yaml文件中配置逻辑删除的字段名称和值即可。

然后我们现在修改一下yaml

现在让我们来试一试新功能吧!

写一个删除写一个查询

我们删除成功了,所以查询不到,返回了一个NULL。

总结:逻辑删除本身也有自己的问题→

比如会导致数据库的垃圾数据越来越多,影响查询效率

SQL中全都需要对逻辑删除字段做判断,影响查询效率

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

Part5.枚举处理器

User类中有一个用户状态字段,现在的状态还比较少,只有1和2,那如果以后状态类型非常多,我们很难全都靠大脑记住,那么该怎么解决呢?

我们就可以定义一个枚举类型。

直接用枚举类型代替Integer。

枚举是可以直接用==来进行比较的,这样我们代码中就不会有莫名其妙的一堆1和0,1和2进行比较了,而是一堆字段进行比较,我们代码的可读性就变好了。

但是方便也带来了一个问题,就是我们数据库表中关于status的定义还是INT类型。那我们就涉及到一个类型的相互转换,而这里面的所有类型转换都是Mybatis来在底层完成的。

而mp对这些处理器都做了拓展,我们现在解决这个问题就好办了,用现成的处理器就OK了。

第一件事,我们框框用注解标记就完事了。

第二件事,我们直接在application.yaml里配置一个全局枚举处理器就完事。

然后让我们使用一下吧

1.我们先把User里面的状态类型改一下

然后会发现有文件报错了,我们打开一看

进行一下小小的修改

我们启动SpringBoot,打开网页试一下。

Part6.JSON处理器

我们定义一个UserInfo类来代替String info。

但很遗憾mp终究不是万能的,这个时候我们就需要自己写一个JSON类型处理器了。

我们要做两件事

第一件事添加注解

第二件事给注解加加料,开启自动映射

我们会发现UserTest里这时候会有一堆报错,因为以前我们是手搓,现在需要用UserInfo方法去构建。

在这里我根据黑马的课发现如果直接写UserInfo.of的话,of会报错,我搜了一下也没找到原因,可能是mybatis的版本问题。

为了解决这个问题,我采用了一个比较笨拙的办法,就是自己去UserInfo中写一个of方法,代码如下:

这个时候我们就发现我们的整个程序都不会报错了。

现在我们直接alt+8去启动SpringBoot,来到网页做一下测试

我们的info已经从以前的一长串String变成这种样子了。

Part7.分页查询

MybatisPlus给我们提供了非常多的各种各样的功能的插件,但是我们大部分用不太上,所以我们就介绍一个分页插件。

1.首先我们要定义一个配置类,在配置类里注册Mp的核心插件,同时添加分页插件。

2.然后我们现在就可以去搞一个测试类试一下分页功能了。

我们编写完测试代码如果和我一样发现疯狂爆红,别慌张。

有可能是导包出现了问题

解决方法如下:

然后看看测试的那段代码

一切正常了。

我们现在做一下运行测试:

我们分页查询到的结果是正确的,没有问题。

感谢阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值