MyBatis-Plus:让数据库操作飞起来的神器

MyBatis-Plus:让数据库操作飞起来的神器

一、MyBatis-Plus 是什么?

在 Java 开发的世界里,数据库操作是不可或缺的一部分。MyBatis 作为一款优秀的持久层框架,深受开发者喜爱,而 MyBatis-Plus 则是在 MyBatis 基础上诞生的强大增强工具。它就像是给 MyBatis 这位 "武林高手" 配备了一套超级装备,让开发变得更加高效和便捷。

MyBatis-Plus 秉持着 "只做增强不做改变" 的设计理念 ,这意味着当你引入它到项目中时,就像给原有的 MyBatis 系统轻轻地披上一层 "魔法披风",不会对现有工程产生任何侵入性影响。它保留了 MyBatis 的所有优点,比如灵活的 SQL 自定义、强大的映射功能等,同时又添加了许多实用的新特性,让开发人员从繁琐的基础代码编写中解脱出来。

想象一下,在传统的数据库操作中,我们常常需要为每一个基本的增删改查(CRUD)操作编写大量重复的 SQL 语句,这不仅耗时费力,还容易出错。而 MyBatis-Plus 内置了通用的 Mapper 和 Service,通过少量的配置,就能轻松实现单表大部分的 CRUD 操作 。就好比有了一个万能的 "数据库操作助手",只要告诉它你的需求,它就能快速准确地完成任务,大大提高了开发效率。

例如,在查询用户信息时,以往我们可能需要编写复杂的 SQL 语句来构建查询条件,而现在使用 MyBatis-Plus,借助它强大的条件构造器,只需通过简单的代码就能实现各种复杂查询,代码量大幅减少,而且可读性更强。 它还支持 Lambda 形式调用,让我们在编写查询条件时,无需再担心字段写错,就像有了一个智能的代码检查器,时刻为我们的代码质量保驾护航。

二、为什么选择 MyBatis-Plus?

(一)原生 MyBatis 的痛点

在 MyBatis-Plus 出现之前,原生 MyBatis 在开发中虽然有着灵活的 SQL 定制能力,但也暴露出不少让人头疼的问题 。

大量重复代码就是其中之一,在使用原生 MyBatis 时,每一个数据库表都需要开发者手动编写对应的 Mapper 接口和 XML 映射文件,实现增删改查(CRUD)操作。想象一下,一个项目中如果有十几个甚至几十个表,那光是这些基础的 CRUD 代码就得花费大量的时间和精力去编写,而且这些代码结构相似,只是针对不同表的字段和表名有所变化,这无疑是一种重复劳动,不仅降低开发效率,还增加了维护成本。

XML 文件臃肿也是原生 MyBatis 难以避免的问题。即使是非常简单的查询操作,也需要在 XML 文件中编写完整的 SQL 语句。随着项目的不断迭代和功能的增加,XML 文件会越来越大,内容也越来越复杂,这使得代码的可读性和可维护性大幅下降。修改一个小功能,可能就需要在冗长的 XML 文件中艰难地找到对应的 SQL 语句,稍有不慎还可能影响其他功能。

分页功能繁琐同样困扰着开发者,在原生 MyBatis 中实现分页,需要手动编写分页 SQL 语句,不同数据库的分页语法还存在差异,比如 MySQL 使用 LIMIT 关键字实现分页,而 Oracle 则需要使用 ROWNUM 伪列。这就意味着开发者需要针对不同数据库编写不同的分页代码,大大增加了开发难度和工作量。此外,还需要手动处理分页参数,如当前页码、每页显示条数等,容易出现参数传递错误或计算错误的情况。

还有就是条件查询麻烦,当进行动态条件查询时,原生 MyBatis 需要在代码中编写大量的 if - else 判断语句来拼接 SQL 条件。这不仅使得代码变得冗长复杂,可读性差,而且还容易出现 SQL 注入风险,对系统的安全性构成威胁。例如,当根据用户输入的条件进行查询时,如果没有对用户输入进行严格的过滤和转义,恶意用户可能会通过输入特殊字符来篡改 SQL 语句,获取或修改不应该访问的数据。

(二)MyBatis-Plus 优势

MyBatis-Plus 的出现,就像是为这些问题量身定制的解决方案,完美地解决了原生 MyBatis 的诸多痛点 。

它的无侵入性是一大亮点,正如前面所说,它秉持 "只做增强不做改变" 的理念,在引入到项目中时,不会对原有的 MyBatis 代码结构和配置产生任何影响,就像给项目轻轻地披上一层 "隐形披风",既能享受它带来的强大功能,又无需担心兼容性问题 。

强大的 CRUD 操作让开发效率大幅提升。MyBatis-Plus 内置了通用的 Mapper 和 Service,只要让自定义的 Mapper 接口继承 BaseMapper,就可以自动拥有单表的大部分 CRUD 方法,无需再编写大量重复的 SQL 语句和 XML 映射文件。这就好比有了一个 "超级代码生成器",一键就能生成常用的数据库操作代码,让开发者从繁琐的基础代码编写中解放出来,专注于业务逻辑的实现。

在条件查询方面,MyBatis-Plus 提供了强大的条件构造器,如 QueryWrapper 和 LambdaQueryWrapper 。使用它们,开发者可以通过简单的方法调用轻松构建各种复杂的查询条件,而无需编写 SQL 语句。LambdaQueryWrapper 更是支持 Lambda 表达式,通过方法引用获取实体类字段,避免了手动输入字段名可能出现的拼写错误,同时也让代码更加简洁、易读。比如,想要查询年龄大于 18 岁且姓名中包含 "张" 的用户,使用 LambdaQueryWrapper 只需短短几行代码就能实现:

java 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18).like(User::getName, "张");
List<User> userList = userMapper.selectList(wrapper);

MyBatis-Plus 的内置分页插件也十分实用,只需要简单配置,就能实现物理分页功能。它会自动在 SQL 语句中添加分页相关的关键字和参数,适配不同的数据库,开发者无需再手动编写分页 SQL 和处理分页参数,大大简化了分页操作的实现过程 。比如,要实现查询第 2 页,每页显示 10 条数据,代码如下:

java 复制代码
Page<User> page = new Page<>(2, 10);
Page<User> result = userMapper.selectPage(page, null);

此外,MyBatis-Plus 还拥有丰富的插件体系,支持逻辑删除、自动填充、乐观锁、多数据源等功能。逻辑删除可以在不真正删除数据的情况下,将数据标记为已删除状态,避免数据误删,同时也方便数据的回溯和统计;自动填充功能可以在插入或更新数据时,自动填充一些公共字段,如创建时间、更新时间等,减少手动赋值的操作;乐观锁插件则通过版本号机制解决了并发更新时的数据一致性问题,确保在高并发场景下数据的准确性 。

三、快速上手 MyBatis-Plus

(一)环境搭建

以 Spring Boot 项目为例,搭建 MyBatis-Plus 开发环境的步骤并不复杂 。

首先创建一个 Spring Boot 项目,可以使用 IDEA 的 Spring Initializr 快速创建,也可以通过 Maven Archetype 手动创建。在创建过程中,记得勾选 Spring Web 依赖,这将为项目提供 Web 开发支持。

创建好项目后,在pom\.xml文件中添加 MyBatis-Plus 依赖。如果还需要连接数据库,比如 MySQL,还需要添加 MySQL 驱动依赖。如果想简化实体类的编写,使用 Lombok 也是个不错的选择,它能通过注解自动生成 getter、setter、equals、hashCode 和 toString 等方法 。以下是相关依赖配置示例:

xml 复制代码
<dependencies>
    <!-- Spring Boot 核心启动器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- MyBatis-Plus 启动器 -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Lombok 简化实体类编写 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

添加完依赖后,点击pom\.xml文件右上角的 Maven 图标刷新依赖,确保所有依赖都成功下载。

接着配置数据库连接,在src/main/resources目录下找到application\.yml文件(如果没有则新建一个),添加数据库连接相关配置 :

yaml 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/your_database_name?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
    username: your_username
    password: your_password

记得将your\_database\_nameyour\_usernameyour\_password替换为实际的数据库名、用户名和密码 。

最后,为了让 Spring 能够扫描到 MyBatis-Plus 的 Mapper 接口,需要在 Spring Boot 启动类上添加@MapperScan注解,并指定 Mapper 接口所在的包路径 :

java 复制代码
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.example.demo.mapper") // 替换为实际的Mapper接口包路径
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

(二)基本 CRUD 操作

完成环境搭建后,就可以使用 MyBatis-Plus 进行基本的 CRUD 操作了 。

假设我们有一个User实体类,对应数据库中的user表 :

java 复制代码
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

这里使用了 Lombok 的@Data注解简化代码,同时通过@TableName注解指定实体类对应的数据库表名。

接下来创建UserMapper接口,让它继承BaseMapper\&lt;User\&gt;,这样就自动拥有了单表的大部分 CRUD 方法 :

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

有了实体类和 Mapper 接口,就可以进行具体的 CRUD 操作了 。

插入操作 :使用insert方法将一个User对象插入到数据库中 :

java 复制代码
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testInsert() {
        User user = new User();
        user.setName("张三");
        user.setAge(20);
        user.setEmail("zhangsan@example.com");
        int result = userMapper.insert(user);
        System.out.println("插入成功,影响行数:" + result);
        System.out.println("自动生成的ID:" + user.getId());
    }
}

运行测试方法后,会发现一条新的用户记录被插入到数据库中,并且user对象的id属性会自动填充为数据库生成的主键值,这得益于 MyBatis-Plus 默认的雪花算法策略生成 id 。

更新操作 :使用updateById方法根据id更新User对象的信息 :

java 复制代码
@Test
public void testUpdate() {
    User user = new User();
    user.setId(1L); // 假设要更新id为1的用户
    user.setName("李四");
    user.setAge(22);
    int result = userMapper.updateById(user);
    System.out.println("更新成功,影响行数:" + result);
}

这里创建一个User对象,设置需要更新的id以及新的属性值,调用updateById方法即可完成更新操作,返回值为受影响的行数 。

删除操作 :可以使用deleteById方法根据id删除用户记录 :

java 复制代码
@Test
public void testDelete() {
    Long id = 1L; // 假设要删除id为1的用户
    int result = userMapper.deleteById(id);
    System.out.println("删除成功,影响行数:" + result);
}

调用deleteById方法并传入要删除的用户id,即可从数据库中删除对应的记录,返回值同样为受影响的行数 。

查询操作 :使用selectById方法根据id查询用户信息 :

java 复制代码
@Test
public void testSelect() {
    Long id = 1L; // 假设要查询id为1的用户
    User user = userMapper.selectById(id);
    System.out.println("查询结果:" + user);
}

调用selectById方法并传入id,即可从数据库中查询到对应的用户信息,并将结果封装成User对象返回 。

除了以上单个操作外,MyBatis-Plus 还提供了批量操作的方法,如deleteBatchIds用于批量删除,selectBatchIds用于批量查询等 ,使用方式与单个操作类似,只是传入的参数变为id的集合 。通过这些简单的方法调用,就能轻松完成数据库的基本 CRUD 操作,大大提高了开发效率 。

四、MyBatis-Plus 高级特性

(一)条件构造器(Wrapper)

在实际的数据库操作中,简单的 CRUD 往往无法满足复杂的业务需求,我们经常需要构建各种复杂的查询条件。MyBatis-Plus 的条件构造器(Wrapper)就像是一把 "万能钥匙",为我们打开了复杂查询的大门。

其中,QueryWrapperUpdateWrapper是最常用的两个条件构造器 。QueryWrapper主要用于构建查询条件,而UpdateWrapper则用于构建更新条件。它们都支持链式调用,让我们可以像搭积木一样,轻松地组合出各种复杂的查询和更新逻辑 。

比如,我们要查询年龄大于 18 岁且姓名中包含 "张" 的用户,并且按照年龄降序排列 。使用QueryWrapper可以这样实现:

java 复制代码
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18)
       .like("name", "张")
       .orderByDesc("age");
List<User> userList = userMapper.selectList(wrapper);

在这段代码中,gt方法表示 "大于",like方法表示 "模糊查询",orderByDesc方法表示 "降序排列" 。通过链式调用,我们可以清晰地表达出复杂的查询条件,而且代码简洁易读 。

再比如,我们要将年龄大于 30 岁的用户的邮箱统一更新为 "new\_email@example.com" 。使用UpdateWrapper可以这样实现:

java 复制代码
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("email", "new_email@example.com")
              .gt("age", 30);
int result = userMapper.update(null, updateWrapper);

这里的set方法用于设置更新的字段和值,gt方法用于指定更新的条件 。通过UpdateWrapper,我们可以在不创建实体对象的情况下,直接设置更新字段和条件,实现高效的更新操作 。

除了QueryWrapperUpdateWrapper,MyBatis-Plus 还提供了基于 Lambda 表达式的LambdaQueryWrapperLambdaUpdateWrapper 。它们通过 Lambda 表达式引用实体类的属性,避免了硬编码字段名,进一步提高了代码的可读性和可维护性 。例如,上述查询年龄大于 18 岁且姓名中包含 "张" 的用户的代码,使用LambdaQueryWrapper可以写成:

java 复制代码
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
       .like(User::getName, "张")
       .orderByDesc(User::getAge);
List<User> userList = userMapper.selectList(wrapper);

这样,即使实体类的字段名发生变化,只要属性名不变,代码就无需修改,大大降低了维护成本 。

(二)分页查询

在处理大量数据时,分页查询是必不可少的功能 。MyBatis-Plus 提供了便捷的分页插件,让分页查询变得轻而易举 。

要使用分页功能,首先需要配置分页插件 。在 Spring Boot 项目中,可以通过创建一个配置类来实现:

java 复制代码
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件,这里以MySQL数据库为例
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

在这个配置类中,我们创建了一个MybatisPlusInterceptor拦截器,并向其中添加了PaginationInnerInterceptor分页插件 。这样,MyBatis-Plus 就会自动在 SQL 语句中添加分页相关的逻辑 。

配置好分页插件后,就可以在代码中使用分页查询了 。例如,要查询第 2 页,每页显示 10 条用户数据 :

java 复制代码
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserMapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testPage() {
        // 构建分页对象,参数分别为当前页码和每页显示条数
        Page<User> page = new Page<>(2, 10);
        IPage<User> userPage = userMapper.selectPage(page, null);
        // 获取当前页的数据列表
        userPage.getRecords().forEach(System.out::println);
        // 获取总记录数
        System.out.println("总记录数:" + userPage.getTotal());
        // 获取总页数
        System.out.println("总页数:" + userPage.getPages());
    }
}

在这段代码中,我们创建了一个Page对象,传入当前页码和每页显示条数 。然后调用userMapper\.selectPage方法,传入Page对象和查询条件(这里为null,表示查询所有数据) 。selectPage方法会返回一个IPage对象,通过它我们可以获取当前页的数据列表、总记录数和总页数等信息 。

通过 MyBatis-Plus 的分页插件,我们无需手动编写分页 SQL 语句,也无需关心不同数据库的分页语法差异,就能轻松实现高效的分页查询功能 。

(三)逻辑删除

在实际应用中,有时我们并不希望真正删除数据库中的数据,而是将其标记为已删除状态,以便在需要时进行数据回溯和统计分析 。这就是逻辑删除的作用 。

逻辑删除是指在数据库表中添加一个逻辑删除字段(通常为一个布尔类型、整数类型或枚举类型的字段),用来表示数据是否被删除 。当执行删除操作时,实际上是将该字段的值更新为表示已删除的状态,而不是真正从数据库中删除数据 。

在 MyBatis-Plus 中配置和使用逻辑删除非常简单 。首先,在数据库表中添加逻辑删除字段,例如deleted字段,类型为int,默认值为0,表示未删除;值为1表示已删除 。

然后,在实体类中添加对应的逻辑删除属性,并使用@TableLogic注解标识 :

java 复制代码
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 逻辑删除字段
    @TableLogic
    private Integer deleted;
}

最后,在配置文件中进行全局逻辑删除配置(也可以在@TableLogic注解中单独配置) :

yaml 复制代码
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值
      logic-not-delete-value: 0 # 逻辑未删除值

完成上述配置后,MyBatis-Plus 会自动在查询和删除操作中处理逻辑删除 。例如,当执行userMapper\.deleteById\(id\)方法时,实际上执行的是更新操作,将deleted字段的值更新为1,而不是真正删除数据 。在查询时,MyBatis-Plus 会自动在 SQL 语句中添加deleted = 0的条件,只查询未被逻辑删除的数据 。

通过逻辑删除,我们可以有效地避免数据误删,同时满足业务对数据历史记录的需求 。

(四)自动填充

在数据库表设计中,通常会有一些公共字段,如创建时间、更新时间、创建人、更新人等 。每次插入或更新数据时,都需要手动为这些字段赋值,这不仅繁琐,还容易出错 。MyBatis-Plus 的自动填充功能可以很好地解决这个问题 。

自动填充功能允许我们在插入或更新数据时,自动为某些字段填充指定的值 。要使用自动填充功能,首先需要在实体类的字段上添加@TableField注解,并设置fill属性来指定填充时机 。例如,对于创建时间字段createTime,在插入数据时自动填充当前时间;对于更新时间字段updateTime,在更新数据时自动填充当前时间 :

java 复制代码
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 创建时间,插入时自动填充
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    // 更新时间,更新时自动填充
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
}

然后,创建一个实现MetaObjectHandler接口的类,来实现自动填充的逻辑 :

java 复制代码
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

在这个类中,insertFill方法用于在插入数据时填充字段,updateFill方法用于在更新数据时填充字段 。strictInsertFillstrictUpdateFill方法是 MyBatis-Plus 提供的严格填充方法,确保填充字段在实体类中存在,若不存在则抛出异常 。

通过自动填充功能,我们可以大大简化代码,提高开发效率,同时保证数据的一致性和准确性 。

(五)乐观锁

在高并发环境下,当多个线程同时对数据库中的数据进行更新操作时,可能会出现数据更新冲突的问题 。例如,线程 A 和线程 B 同时读取了数据库中的一条数据,然后线程 A 对数据进行了更新并提交,接着线程 B 也对数据进行了更新并提交,由于线程 B 在更新时并不知道线程 A 已经对数据进行了修改,所以线程 B 的更新操作会覆盖线程 A 的更新结果,导致数据不一致 。

乐观锁是一种解决并发更新问题的机制,它假设在大多数情况下,并发操作不会发生冲突,只有在提交更新时才检查数据是否被其他事务修改过 。如果数据没有被修改过,则更新操作成功;如果数据已经被修改过,则更新操作失败,需要重新读取数据并进行更新 。

在 MyBatis-Plus 中配置和使用乐观锁也很简单 。首先,在实体类中添加一个版本号字段,例如version字段,并使用@Version注解标识 :

java 复制代码
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    // 乐观锁版本号字段
    @Version
    private Integer version;
}

然后,在配置类中注册乐观锁插件 :

java 复制代码
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

完成上述配置后,MyBatis-Plus 会在更新数据时自动检查版本号 。例如,当执行userMapper\.updateById\(user\)方法时,MyBatis-Plus 会生成类似于以下的 SQL 语句:

sql 复制代码
UPDATE user SET name = '新名字', age = 25, version = version + 1 WHERE id = 1 AND version = 1;

在这个 SQL 语句中,version = 1用于确保数据在更新时未被其他事务修改 。如果当前数据的version值不是1,则更新操作不会成功,返回值为0

通过乐观锁机制,我们可以有效地避免高并发环境下的数据更新冲突问题,确保数据的一致性和完整性 。

五、实战案例:用户管理系统

(一)需求分析

为了更直观地展示 MyBatis-Plus 在实际项目中的应用,我们以一个简单的用户管理系统为例进行实战。这个用户管理系统需要具备以下基本功能:

  • 用户注册:用户可以在系统中填写用户名、密码、邮箱等信息进行注册,系统会对用户输入的信息进行校验,确保信息的合法性和完整性,同时要检查用户名是否已被注册 。

  • 用户登录:用户输入已注册的用户名和密码进行登录验证,系统会在数据库中查询对应的用户信息,验证密码是否正确,如果验证通过则允许用户登录 。

  • 用户查询:支持根据用户 ID 查询单个用户的详细信息,也可以根据用户名进行模糊查询,获取符合条件的用户列表 。此外,还需要支持分页查询,以便在处理大量用户数据时,能够高效地展示数据 。

  • 用户修改:已登录的用户可以修改自己的个人信息,如密码、邮箱等。管理员用户则可以修改任意用户的信息,包括用户名、角色等 。在修改时,要确保数据的一致性和安全性,例如修改密码时需要进行密码强度校验 。

  • 用户删除:管理员用户有权限删除指定的用户。为了避免数据的误删,采用逻辑删除的方式,即只是将用户数据在数据库中标记为已删除,而不是真正从数据库中删除 。

(二)数据库设计

根据上述需求,我们设计一个名为user的用户表,其结构如下:

sql 复制代码
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除:0-未删除,1-已删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
  • id:用户的唯一标识,使用自增长的bigint类型,确保每个用户都有一个唯一的 ID 。

  • username:用户名,varchar类型,长度为 50,不能为空,并且设置了唯一索引,保证用户名的唯一性 。

  • password:用户密码,varchar类型,长度为 100,用于存储加密后的用户密码,保障用户信息安全 。

  • email:用户邮箱,varchar类型,长度为 100,可为空,用于找回密码、接收系统通知等 。

  • phone:用户手机号,varchar类型,长度为 20,可为空,用于短信验证、账号绑定等 。

  • status:用户状态,tinyint类型,0 表示禁用,1 表示正常,用于控制用户是否能够登录系统 。

  • create\_time:用户创建时间,datetime类型,默认值为当前时间,记录用户注册的时间 。

  • update\_time:用户信息更新时间,datetime类型,默认值为当前时间,并且在数据更新时自动更新为当前时间,用于记录用户信息的最后修改时间 。

  • deleted:逻辑删除字段,tinyint类型,0 表示未删除,1 表示已删除,通过这个字段实现逻辑删除功能 。

(三)代码实现

接下来,我们结合 MyBatis-Plus 的功能,逐步实现用户管理系统的各个功能模块 。

1. 实体类 :首先创建User实体类,用于映射数据库中的user表 。使用 Lombok 的@Data注解简化代码,通过@TableName注解指定实体类对应的数据库表名,使用@TableId注解标识主键字段,并指定主键生成策略为自增 。同时,添加@TableField注解实现自动填充功能,使用@TableLogic注解实现逻辑删除功能 。

java 复制代码
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String email;
    private String phone;
    private Integer status;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @TableLogic
    private Integer deleted;
}

2. Mapper 接口 :创建UserMapper接口,继承BaseMapper\&lt;User\&gt;,这样就自动拥有了单表的大部分 CRUD 方法 。

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

3. Service 层 :创建UserService接口及其实现类UserServiceImpl 。在UserService接口中定义用户管理的业务方法,如注册、登录、查询、修改、删除等 。在UserServiceImpl中实现这些方法,通过调用UserMapper来操作数据库 。

UserService接口:

java 复制代码
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.User;

public interface UserService extends IService<User> {
    // 用户注册
    boolean register(User user);
    // 用户登录
    User login(String username, String password);
    // 根据ID查询用户
    User getUserById(Long id);
    // 根据用户名模糊查询用户列表
    List<User> listUsersByUsername(String username);
    // 更新用户信息
    boolean updateUser(User user);
    // 删除用户(逻辑删除)
    boolean deleteUser(Long id);
}

UserServiceImpl实现类:

java 复制代码
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.List;

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

    @Override
    public boolean register(User user) {
        // 检查用户名是否已存在
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, user.getUsername());
        if (count(wrapper) > 0) {
            return false;
        }
        // 对密码进行加密
        String encryptedPassword = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
        user.setPassword(encryptedPassword);
        // 插入用户数据
        return save(user);
    }

    @Override
    public User login(String username, String password) {
        // 对密码进行加密
        String encryptedPassword = DigestUtils.md5DigestAsHex(password.getBytes());
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username)
               .eq(User::getPassword, encryptedPassword)
               .eq(User::getDeleted, 0);
        return getOne(wrapper);
    }

    @Override
    public User getUserById(Long id) {
        return getById(id);
    }

    @Override
    public List<User> listUsersByUsername(String username) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(User::getUsername, username)
               .eq(User::getDeleted, 0);
        return list(wrapper);
    }

    @Override
    public boolean updateUser(User user) {
        return updateById(user);
    }

    @Override
    public boolean deleteUser(Long id) {
        return removeById(id);
    }
}

4. Controller 层 :创建UserController,用于处理 HTTP 请求,调用UserService的方法来实现用户管理的功能 。通过@RestController注解将该类标记为 RESTful 风格的控制器,使用@RequestMapping注解映射请求路径 。

java 复制代码
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    // 用户注册
    @PostMapping("/register")
    public boolean register(@RequestBody User user) {
        return userService.register(user);
    }

    // 用户登录
    @PostMapping("/login")
    public User login(@RequestParam String username, @RequestParam String password) {
        return userService.login(username, password);
    }

    // 根据ID查询用户
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    // 根据用户名模糊查询用户列表
    @GetMapping("/list")
    public List<User> listUsersByUsername(@RequestParam String username) {
        return userService.listUsersByUsername(username);
    }

    // 更新用户信息
    @PutMapping
    public boolean updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }

    // 删除用户(逻辑删除)
    @DeleteMapping("/{id}")
    public boolean deleteUser(@PathVariable Long id) {
        return userService.deleteUser(id);
    }

    // 分页查询用户列表
    @GetMapping("/page")
    public Page<User> pageUsers(@RequestParam(defaultValue = "1") Integer current,
                                @RequestParam(defaultValue = "10") Integer size) {
        Page<User> page = new Page<>(current, size);
        return userService.page(page);
    }
}

通过以上代码,我们利用 MyBatis-Plus 的强大功能,实现了一个简单但功能完整的用户管理系统 。这个系统涵盖了用户注册、登录、查询、修改和删除等核心功能,并且充分展示了 MyBatis-Plus 在简化数据库操作方面的优势 。无论是基础的 CRUD 操作,还是复杂的条件查询和分页功能,MyBatis-Plus 都能轻松应对,让开发变得更加高效和便捷 。

相关推荐
2301_811274312 小时前
基于SpringBoot的智能家居管理系统
spring boot·后端·智能家居
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第15题:JDK1.7中HashMap扩容为什么会发生死循环?如何解决
java·开发语言·数据结构·后端·面试·哈希算法
舒一笑2 小时前
我把设备指纹生成逻辑拆开了:它到底凭什么区分不同设备?
后端·程序员·掘金技术征文
Nicander3 小时前
多数据源下@transcation事务踩坑
java·后端
郑州光合科技余经理3 小时前
同城O2O海外版二次开发实战:从支付网关到配送算法
开发语言·前端·后端·算法·架构·uni-app·php
sjsjsbbsbsn4 小时前
大模型核心知识总结
java·人工智能·后端
Moment4 小时前
2026 年,AI 全栈时代到了,前端简历别再只写前端技术了 🫠🫠🫠
前端·后端·面试
白晨并不是很能熬夜5 小时前
【PRC】第 2 篇:Netty 通信层 — NIO 模型 + 自定义协议 + 心跳
java·开发语言·后端·面试·rpc·php·nio
zshs0005 小时前
#从偶发无字幕到补偿探测链路:一次 B 站字幕导入问题的完整收敛过程
java·后端·重构