# MyBatisPlus从入门到精通

# 入门案例

# 创建数据库及表

# 创建表

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 
use `mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名', 
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱', 
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 写入数据

INSERT INTO user (id, name, age, email) VALUES 
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'), 
(3, 'Tom', 28, 'test3@baomidou.com'), 
(4, 'Sandy', 21, 'test4@baomidou.com'), 
(5, 'Billie', 24, 'test5@baomidou.com');

# 创建SpringBoot工程

官网的https://start.spring.io/ 最低支持JDK17

中文社区搭建https://start.springboot.io,最低支持JDK17

阿里云代理搭建http://start.aliyun.com,支持JDK8创建

# 引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.22</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.12</version>
    <scope>runtime</scope>
</dependency>

并在IDEA中安装lombok插件

# 编写代码

# 配置application.yml文件

spring:
  # 配置数据源信息
  datasource:
    # 配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    # 配置连接数据库的信息
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false&serverTimeZone=GMT%2B8
    username: root
    password: 123456

连接地址:

MySQL5.0

jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8$useSSL=false

MySQL8.0,存在时区问题

jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8$useSSL=false&serverTimeZone=GMT%2B8

# 启动类

添加注解

@SpringBootApplication
// 用于扫描指定包下的mapper接口
@MapperScan("com.rong.hui.mybatisplus.mapper")
public class MybatisPlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }

}

# 添加实体

// lombok注解可以生成无参构造、getter、setter、hashCode、toString、canEqual
@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

# 添加mapper

BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型

public interface UserMapper extends BaseMapper<User> {
}

# 测试

@SpringBootTest
public class MyBatisPlusTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList(){
        // 通过查询构造器查询一个list集合,若没有条件,则可以设置为null
        List<User> list = userMapper.selectList(null);
        list.forEach(System.out::println);
    }
}

# 添加日志

在application.yml中添加

# 添加日志打印
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 基本CRUD

# 添加

@Test
public void testInsert(){
    // 新增用户信息
    User user = new User();
    user.setName("张三");
    user.setAge(23);
    user.setEmail("zhangsan@163.com");
    int result = userMapper.insert(user);
    System.out.println(result);
    System.out.println(user.getId());
}

# 修改

@Test
public void testUpdate(){
    // 修改用户信息,UPDATE user SET name=?, age=?, email=? WHERE id=?
    User user = new User();
    user.setId(4L);
    user.setName("李四");
    user.setAge(23);
    user.setEmail("test@163.com");
    int i = userMapper.updateById(user);
    System.out.println(i);
}

# 删除

@Test
public void testDelete(){
    // 通过ID删除用户信息
    int result = userMapper.deleteById(1808416315389857794L);
    System.out.println(result);

    // 根据map定义的sql条件进行删除, 匹配map的属性值,DELETE FROM user WHERE name = ? AND age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("name","张三");
    map.put("age",23);
    int i = userMapper.deleteByMap(map);
    System.out.println(i);

    // 批量删除,DELETE FROM user WHERE id IN ( ? , ? )
    //List<Long> list = Arrays.asList(1L, 2L);
    //int result1 = userMapper.deleteBatchIds(list);
    //System.out.println(result1);
}

# 查询

@Test
public void testSelect(){
    // 根据ID查询
    //User user = userMapper.selectById(3L);
    //System.out.println(user);

    // 根据多个ID查询
    //List<Long> list = Arrays.asList(3L, 4L, 5L);
    //List<User> users = userMapper.selectBatchIds(list);
    //users.forEach(System.out::println);

    // 根据Map中设置的条件查询
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","Tom");
    map.put("age",28);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

# 自定义

# 添加mapper映射文件路径

# 添加自定义方法

Map<String,Object> selectMapById(Long id);

# 创建mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.rong.hui.mybatisplus.mapper.UserMapper">

    <select id="selectMapById" resultType="map">
        select id,name,age,email from user where id = #{id}
    </select>
</mapper>

# 测试

@Test
public void testCustomSelect(){
    Map<String, Object> map = userMapper.selectMapById(3L);
    System.out.println(map);
}

# 通用Service

# 创建Service接口和实现类

MyBatis提供了默认的IService接口和ServiceImpl的实现类,使用默认方法需要实现接口并继承默认实现类

创建service接口

public interface UserService extends IService<User> {
}

创建实现类

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

# 测试查询记录数

@Test
public void testCount(){
    // 查询总记录数
    long count = userService.count();
    System.out.println(count);
}

# 测试批量插入

@Test
public void testBatchSave(){
    List<User> list = new ArrayList<>();
    for(int i=1;i<=10;i++){
        User user = new User();
        user.setName("huyadish"+i);
        user.setAge(20+i);
        user.setEmail("123456@qq.com");
        list.add(user);
    }
    boolean b = userService.saveBatch(list);
    System.out.println(b);
}

# 常用注解

# @TableName

用于设置实体类对应的表名称,可以解决实体类名和数据库表名称不一致的情况

// 设置实体类对应表名
@TableName("t_user")
public class User {
    ...
}

当数据库表存在统一前缀的时候,也可以在yml配置文件中进行统一配置

mybatis-plus:
  # mybatis-plus全局配置
  global-config:
    db-config:
   	  # 设置实体类对应表的统一前缀
      table-prefix: "t_"

# @TableID

Myabtis plus默认支持以id作为主键,当数据表使用其他字段如uid作为主键的时候,就需要添加@TableID注解进行标识

@TableId
private Long uid;

Mybatis plus默认以雪花算法生成ID,如果想使用数据库自增ID,首先需要在数据库表中设置ID自增,然后需要指定注解值的类型,默认是ASSIGN_ID雪花算法

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

也可在yml文件中进行统一设置

mybatis-plus:
  # mybatis-plus全局配置
  global-config:
    db-config:
      # 设置统一的主键生成策略
      id-type: auto

雪花算法

由Twitter公布的分布式主键生成算法,能够保证不同表的主键不重复,并且相同表的主键有序,适合分布式数据库系统

核心思想:

  1. 长度共64bit(long型)
  2. 首先是1bit的符号位
  3. 41bit时间戳,存储时间戳的差值
  4. 10bit是机器的ID
  5. 12bit作为毫秒内的流水号(每个毫秒产生4096个ID)

# @TableField

实体类属性名和字段名不一致的情况

MyBatis-Plus会自动将下划线转化为小驼峰,例如表字段user_name,实体类属性userName

但是当实体类属性为name,数据表属性为user_name时,就会出错了

@TableField(value = "user_name")
private String name;

可以使用@TableField注解处理这种特殊情况

# @TableLogic

# 逻辑删除

物理删除:真实删除,将对应数据从数据库删除,之后查询不到这条数据

逻辑删除:假删除,将对应数据的状态修改为“被删除”,之后在数据库中仍能看到这条数据

使用场景:数据恢复

# 实现逻辑删除

首先在数据表添加int类型删除标识字段is_delete,默认为0,随后在实体类添加注解

@TableLogic
private Integer isDelete;

执行删除操作实际执行的语句

UPDATE t_user SET is_delete=1 WHERE user_name = ? AND age = ? AND is_delete=0

执行查询也不会查到逻辑删除的数据

 SELECT uid AS id,user_name AS name,age,email,is_delete FROM t_user WHERE is_delete=0

# 条件构造器和常用接口

# wapper介绍

20240710103247

# QueryWapper

  • 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name","张三")
        .between("age",22,25)
        .isNotNull("email");
  • 组装排序条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
	.orderByAsc("uid");
  • 组装删除条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int i = userMapper.delete(queryWrapper);
  • 条件的优先级
//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
    .like("user_name","o")
    .or()
    .isNull("email");
User user = new User();
user.setEmail("huyadish@qq.com");
user.setName("rong");
int i = userMapper.update(user, queryWrapper);

and和or方法支持传入lambda表达式,用于控制条件优先级

//将年龄大于20并且(用户名中包含有a或邮箱为null)的用户信息修改
// lambda中的条件优先执行
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
    .and(i->i.like("user_name","o").or().isNull("email"));
  • 查询部分字段
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("user_name","age","email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
  • 实现子查询
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select uid from t_user where uid <= 6");
List<User> list = userMapper.selectList(queryWrapper);

# UpdateWrapper

// UPDATE t_user SET user_name=?,email=? WHERE is_delete=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("user_name","a")
        .and(i->i.gt("age",20).or().isNull("email"));
updateWrapper.set("user_name","小黑").set("email","dish@4396.com");
int i = userMapper.update(null, updateWrapper);

注意:updateWrapper和queryWrapper区别是,updateWrapper除了可以设置查询条件,还可以设置修改的属性

# condition条件

在特定条件下,需要先对用户提供的参数进行判断,再选择性组装查询条件。

@Test
public void testCondition(){
    String username = "a";
    Integer beginAge = 20;
    Integer endAge = 30;
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(username),"user_name",username)
        .ge(beginAge != null,"age",beginAge)
        .le(endAge != null,"age",endAge);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

# LambdaQueryWrapper

在构造查询条件时,需要写数据表字段名,为防止写错字段名,可使用方法引用,如User::getName来获取字段名

LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
    .ge(beginAge != null, User::getAge, beginAge)
    .le(endAge!=null,User::getAge,endAge);

# LambdaUpdateWrapper

LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
    .ge(beginAge != null, User::getAge, beginAge)
    .le(endAge!=null,User::getAge,endAge);

# 小结

条件构造器就是通过函数式API构造条件,同时具备以下功能:

  • queryWrapper可实现查询、修改和删除
  • 为防止字段名写错,可使用方法引用如User::getName,替代直接写user_name
  • 通过condition判断是否添加条件,queryWrapper.like(condition,column,value)
  • updateWrapper相比queryWrapper,多了set方法,可设置更新后的值

# 插件

# 分页插件

# 添加配置类

@Configuration
// 可以将主类的注解移到此处
@MapperScan("com.rong.hui.mybatisplus.mapper")
public class MybatisplusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 这里可以指定不同的数据源
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

# 使用

@Test
public void testPlugins(){
    // 设置分页参数
    Page<User> page = new Page<>(2,3);
    userMapper.selectPage(page,null);
    // 获取分页数据
    System.out.println(page.getRecords());
    System.out.println(page.getCurrent());
    System.out.println(page.getPages());
    System.out.println(page.getTotal());
    System.out.println(page.hasPrevious());
    System.out.println(page.hasNext());
}

# 自定义分页功能

创建接口方法

/**
* 通过年龄查询用户信息并分页
* @param page MP提供的分页对象,xml中可从中取值,传递参数必须为第一位
* @param age 年龄
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);

创建映射文件查询语句

<select id="selectPageVo" resultType="User">
    select * from t_user where age > #{age}
</select>

测试

@Test
public void testPageVo(){
    Page<User> page = new Page<>(1,3);
    userMapper.selectPageVo(page,20);
}

# 乐观锁

乐观锁原理:使用版本号机制,每条数据都有一个版本号。读取数据时,会一并读取该记录的版本号。更新数据时,检查当前版本号与读取时的版本号是否一致。如果一致,说明这段时间内,没有其他事务修改过记录,可以安全更新,并将版本号+1。如果不一致,说明有其他事务修改过改记录,可以重试或回滚。

作用:

  • 提高并发性能:允许多个事务并发操作数据,不需要在一开始就进行锁定,减少等待时间
  • 避免死锁:不在一开始就锁定数据,减少死锁发生的机会
  • 减少锁的开销:不需要进行锁定和解锁的操作

悲观锁:在操作数据之前先对数据进行绑定,防止其他事务对数据进行修改

乐观锁插件的原理:每次更新时,先判断version是否与查询的时候一致;每次更新后,为指定@Version字段+1

代码实现:

实体类添加注解

@Data
public class Product {
    ...
    @Version
    private Integer version;
}

配置类添加插件

@Configuration
// 可以将主类的注解移到此处
@MapperScan("com.rong.hui.mybatisplus.mapper")
public class MybatisplusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

测试:

@Test
public void testProduct01(){
    // 小李查询商品价格
    Product productLi = productMapper.selectById(1L);
    System.out.println("小李查询的商品价格"+productLi.getPrice());

    // 小王查询商品价格
    Product productWang = productMapper.selectById(1L);
    System.out.println("小王查询的商品价格"+productWang.getPrice());

    // 小李将商品价格+50
    productLi.setPrice(productLi.getPrice()+50);
    productMapper.updateById(productLi);

    // 小王将商品价格-30
    productWang.setPrice(productWang.getPrice()-30);
    int result = productMapper.updateById(productWang);
    // 如果版本号不一致,受影响的行数为0,重新查询数据
    if(result == 0){
        Product product1 = productMapper.selectById(1L);
        product1.setPrice(product1.getPrice()-30);
        productMapper.updateById(product1);
    }

    // 老板查询商品价格
    Product productBoss = productMapper.selectById(1L);
    System.out.println("老板查询的商品价格"+productBoss.getPrice());
}

此时原始数据为100,小王+50,小李-30后,查询的结果为120,

# 通用枚举

定义枚举

@Getter
public enum SexEnum {
    // 枚举成员之间逗号分隔
    MALE(1,"男"),
    FEMALE(2,"女");

    @EnumValue // 使用 @EnumValue 注解标识枚举的值
    private Integer sex;
    private String sexName;

    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}

添加配置文件

mybatis-plus:
  # 扫描通用枚举的包
  type-enums-package: com.rong.hui.mybatisplus.enums

测试

@Test
public void test(){
    User user = new User();
    user.setName("admin");
    user.setAge(33);
    user.setSex(SexEnum.FEMALE);
    userMapper.insert(user);
}

# 代码生成器

可以通过代码生成器,使用函数API方式配置生成信息,可以生成实体类、Mapper接口、Mapper.xml、Service接口和实现类

# 添加依赖

<!--MP框架提供的代码生成器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>
<!--文本输出模板引擎-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

# 代码生成

public class FastAutoGeneratorTest {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false","root","123456")
                .globalConfig(builder -> {
                    builder.author("oldstag") // 设置作者
                            //.enableSwagger() // 开启swagger模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://mybatis_plus"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.rong.hui") // 设置父包名
                            .moduleName("mybatisPlus") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml,"D://mybatis_plus")); // 设置mapper.xml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_user") // 需要生成的表名
                            .addTablePrefix("t_","c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用freemaker作为模板引擎,默认是Velocity模板引擎
                .execute();
    }
}

# 多数据源

# 创建数据库及表

在mybatis_plus_1数据库中创建

DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称',
  `price` int NULL DEFAULT NULL COMMENT '价格',
  `version` int UNSIGNED NULL DEFAULT 0 COMMENT '版本号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

在mybatis_plus数据库中创建

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `uid` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `age` int NULL DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `is_delete` int NULL DEFAULT 0,
  `is` int NULL DEFAULT 0,
  `sex` int NULL DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

# 引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

# 配置多数据源

配置数据源信息及默认数据源

spring:
  # 配置数据源信息
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 123456

# 创建service

Product 接口和实现类 在实现类上使用@DS注解标识数据源

public interface ProductService extends IService<Product> {
}

@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}

User 接口和实现类 在实现类上使用@DS注解标识数据源

public interface UserService extends IService<User> {
}

@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

# 测试

@Test
public void test(){
    System.out.println(userService.getById(3));
    System.out.println(productService.getById(1));
}

# MyBatisX插件

Last Updated: 11/18/2024, 4:01:47 PM