MybatisPlus教程-从入门到进阶

前言

首先它是国产的,所以直接用官网的简介。

  1. 简介

MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
在这里插入图片描述

  1. 特性
  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
    支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  1. 支持数据库

在这里插入图片描述

  1. 框架结构

在这里插入图片描述

好了,上面的都来自于官网,我们要做的就是把官网中各种零散的教程,整合成一个个通俗易懂的案例。

使用步骤

拢共就两步

  1. 引入MybatisPlus依赖,代替Mybatis依赖
 <!-- mybatis依赖直接移除 -->
 <!--        <dependency>-->
 <!--            <groupId>org.mybatis.spring.boot</groupId>-->
 <!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
 <!--            <version>3.x.xx</version>-->
 <!--        </dependency>-->

 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.5.10.1</version>
 </dependency>
  1. 定义Mapper接口并继承BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

快速入门-入门案例

需求:基于课前资料提供的项目,实现下列功能:

  • 新增用户功能
  • 根据id查询用户
  • 根据id批量查询用户
  • 根据id更新用户
  • 根据id删除用户
入门准备
  1. 创建一个springboot项目

详情参考SpringBootWeb快速入门。

  1. 创建mysql用户表
create table user
(
    id          bigint primary key auto_increment,
    username    varchar(50),
    password    varchar(50),
    phone       varchar(50),
    status      int default 1,
    balance     int,
    info        varchar(255),
    create_time datetime,
    insert_time datetime
)
  1. 创建对应的实体类
package com.example.javawebdemo.entity;

import lombok.Data;

import java.time.LocalDateTime;

@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 insertTime;
}
  1. 创建数据库配置文件
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/demo

为了可以在控制台日志中看到打印SQL语句,其实我们可以设置的,在application.yml中添加

mybatis-plus:
    configuration:
       log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
套用mybatis-plus上面两个步骤
  1. 引入依赖
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.10.1</version>
</dependency>
  1. 定义Mapper接口并继承BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

BaseMapper定义了大量的增删改查方法,我们继承它就实现了 user 表的 CRUD 功能,甚至连 XML 文件都不用编写!

这里还需要在启动类上面加一个@MapperScan注解,来标注mapper包的位置

package com.example.javawebdemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("com.example.javawebdemo.mapper")
@SpringBootApplication
public class JavawebDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(JavawebDemoApplication.class, args);
    }
}
编写测试类实现入门案例
  1. 新增用户
package com.example.javawebdemo;

import com.example.javawebdemo.mapper.UserMapper;
import com.example.javawebdemo.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
class JavawebDemoApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void insertUser() {
        User user = new User();
        user.setId(1L);
        user.setUsername("Jack");
        user.setPassword("123456");
        user.setPhone("13111111111");
        user.setInfo("{\"age\":20,\"gender\":\"male\"}");
        user.setStatus(1);
        user.setBalance(200);
        user.setCreateTime(LocalDateTime.now());
        user.setInsertTime(LocalDateTime.now());
        userMapper.insert(user);
    }
}

可以看到我们根本没有配置xml,甚至连sql语句都没写就实现了插入数据的功能。

  1. 根据id查询用户
    @Test
    public void selectUserById() {
        User user = userMapper.selectById(1);
        System.out.println(user);
    }
  1. 根据id批量查询用户

目前表中只有1条数据,我们再插入id=2的一条数据,来测试批量查询:

    @Test
    public void selectUserByIds() {
        List<User> users = userMapper.selectByIds(Arrays.asList(1, 2));
        for (User user : users) {
            System.out.println(user);
        }
    }

在这里插入图片描述

  1. 根据id更新用户
    @Test
    public void updateUserById() {
        User user = new User();
        user.setId(1);
        user.setBalance(20000);
        userMapper.updateById(user);
    }

根据执行日志,可以看出会根据id只更新balance字段:
在这里插入图片描述
在这里插入图片描述

  1. 根据id删除用户
    @Test
    public void deleteUserById() {
        userMapper.deleteById(1);
    }

就这样单表的增删改查就完成了,的确是省事啊。但是它是怎么知道访问哪张表,以及表里有哪些字段的呢?继续往下看…

快速入门-常见注解

MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
在这里插入图片描述

并且有下面的约定:

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

所以我们在写实体类的时候尽量根据表的DDL来创建,简单来说约定大于配置。

但是如果我们实体类不符合这些约定怎么办?

不符合就得根据提供的注解来处理这些表名和字段名的映射了。

最常见的注解如下:

  • @TableName:用来指定表名
  • @TableId:用来指定表中的主键字段信息
  • @TableField:用来指定表中的普通字段信息

接下来我们详细讲解一下常见的注解。

TableName

用来映射数据库的表名。

上面的例子中我们可以看到,为什么MP会准确找到user这张表进行查询?是不是就是因为我们创建的实体类,名字就叫User,所以可以和数据库中的user表对应起来的关系。

那假如现在我的实例类名称换成Student,测试一下刚才写的根据id查用户的方法,看下什么效果:

Caused by: java.sql.SQLSyntaxErrorException: Table 'demo.student' doesn't exist

那么有时候我的实体类名称不一定和数据库中的表名能完全对应上,这时候@TableName注解就派上用场了。

我们可以在Student实体类加上这个注解​​​​​​​:

@TableName("user")
@Data
public class Student {

再查就没有问题了。

@TableField

@TableName注解是把实体类和表做映射,那么表里面还有字段信息,类属性和表字段肯定也是一一映射的。
如果类属性和表字段无法一一对应时,假如我将 ”username“ 这个属性修改为 ”name“,调用查询方法就会报错:

Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'name' in 'field list'

就需要@TableField注解的 value 属性即可解决。

    @TableField("username")
    private String name;

除了value属性之外,还有两个我们需要关注的属性。

  1. exist

当我们的实体类中定义了一些数据库中不存在的字段时​​​​​​​:

private String nickname;

调用查询接口就会报错:

Caused by: java.sql.SQLSyntaxErrorException: Unknown column 'nickname' in 'field list'

这种情况只需要设置一下exist属性即可解决​​​​​​:

    @TableField(exist = false)
    private String nickname;
  1. select

该属性表示是否查询该字段。

比如我们给balance字段设置了select=false:

    @TableField(select = false)
    private Integer balance;

我们调用查询语句就会发现SQL语句中就不再查询balance这个字段了。

==>  Preparing: SELECT id,username AS name,password,phone,info,status,create_time,insert_time FROM user WHERE id=?
==> Parameters: 1(Integer)
<==    Columns: id, name, password, phone, info, status, create_time, insert_time
<==        Row: 1, Jack, 123456, 13111111111, {"age":20,"gender":"male"}, 1, 2025-02-19 14:33:54, 2025-02-19 14:33:54
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@11f9535b]
Student(id=1, name=Jack, nickname=null, password=123456, phone=13111111111, info={"age":20,"gender":"male"}, status=1, balance=null, createTime=2025-02-19T14:33:54, insertTime=2025-02-19T14:33:54)
  1. fill

表示是否自动填充。参考官网
在我们日常的开发工作中,数据库表里面通常都会有create_time、update_time字段,而这两个字段通常都是取当前时间来插入,我们就需要使用到自动填充。

    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime insertTime;

FieldFill是个枚举类​​​,有下面几种类型:

  • DEFAULT:不填充
  • INSERT:在插入的时候填充
  • UPDATE:在更新的时候填充
  • INSERT_UPDATE:在插入或者更新时填充​​​​

做完这些,程序还不会帮我们自动去填充这两个字段,我们需要创建一个处理器​​​​​​​:

package com.example.javawebdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // 此处的metaObject代表当前对象
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("insertTime", LocalDateTime.now(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("insertTime", LocalDateTime.now(), metaObject);
    }
}

我们再来调用保存用户的方法进行测试:

    @Test
    public void insertUser() {
        Student student = new Student();
        student.setId(2L);
        student.setName("Rose");
        student.setPassword("123456");
        student.setPhone("13222222222");
        student.setInfo("{\"age\":18,\"gender\":\"female\"}");
        student.setStatus(1);
        student.setBalance(2000);
//        student.setCreateTime(LocalDateTime.now());
//        student.setInsertTime(LocalDateTime.now());
        userMapper.insert(student);
    }

可以看到两个时间被插入进来了:

==>  Preparing: INSERT INTO user ( id, username, password, phone, info, status, balance, create_time, insert_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 2(Long), Rose(String), 123456(String), 13222222222(String), {"age":18,"gender":"female"}(String), 1(Integer), 2000(Integer), 2025-02-19T15:33:55.894(LocalDateTime), 2025-02-19T15:33:55.894(LocalDateTime)
@TableId

@TableField注解是用来处理数据库中普通字段的,@TableId是用于处理主键字段的。
这注解中有几个属性需要重点关注一下:

  • value

映射主键id的字段名,这个和TableField中的value类似。

  • type

设置主键类型,主键的生成策略。这个type的值是使用一组枚举类型来表示的,它一共有五种类型​​​​​​​:

类型描述
AUTO数据库自增
NONE(默认)MP来设置主键,使用雪花算法实现
INPUT需要自己给主键赋值
ASSIGN_IDMP使用Long、Integer、String来分配ID
ASSIGN_UUIDMP分配UUID
  • NONE

我们的id字段是自增主键,但是我们上面测试插入数据的方法的时候,依然手动指定了,如果我们不指定默认情况就会是雪花算法实现的主键:

    @Test
    public void insertUser() {
        Student student = new Student();
        student.setName("Lucy");
        student.setPassword("123456");
        student.setPhone("13333333333");
        student.setInfo("{\"age\":18,\"gender\":\"female\"}");
        student.setStatus(1);
        student.setBalance(2000);
        userMapper.insert(student);
    }

我们看一下这个主键id是1892117103913984001是雪花算法生产的数字:

==>  Preparing: INSERT INTO user ( id, username, password, phone, info, status, balance, create_time, insert_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 1892117103913984001(Long), Lucy(String), 123456(String), 13333333333(String), {"age":18,"gender":"female"}(String), 1(Integer), 2000(Integer), 2025-02-19T15:40:46.071(LocalDateTime), 2025-02-19T15:40:46.071(LocalDateTime)
  • INPUT​​​​​​​

这种类型比较简单,就是你手工在程序里给主键赋值。

  • AUTO

这种方式就是交给数据库去处理,该自增自增。开发者无需处理了(就算你在程序里面手工给主键赋值,也只能按照数据库的自增来,相当于赋值无用)。

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

我们表中就是自增id,因此一般可以用这种类型。

  • ASSIGN_ID 和 ASSIGN_UUID

当使用该属性,同样是MP采用雪花算法生成一个随机值,赋值给你类中id属性,并把该属性插入到数据库,和ASSIGN_UUID的区别在于,ASSIGN_UUID规定id必须是String类型,数据库字段必须是varchar类型,而且数据库原先的主键自增得去掉,否则也会报错,字符串类型是无法自增的。

快速入门-常用配置

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

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true # 是否开启下划线和驼峰映射
    cache-enabled: false # 是否开启二级缓存
  global-config:
    db-config:
      id-type: auto
      update-strategy: not_null # 更新策略:只更新非空字段
  type-aliases-package: com.example.javawebdemo.entity # 别名扫描包
  mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值

其他配置参考官网:https://baomidou.com/reference/

  • type-aliases-package

MyBatis-Plus 会自动为 com.example.javawebdemo.entity 包下的每个类创建一个别名。默认别名是类名的 首字母小写 形式。例如,包中有一个名为 User 的类,则 MyBatis 会自动为它生成别名 user。

配置别名后,你可以在 MyBatis 的 XML 映射文件中直接使用这些别名,而不需要全限定类名,例如:

<resultMap id="UserResultMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
</resultMap>

在 type 属性中使用 user 别名,代替了 com.example.javawebdemo.entity.User,从而使 XML 文件更简洁,提升代码可读性和可维护性。

核心功能-条件构造器

上面的查询都是基于id进行的,有些查询场景可能比较复杂,MP就提供了条件构造器来解决这类问题。

MyBatisPlus支持各种复杂的where条件,可以满足日常开发的所有需求。
在这里插入图片描述
这里的这些方法中需要传入Wrapper就是条件构造器,下面的Wrapper的继承体系:

在这里插入图片描述
我们这里截了有些Wrapper使用方法的图:
在这里插入图片描述

QueryWrapper顾名思义是做查询的,select * from tb where xxx,父类解决了where后面的条件,QueryWrapper拓展了select功能:
在这里插入图片描述
UpdataWrapper拓展了update功能:
在这里插入图片描述

  1. 基于QueryWrapper的查询案例
  • 查询出名字中带o的,存款大于等于1000元的人的id、username、info、balance字段
    @Test
    public void testQueryWrapper(){
        // 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);
        for (User user : users) {
            System.out.println(user);
        }
    }

我们看一下查询日志:

==>  Preparing: SELECT id,username,info,balance FROM user WHERE (username LIKE ? AND balance >= ?)
==> Parameters: %o%(String), 1000(Integer)
<==    Columns: id, username, info, balance
<==        Row: 2, Rose, {"age":18,"gender":"female"}, 2000
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44a085e5]
User(id=2, username=Rose, password=null, phone=null, info={"age":18,"gender":"female"}, status=null, balance=2000, createTime=null, insertTime=null)
  • 更新用户名为Jack的用户的余额为2000
    @Test
    public void testQueryWrapper() {
        // 1. 要更新的数据
        User user = new User();
        user.setBalance(2000);
        // 2. 更新的条件
        QueryWrapper<User> wrapper = new QueryWrapper<User>()
                .eq("username", "Jack");
        // 3. 执行更新
        userMapper.update(user, wrapper);
    }

我们看看执行过程日志:

==>  Preparing: UPDATE user SET balance=?, insert_time=? WHERE (username = ?)
==> Parameters: 2000(Integer), 2025-02-19T16:50:01.476(LocalDateTime), Jack(String)

在这里插入图片描述

  1. 基于UpdateWrapper的更新
  • 更新id为1,2的用户的余额,扣200
    @Test
    public void testUpdateWrapper() {
        List<Integer> list = Arrays.asList(1, 2);
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", list);
        userMapper.update(wrapper);
    }

查看执行过程:

==>  Preparing: UPDATE user SET balance = balance - 200 WHERE (id IN (?,?))
==> Parameters: 1(Integer), 2(Integer)

在这里插入图片描述

我们这里再演示一下LambdaQueryWrapper的用法,其实和QueryWrapper是类似的,区别在于它在构造条件时是基于lambda语法,这样就可以在选择字段的时候不用硬编码了,我们现在都是把字段名称写死了:

    @Test
    public void testLambdaQueryWrapper() {
        // 1. 构建查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
                .like(User::getUsername, "o")
                .ge(User::getBalance, 1000);
        // 2. 查询
        List<User> users = userMapper.selectList(wrapper);
        for (User user : users) {
            System.out.println(user);
        }
    }
  1. 条件构造器的用法:
  • QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分
  • UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊才使用
  • 尽量使用LambdaQueryWrapper和LambdaUpdateWrapper,避免硬编码

核心功能-自定义SQL

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

我们用案例来体验一下:

  1. 需求:将id在指定范围的用户(例如1、2)的余额扣减指定值

这个需求我们上面就做过:

    @Test
    public void testUpdateWrapper() {
        List<Integer> list = Arrays.asList(1, 2);
        UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
                .setSql("balance = balance - 200")
                .in("id", list);
        userMapper.update(wrapper);
    }

上面的代码有什么问题呢?我们现在写的逻辑是业务逻辑,将来是在service层定义,这就相当于我们把一部分sql语句写到service层了,这在很多企业开发规范中是不允许的,他们会要求只能在mapper和mapper.xml中定义SQL语句,所以上面的写法就得改造了。

既然MP擅长where条件的构建,一旦出现复杂场景,比如:
在这里插入图片描述
在这里插入图片描述
那就把where条件交给MP做,剩下的自定义。把MP构建好的where条件传递给mapper层,在mapper层或者mapper.xml中实现SQL组装,那具体怎么实现传递和组装呢?需要三步:

  • 基于Wrapper构建where条件

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

我们来跟着这三个步骤实现一下:

(1)基于Wrapper构建where条件

    @Test
    public void testCustomSqlUpdate() {
        // 更新条件
        List<Integer> ids = Arrays.asList(1, 2);
        int amount = 200;
        // 定义条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
                .in(User::getId, ids);
        // 自定义SQL
        userMapper.updateBalanceByIds(wrapper,amount);
    }

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

package com.example.javawebdemo.mapper;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.example.javawebdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper extends BaseMapper<User> {

    void updateBalanceByIds(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper, @Param("amount") int amount);

}

(3)自定义SQL,并使用Wrapper条件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.javawebdemo.mapper.UserMapper">
    <update id="updateBalanceByIds">
        UPDATE user SET balance = balance - #{amount} $(ew.customSqlSegment)
    </update>
</mapper>

接下来我们测试一下:

==>  Preparing: UPDATE user SET balance = balance - ? WHERE (id IN (?,?))
==> Parameters: 200(Integer), 1(Integer), 2(Integer)

核心功能-IService接口基本用法

MP还提供了IServcie接口,只要继承了它,连Service代码都可以不用写了。

下面我们看看IService提供的一些方法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到把常用的增删改查都封装好了。

使用IService之前,看看我们之前开发流程:
在这里插入图片描述

需要自定义Service接口,并写一个实现类。

现在IService中有大量封装好的方法,我们只要继承它就可以,现在的继承体系如下:
在这里插入图片描述

总结下来MP的Service接口使用流程就是两步:

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

在这里插入图片描述

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

在这里插入图片描述

接下来我们按照这两步骤,代码中写一下:

(1)自定义Service接口继承IService接口

package com.example.javawebdemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.javawebdemo.entity.User;

public interface IUserService extends IService<User> {
}

(2)自定义Service实现类,实现自定义接口并继承ServiceImpl类

package com.example.javawebdemo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.javawebdemo.entity.User;
import com.example.javawebdemo.mapper.UserMapper;
import com.example.javawebdemo.service.IUserService;
import org.springframework.stereotype.Service;

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

然后我们写逻辑测试一下:

  • 新增用户
    @Autowired
    private IUserService userService;
    @Test
    public void testSaveUser(){
        User user = new User();
        user.setUsername("Curry");
        user.setPassword("123456");
        user.setPhone("18888888888");
        user.setInfo("{\"age\":30,\"gender\":\"male\"}");
        user.setStatus(1);
        user.setBalance(20000000);
        userService.save(user);
    }

执行日志可以看到插入成功了:

==>  Preparing: INSERT INTO user ( username, password, phone, info, status, balance, create_time, insert_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: Curry(String), 123456(String), 18888888888(String), {"age":30,"gender":"male"}(String), 1(Integer), 20000000(Integer), 2025-02-20T09:54:39.429(LocalDateTime), 2025-02-20T09:54:39.429(LocalDateTime)

在这里插入图片描述

这里有个问题,就是我们在配置文件中配了全局id自增策略,由于刚开始默认是雪花的,所以后面就算是已经设置了自增,也是基于之前ID数,自增了。

  • 根据id查询
    @Test
    public void testQuery(){
        List<User> users = userService.listByIds(Arrays.asList(1, 2));
        for (User user : users) {
            System.out.println(user);
        }
    }

执行日志:

==>  Preparing: SELECT id,username,password,phone,info,status,balance,create_time,insert_time FROM user WHERE id IN ( ? , ? )
==> Parameters: 1(Integer), 2(Integer)
<==    Columns: id, username, password, phone, info, status, balance, create_time, insert_time
<==        Row: 1, Jack, 123456, 13111111111, {"age":20,"gender":"male"}, 1, 1600, 2025-02-19 14:33:54, 2025-02-19 16:54:06
<==        Row: 2, Rose, 123456, 13222222222, {"age":18,"gender":"female"}, 1, 1600, 2025-02-19 15:33:56, 2025-02-19 15:33:56
<==      Total: 2

核心功能-IService开发基础业务接口

我们发现MP提供的BaseMapper和IService中的方法很多是重复的,那实际业务开发中,到底该使用哪个接口提供的方法呢?接下来用几个案例来看看实际开发如何使用。

  1. 基于Restful风格实现下列接口

在这里插入图片描述

这个不是像上面做单元测试,而是写接口,做完成的业务开发。在实现之前要做点准备工作:

(1)引入相关依赖

  • 引入Swagger依赖,方便做单元测试
  • 引入Web依赖,编写Swagger相关功能
        <!--  swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
            <version>4.1.0</version>
        </dependency>
        <!-- web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
         <!-- hutool用到BeanUtil等工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.21</version>
        </dependency>

(2)swagger相关配置

knife4j:
  enable: true
  openapi:
    title: 用户接口管理文档
    description: "用户接口管理文档"
    email: xuec_7@163.com
    concat: sunny
    url: https://sunnyrivers.blog.csdn.net/
    version: v1.0.0
    group:
      default:
        group-name: default
        api-rule: package
        api-rule-resources:
          - com.example.javawebdemo.controller

(3)实体类

我们把所有实体相关类都放在domain包:

在这里插入图片描述

package com.example.javawebdemo.domain.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户表实体")
public class UserFromDTO {
    @ApiModelProperty("id")
    private Long id;
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;
    @ApiModelProperty("注册手机号")
    private String phone;
    @ApiModelProperty("详细信息,JSON风格")
    private String info;
    @ApiModelProperty("账户余额")
    private Integer balance;
}

新增用户接口开发

package com.example.javawebdemo.controller;

import cn.hutool.core.bean.BeanUtil;
import com.example.javawebdemo.domain.dto.UserFromDTO;
import com.example.javawebdemo.domain.entity.User;
import com.example.javawebdemo.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor
public class UserController {
    // @Autowired   Spring不推荐用这种方法注入:  Field injection is not recommended ,推荐用构造函数
    // 构造函数可以用lombok简写,但是用哪个构造注解呢?一个类的成员变量有很多,但并不是所有的都需要注入
    // 首先给类加上final,也就是必须在初始化的时候,给变量初始化,
    // 然后用@RequiredArgsConstructor注解,它只会对一些需要一开始初始化的变量构造
    private final IUserService userService;

    @ApiOperation("新增用户接口")
    @PostMapping
    public void saveUser(@RequestBody UserFromDTO userDTO) {
        // 以前我们都是先写service方法,然后再写mapper方法,现在我们有了MP后,我们写接口的时候,
        // 我们先考虑这个功能MP是否已经提供了?

        // 1.把DTO拷贝到ENTITY
        User user = BeanUtil.copyProperties(userDTO, User.class);
        // 2. 保存
        userService.save(user);
    }
}

我们一行service代码都没有编写,一个新增用户的接口就开发完成了。

同理删除用户接口

    @ApiOperation("删除用户接口")
    @DeleteMapping("{id}")
    public void deleteUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
       userService.removeById(id);
    }

根据id查询用户接口

    @ApiOperation("根据id查询用户接口")
    @GetMapping("{id}")
    public UserVO queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
        User user = userService.getById(id);
        // 把entity拷贝到VO
        return BeanUtil.copyProperties(user, UserVO.class);
    }

根据id批量查询接口

    @ApiOperation("根据id批量查询用户接口")
    @GetMapping()
    public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
        List<User> users = userService.listByIds(ids);
        return BeanUtil.copyToList(users, UserVO.class);
    }

上面的接口都比较简单,没什么业务逻辑,都直接在controller里面都搞定了,有些接口有复杂的业务逻辑,就需要自定义service,当然还有些复杂的,我们还要在mapper中自定sql语句。

根据id扣减余额接口

这个接口需要写业务逻辑,比如拿到用户的id,先要判断状态是否正常,只有正常才扣减,还要判断余额是否充足,只有充足才能扣减,所以这里面有很多业务判断,而MP中的方法是没有业务逻辑的。
还有我们更新余额的sql语句,不建议在业务层来写,需要自定义SQL语句完成更新。

controller层:

    @ApiOperation("扣减余额接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance(
            @ApiParam("用户id") @PathVariable("id") Long id,
            @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
        userService.deductBalance(id, money);
    }

service层:

package com.example.javawebdemo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.javawebdemo.domain.entity.User;
import com.example.javawebdemo.mapper.UserMapper;
import com.example.javawebdemo.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Override
    public void deductBalance(Long id, Integer money) {
        // 查询用户
        User user = getById(id);
        // 校验用户状态
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常");
        }
        // 检验余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足");
        }
        // 扣减余额
        baseMapper.deductBalance(id, money);
    }
}

mapper层:

package com.example.javawebdemo.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.javawebdemo.domain.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper extends BaseMapper<User> {

    void deductBalance(@Param("id") Long id, @Param("money") Integer money);
}

mapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.javawebdemo.mapper.UserMapper">
    <update id="deductBalance">
        UPDATE user SET balance = balance - #{money}
        <where>
            id = #{id}
        </where>
    </update>
</mapper>

写完代码,我们启动服务进行测试,先打开swagger:
http://localhost:8080/doc.html
在这里插入图片描述
接下来就可以在swagger中进行接口测试了。

在这里插入图片描述
其他接口可以进行测试,这里就不演示了。

核心功能-IService提供的lambda方法

我们依然是通过案例来讲解

  1. 需求一:实现一个根据复杂条件查询用户的接口,查询条件如下:
    (1)name:用户名关键字,可以为空
    (2)status:用户状态,可以为空
    (3)minBalance:最小余额,可以为空
    (4)maxBalance:最大余额,可以为空

这个需求如果我们按照之前mybatis的写法将会是这样的:

    <select id="queryUsers" resultType="com.example.javawebdemo.domain.entity.User">
        SELECT * FROM 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>

可以看到这种写法非常麻烦,我们看看IService的Lambda是如何做的。

这里写的方法中参数可以用@RequestParam(value = “xxx”,required = false)来注解,但是如果参数很多就推荐用定义一个对象来接收:

package com.example.javawebdemo.domain.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
    @ApiModelProperty("用户名关键字")
    private String name;
    @ApiModelProperty("用户状态:1-正常,2-冻结")
    private Integer status;
    @ApiModelProperty("余额最小值")
    private Integer minBalance;
    @ApiModelProperty("余额最大值")
    private Integer maxBalance;
}

controller层:

    @ApiOperation("根据复杂条件查询用户接口")
    @GetMapping("/list")
    public List<UserVO> queryUser(UserQuery query) {
        List<User> users = userService.queryUser(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
        return BeanUtil.copyToList(users, UserVO.class);
    }

service层:

    @Override
    public List<User> queryUser(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();
    }

重启服务进行测试:
在这里插入图片描述

我们查询名字中带o的用户:
在这里插入图片描述

除了查询外还提供了一个更新的方法,也很方便,我们用案例讲解。

  1. 需求:改造根据id修改用户余额的接口,要求如下
    (1)完成对用户状态校验
    (2)完成对用户余额校验
    (3)如果扣减后余额为0,则将用户status修改为冻结状态-2

controller层:

    @ApiOperation("扣减余额接口")
    @PutMapping("/{id}/deduction/{money}")
    public void deductBalance(
            @ApiParam("用户id") @PathVariable("id") Long id,
            @ApiParam("扣减的金额") @PathVariable("money") Integer money) {
        userService.deductBalance(id, money);
    }

service层:

    @Transactional
    public void deductBalance(Long id, Integer money) {
        // 查询用户
        User user = getById(id);
        // 校验用户状态
        if (user == null || user.getStatus() == 2) {
            throw new RuntimeException("用户状态异常");
        }
        // 检验余额是否充足
        if (user.getBalance() < money) {
            throw new RuntimeException("用户余额不足");
        }
        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();
    }

重启服务进行测试:
在这里插入图片描述
Lucy余额原来的1800,扣减1800后余额为0,状态也改成2了:
在这里插入图片描述

  1. 需求3:批量插入10万条用户数据,并作出对比:
    (1)普通for循环插入
    (2)IService的批量插入
    (3)开启rewriteBatchedStatements=true参数

我们先写个方法,给数据库中插入10W条数据,用普通for循环插入:

    private User buildUser(int i) {
        User user = new User();
        user.setUsername("user_" + i);
        user.setPassword("123456");
        user.setPhone("" + (18688190000L + i));
        user.setInfo("{\"age\":30,\"gender\":\"male\"}");
        user.setBalance(2000);
        return user;
    }

    @Test
    void testSaveOneByOne() {
        long b = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            userService.save(buildUser(i));
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

在这里插入图片描述
接下来我们把数据清空,接下来我们测试一下批处理:

    @Test
    void testSaveBatch() {
        // 每次批量插入1000条,插入100次
        List<User> list = new ArrayList<>(1000);
        long b = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            list.add(buildUser(i));
            if (i % 1000 == 0) {
                userService.saveBatch(list);
                list.clear();
            }
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - b));
    }

在这里插入图片描述
可以看到效率提升还是比较明显,核心就是减少网络请求的次数。如果把sql语句变成类似于:
在这里插入图片描述

那句只需要1次网络请求,效率会更快。

其实只要把mysql的参数rewriteBatchedStatements=true即可。

在这里插入图片描述

默认情况下,MySQL JDBC驱动(Connector/J)的addBatch()方法仅作语句缓存,实际执行时会逐条发送SQL语句到数据库,开启rewriteBatchedStatements=true后,驱动会重写批量语句为更高效的格式:

INSERT INTO table (col1,col2) VALUES (1,2),(3,4),(5,6)  -- 合并为多值语句
UPDATE table SET col=val WHERE id=1; UPDATE table SET col=val WHERE id=2  -- 分号连接多语句

接下来我们重新执行批处理的代码,发现10W条数据大约用了7s就插入完成了。

总结一下批处理的三种方案:
在这里插入图片描述

扩展功能-静态工具

静态工具提供的方法和IService方法基本上一样,只不过IService方法中的接口都是非静态的,而Db静态工具里面的方法都是静态的,我们用IService的时候就需要继承它,还要传入实体类,这样IService就能通过反射得到实体类的字节码,得到表信息从而实现增删改查;而静态工具的每个方法都需要传入实体类的字节码:
在这里插入图片描述

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

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

那既然两个功能相似为什么要搞两个呢?
开发业务的时候,有的时候会出现多个service相互调用的场景,如果采用传统的方式,也就是用@Autowired来注入,那Service之间相互注入就会出现循环依赖,因此此时就直接用静态工具。

扩展功能-逻辑删除

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

  • 在表中添加一个字段标记数据是否被删除
  • 当删除数据时把标记置为1
  • 查询时只查询标记为0的数据

例如逻辑删除字段为deleted:

  • 删除操作:
    在这里插入图片描述

  • 查询操作:

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

扩展功能-枚举处理器

枚举处理器可以实现java中的枚举类型和数据库之间的相互转换。
比如现在有下面User实体类,用户状态1代表正常、2代表冻结:
在这里插入图片描述
我们在比较或者赋值的时候都是基于数字进行的,如果类型有很多的情况下,那我们就不知道哪个数字对应什么状态了,这个时候我们就可以用枚举来表示:
在这里插入图片描述
那将来不管有多少枚举项,都可以在这写出来,这样一看到枚举对于每个数字代表什么状态就一目了然了。

现在状态字段是Integer类型,我们在用set赋值的时候依然得赋1和2,这样还存在两个问题:

  1. 每次比较或赋值的时候还得查一下枚举
  2. 代码的可读性很差

为了解决这个问题,这里的状态字段就不要再用Integer类型了,就应该用枚举类型:

private UserStatus status;

但是这样又有一个问题:我们数据表中status依然是int类型,这就存在java中的枚举类型和数据库之间相互转换的问题。其实不仅仅是枚举类型,java中的所有类型都需要和数据库类型进行相互转换,只不过底层都是由mybatis帮我们做了:
在这里插入图片描述
我们看到上面也有枚举类型,但是它的功能有限,仅支持枚举的序数(ordinal())或名称(name())与数据库字段的简单映射。例如,将枚举的ordinal()值(如0,1,2)存入数据库,功能较为基础:

public enum UserStatus {
    ENABLED,  // ordinal = 0 
    DISABLED; // ordinal = 1 
}
  • 插入数据
    当插入一个 User 对象时,若 status = UserStatus.ENABLED,MyBatis 会将其转换为 0 存入数据库。
  • 查询数据
    若数据库中的 status 值为 1,MyBatis 会将其转换为 UserStatus.DISABLED。
  • 存在问题
    如果调整枚举定义的顺序(例如将 DISABLED 定义在 ENABLED 之前),ordinal() 值会变化,导致历史数据错乱。

所以上面有两个是mybatisplus进行扩展的类:MybatisEnumTypeHandler和AbstractJsonTypeHandler。

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

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

在这里插入图片描述

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

在这里插入图片描述

我们做个测试:

定义枚举类:

package com.example.javawebdemo.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

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

配置文件:

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

用枚举类替换Integer:

package com.example.javawebdemo.domain.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.example.javawebdemo.enums.UserStatus;
import lombok.Data;

import java.time.LocalDateTime;

@TableName("user")
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String phone;
    private String info;
    private UserStatus status;
    private Integer balance;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime insertTime;
}

插件功能

MyBatisPlus提供的内置拦截器有下面这些:
在这里插入图片描述
我们这里主要讲解分页插件的使用:

  1. 注册一个配置类​​​​​​​,并编写paginationInterceptor方法
@Configuration
public class MybatisPlusConfig {
 
	@Bean
	public PaginationInterceptor paginationInterceptor() {
	  return new PaginationInterceptor();
	}
 
}
  1. 编写分页查询代码
@Test
void select() {
  // Page构造方法有两个参数:current-当前页;size:-页大小
  Page<User> page = new Page<>(1, 10);
  // 返回值也是一个Page类型,里面包含了分页信息以及查询出来的数据信息。
  Page<User> result = mapper.selectPage(page, null);
  // 查询的结果在Records中
  result.getRecords().forEach(System.out::println);
}

参考文献

https://baomidou.com/guides/mybatis-x/
https://blog.csdn.net/qq_41320700/article/details/143837356

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/973533.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法1-4 数楼梯

题目描述 楼梯有 N 阶&#xff0c;上楼可以一步上一阶&#xff0c;也可以一步上二阶。 编一个程序&#xff0c;计算共有多少种不同的走法。 输入格式 一个数字&#xff0c;楼梯数。 输出格式 输出走的方式总数。 输入输出样例 输入 #1 4 输出 #1 5 说明/提示 对于…

DigitalOcean H200 GPU裸机服务器上线!可更好支持DeepSeek满血版

在 DigitalOcean&#xff0c;我们始终致力于为开发者、初创企业和人工智能驱动型公司提供更便捷的高性能计算资源&#xff0c;助力其业务扩展。今日&#xff0c;DigitalOcean 隆重推出基于 NVIDIA HGX H200 AI 超级计算平台的裸机服务器&#xff0c;专为高性能AI工作负载而生。…

企业组网IP规划与先关协议分析

目录 一、IP编址 1、IP地址组成 2、IP地址表达 3、IP 地址分类 4、IP地址类型 5、IP网络通信 6、子网掩码 7、默认子网掩码 8、IP 地址规划 9、有类IP编制缺陷 10、VLSM 11、变长子网掩码案例 12、网关 13、无类域间路由 一、IP编址 网络层位于数据链路层与传输层之间…

Python之装饰器三 踩坑(带参数,不带参数,两者都带参数)

文章目录 前言一、装饰器不带参数(但是装修器内部的函数又需要参数)二、装饰器带参数(但是被装饰的函数不带参数)三、装饰器带参数(并且被装饰的函数也带参数)总结前言 Python装饰器里面遇到的踩坑点,以及自己的理解。 一、装饰器不带参数(但是装修器内部的函数又需要…

蓝桥杯好数

样例输入&#xff1a; 24 输出&#xff1a;7 输入&#xff1a;2024 输出&#xff1a; 150 思路&#xff1a;本题朴素方法的时间复杂度是O(n * log10(n)) &#xff0c;不超时。主要考察能否逐位取数&#xff0c;注意细节pi&#xff0c;这样不会改变i,否则会导致循环错误。 #in…

人工智能之自动驾驶技术体系

自动驾驶技术体系 自动驾驶技术是人工智能在交通领域的重要应用&#xff0c;旨在通过计算机视觉、传感器融合、路径规划等技术实现车辆的自主驾驶。自动驾驶不仅能够提高交通效率&#xff0c;还能减少交通事故和环境污染。本文将深入探讨自动驾驶的技术体系&#xff0c;包括感…

Vue 实现通过URL浏览器本地下载 PDF 和 图片

1、代码实现如下&#xff1a; 根据自己场景判断 PDF 和 图片&#xff0c;下载功能可按下面代码逻辑执行 const downloadFile async (item: any) > {try {let blobUrl: any;// PDF本地下载if (item.format pdf) {const response await fetch(item.url); // URL传递进入i…

Microsoft 365 Copilot中使用人数最多的是哪些应用

今天在浏览Microsoft 365 admin center时发现&#xff0c;copilot会自动整理过去30天内所有用户使用copilot的概况&#xff1a; 直接把这个图丢给copilot让它去分析&#xff0c;结果如下&#xff1a; 总用户情况 总用户数在各应用中均为 561 人&#xff0c;说明此次统计的样本…

Spring Boot (maven)分页4.0.1版本 专业版- 改

前言&#xff1a; 通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往…

装修流程图: 装修前准备 → 设计阶段 → 施工阶段 → 安装阶段 → 收尾阶段 → 入住

文章目录 引言I 毛坯房装修的全流程**1. 装修前准备****1.1 确定装修预算****1.2 选择装修方式****1.3 选择装修公司****1.4 办理装修手续****2. 设计阶段****2.1 量房****2.2 设计方案****2.3 确认方案****3. 施工阶段****3.1 主体拆改****3.2 水电改造****3.3 防水工程****3.…

若依-@Excel新增注解numberFormat

Excel注解中原本的scale会四舍五入小数&#xff0c;导致进度丢失 想要的效果 显示的时候保留两个小数真正的数值是保留之前的数值 还原过程 若以中有一個專門的工具类&#xff0c;用来处理excel的 找到EXCEL导出方法exportExcel()找到writeSheet,写表格的方法找到填充数据的方法…

鸿蒙5.0实战案例:基于自定义注解和代码生成实现路由框架

往期推文全新看点&#xff08;文中附带全新鸿蒙5.0全栈学习笔录&#xff09; ✏️ 鸿蒙&#xff08;HarmonyOS&#xff09;北向开发知识点记录~ ✏️ 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ ✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景&#…

让浏览器AI起来:基于大模型Agent的浏览器自动化工具

最近有个非常火的项目,利用大模型Agent驱动浏览器完成各种操作,如网页搜索、爬虫分析、机票酒店预定、股票监控等,号称全面替代所有在浏览器上的操作,试用方式还是比较简单的,以下将进行简单介绍。 快速开始 通过pip安装: pip install browser-use安装web自动化框架:…

模电知识点总结(6)

1.选取频率高于1000Hz的信号时&#xff0c;可选用高通滤波器&#xff1b;抑制50Hz的交流干扰时&#xff0c;可选用带阻滤波器如果希望抑制500Hz以下的信号&#xff0c;可选用高通滤波器。 2.有用信号频率高于1000Hz&#xff0c;可选用高通滤波器&#xff1b;希望抑制50Hz的交流…

MyBatis:动态SQL高级标签使用方法指南

一、引言 目前互联网大厂在搭建后端Java服务时&#xff0c;常使用Springboot搭配Mybatis/Mybatis-plus的框架。Mybatis/Mybatis-plus之所以能成为当前国内主流的持久层框架&#xff0c;与其本身的优点有关&#xff1a;支持定制动态 SQL、存储过程及高级映射&#xff0c;简化数…

快速入门——Axios网络请求

学习自哔哩哔哩上的“刘老师教编程”&#xff0c;具体学习的网站为&#xff1a;11.Axios网络请求_哔哩哔哩_bilibili&#xff0c;以下是看课后做的笔记&#xff0c;仅供参考。 第一节Axios的使用 第二节与Vue整合 第三节跨域 第一节Axios的使用 在实际项目开发中&#xff0…

Typora的Github主题美化

对Typora的Github主题进行一些自己喜欢的修改&#xff0c;主要包括&#xff1a;字体、代码块、表格样式 美化前&#xff1a; 美化后&#xff1a; 字体更换 之前便看上了「中文网字计划」的「朱雀仿宋」字体&#xff0c;于是一直想更换字体&#xff0c;奈何自己拖延症作祟&#…

DeepSeek 助力 Vue 开发:打造丝滑的 键盘快捷键(Keyboard Shortcuts)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

[深度学习][python]yolov12+bytetrack+pyqt5实现目标追踪

【算法介绍】 实时目标检测因其低延迟特性而持续受到广泛关注&#xff0c;具有重要的实际应用价值[4, 17, 24, 28]。其中&#xff0c;YOLO系列[3, 24, 28, 29, 32, 45-47, 53, 57, 58]通过有效平衡延迟与精度&#xff0c;在该领域占据主导地位。尽管YOLO的改进多集中在损失函数…

Python大数据可视化:基于大数据技术的共享单车数据分析与辅助管理系统_flask+hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 场地信息界面 单车信息界面 归还信息界面 共享单车界面 系…