MyBatis-Puls一篇就够

文章目录

    • `MyBatisPlus`快速入门
        • 1.简介
        • 2.特性
        • 3.支持数据库
        • 4.框架结构
    • 快速入门
        • 1.创建数据库及表
        • 2.创建一个`SpringBoot`工程
        • 3.配置`application.yml`信息
        • 4.创建`POJO`实体类
        • 5.创建`Mapper`接口并继承`BaseMapper`接口
        • 6.编写测试方法
        • 7.添加日志功能
    • `BseaMapper`源码分析
        • 1.根据条件查询返回`List`集合
        • 2.插入功能
        • 3.删除功能
        • 4.修改功能
        • 5.查询功能
        • 6.自定义功能
    • `BaseMapper`源码
    • 通用`Service`
        • 1.`Service`接口`CRUD`
        • 第一步:定义`UserSerivce`继承`Iservice`接口
        • 第二步:定义`UserServiceImpl`实现类
        • 第三步:编写测试类
    • `ServiceImpl`源码
    • `IService`源码
    • 常用注解
      • 2.`@TableId`
          • `@TableId`的`value`属性
          • `@TableId`的`type`属性
          • 配置全局主键策略
      • 3.`@FableField`
      • 4.逻辑删除
        • 实现逻辑删除步骤
          • 第一步:在数据库表中创建逻辑删除状态列,并设置默认值为`0`
          • 第二步:在实体类添加表示逻辑删除状态属性
    • 条件构造器和常用接口
      • 1.`wrapper`介绍【记住下图】
      • 2.`QueryWrapper`
        • A.组装查询条件
        • B.组装排序条件
        • C.组装删除条件
        • D.条件的优先级【必须理解重点】
        • E.组装`SELECT`子句
        • F.子查询
      • 3.`@UpdateWrapper`
          • 模拟开发中组装条件的情况
          • `condition`
      • 4.`LambdaQueryWrapper`
      • 5.`LambdaUpdateWrapper`
    • 插件
      • 1.分页插件
          • 第一步:添加配置类
          • 第二步:测试
      • 2.自定义分页
          • 第一步:在自定义`UserMapper`接口中定义方法
          • 第三步:定义`UserMapper.xml`文件
          • 第四步:测试
    • `IPage`源码
    • `Page`源码
    • 乐观锁和悲观锁
      • 1.简介
      • 2.模拟修改冲突
    • 3.乐观锁实现流程【重点】
          • 第一步:在数据库中添加`version`字段
          • 第二步:在实现类中的`version`字段上添加`@Version`注解
          • 第三步:创建一个配置类,添加乐观锁插件
          • 第四步:测试
    • 通用枚举
        • 第一步:在数据库中添加字段`sex`
        • 第二步:创建枚举类,并且使用`@EnumValue`注解,那个字段添加到数据库中
        • 第三步:在实体类中添加`枚举`字段
        • 第四步:配置扫描通用枚举
        • 第五步:测试
    • 代码生成器
        • 第一步:添加依赖
        • 第二步:创建类
    • 多数据源
      • 1.创建数据库及
      • 2.引入依赖
      • 3.配置多数据源
      • 4.创建对应的`Mapper`接口和`xml`文件
      • 5.创建对应的`Service`接口和实现类
      • 6.测试
    • `MyBatisX`快速开发插件
      • 第一步:创建`SpringBoot`工程
      • 第二步:配置相关的数据源配置
      • 第三步:下载`MybatisX`
      • 第四步:在`IDEA`连接数据库生成对应文件
      • 第五步:编写方法进行测试
          • A.添加方法测试
          • B.修改用户信息
          • C.查询用户信息
          • D.删除用户信息

MyBatisPlus快速入门

1.简介

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis基础上只做增强不做改变,为简化开发,提高效率而生

2.特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

3.支持数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。

  • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

4.框架结构

MyBatis-Puls一篇就够_第1张图片


执行的流程是:通过BaseMapper扫描POJO实体类,通过反射分析表的字段,再分析调用的方法是增删还是改查,最终通过反射Mapper将生成实现类放到MyBatis容器中



快速入门

1.创建数据库及表

创建表:

#mybatis_plus
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;

注意是:由于mybatis_plus使用雪花算法,在主键自动生成时会很长,所以需要使用BIGINTG数据类型

插入数据:

INSERT INTO USER (id, NAME, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');


2.创建一个SpringBoot工程

MyBatis-Puls一篇就够_第2张图片

注意是:由于在创建工程时,我们还没有添加mybatis_plus的场景启动器,所以下面需要我们手动添加

<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>3.5.1version>
dependency>

3.配置application.yml信息

建议直接复制修改,防止写错

#修改端口号
server:
  port: 80

spring:
  #配置数据源信息
  datasource:
    #配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    username: root
    password: root

注意

1、驱动类driver-class-name
spring boot 2.0(内置jdbc5驱动),驱动类使用:
driver-class-name: com.mysql.jdbc.Driver
spring boot 2.1及以上(内置jdbc8驱动),驱动类使用:
driver-class-name: com.mysql.cj.jdbc.Driver
否则运行测试用例的时候会有 WARN 信息
2、连接地址url
MySQL5.7版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
MySQL8.0版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?
serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
否则运行测试用例报告如下错误:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or
represents more

4.创建POJO实体类

由于mybatisORM框架,表示表和对象的映射关系

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

5.创建Mapper接口并继承BaseMapper接口

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

注意是:也可以在springboot启动类中,添加@MapperSacn扫描包的方式,但是不建议

@SpringBootApplication
// 扫描mapper接口所在的包
@MapperScan("com.haikang.plus.mapper")
public class MybatisPlusApplication {

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

}

6.编写测试方法

@SpringBootTest
public class MyBatisPlusTest {

    // 注入UserMapper对象
    @Autowired
    public UserMapper userMapper;

    @Test
    void selectAllUser(){
        // selectList需要传入条件,传入null表示查询全部
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> System.out.println(user));
    }
}

7.添加日志功能

mybatis-plus:
  configuration:
    #mybatisplus日志配置
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

BseaMapper源码分析

1.根据条件查询返回List集合

源码:

selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper)方法

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

测试

    @Test
    void selectAllUser(){
        // selectList需要传入条件,传入null表示查询全部
        List<User> users = userMapper.selectList(null);
        users.forEach(user -> System.out.println(user));
    }

2.插入功能

源码:

insert方法

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

测试

    // 插入数据
    @Test
    void insert(){
        User user = new User(null,"明天",21,"[email protected]");
        int insert = userMapper.insert(user);
        System.out.println("result:"+insert);
        System.out.println("id:"+user.getId());
    }

3.删除功能

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据实体(ID)删除
     *
     * @param entity 实体对象
     * @since 3.4.4
     */
    int deleteById(T entity);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 删除(根据ID或实体 批量删除)
     *
     * @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList);

测试:

    // 删除功能
    @Test
    void delete(){
        // 根据ID进行删除
        // DELETE FROM user WHERE id=?
        int id = userMapper.deleteById(6);
        System.out.println(id);

        // 根据实体ID删除
        //  DELETE FROM user WHERE id=?
        long l = 23L;
        User user = new User(l,"6",6,"6");
        int userId = userMapper.deleteById(user);
        System.out.println(userId);

        // 根据collection集合批量删除
        // DELETE FROM user WHERE id IN ( ? , ? , ? , ? )
        List<Integer> listId = Arrays.asList(7,8,9,10);
        int batchIds = userMapper.deleteBatchIds(listId);
        System.out.println(batchIds);

        // 根据Map集合封装数据进行删除
        // DELETE FROM user WHERE name = ? AND age = ? AND email = ?
        Map<String,Object> map = new HashMap<>();
        map.put("name","海康");
        map.put("age",21);
        map.put("email","[email protected]");

        int deleteByMap = userMapper.deleteByMap(map);
        System.out.println(deleteByMap);
    }

4.修改功能

源码:

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

测试:

    // 测试功能
    @Test
    void update(){
        User user = new User();
        user.setId(8l);
        user.setName("海康");
        user.setAge(23);
        user.setEmail("[email protected]");

        // UPDATE user SET name=?, age=?, email=? WHERE id=?
        int update = userMapper.updateById(user);
        System.out.println(update);
    }

5.查询功能

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,查询一条记录
     * 

查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常

* * @param queryWrapper 实体对象封装操作类(可以为 null) */
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) { List<T> ts = this.selectList(queryWrapper); if (CollectionUtils.isNotEmpty(ts)) { if (ts.size() != 1) { throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records"); } return ts.get(0); } return null; } /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录 *

注意: 只返回第一个字段的值

* * @param queryWrapper 实体对象封装操作类(可以为 null) */
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

测试

    // 查询功能
    @Test
    void select(){
        // 根据ID查询用户信息
        // SELECT id,name,age,email FROM user WHERE id=?
        User user = userMapper.selectById(1l);
        System.out.println(user);

        
        // 根据Map集合封装条件查询
        // SELECT id,name,age,email FROM user WHERE name = ? AND age = ? AND email = ?
        Map<String,Object> map = new HashMap<>();
        map.put("name","明天");
        map.put("age",21);
        map.put("email","[email protected]");
        List<User> users = userMapper.selectByMap(map);
        System.out.println(users);


        // 根据多个ID查询多条数据
        // SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
        List<Long> asList = Arrays.asList(1l, 2l, 3l );
        List<User> selectBatchIds = userMapper.selectBatchIds(asList);
        selectBatchIds.forEach(user1 -> System.out.println(user1));

        
        // 根据条件进行查询返回List集合,如果没有传入条件(null),表示查询全部
        // SELECT id,name,age,email FROM user
        List<User> selectList = userMapper.selectList(null);
        selectList.forEach(user2-> System.out.println(user2));

        // 查询总记录数,如果没有传入条件(null),表示查询所有记录数
        // SELECT COUNT( * ) FROM user
        Long aLong = userMapper.selectCount(null);
        System.out.println(aLong);
    }

通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针 对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所 有数据

6.自定义功能

例如:我们可以查看返回Map集合,用于JSON数据返回

第一步:在UserMapper接口定义方法

@Mapper
public interface UserMapper extends BaseMapper<User> {

    // 自定义返回Map集合功能
    // 根据Id查询
    @MapKey("id")
    Map<String,Object> getUserById(@Param("id")Long id);
}

第二步:定义UserMapper.xml文件

<mapper namespace="com.haikang.plus.mapper.UserMapper">

    <select id="getUserById" resultType="map">
        select * from user where id=#{id};
    select>
mapper>

第三步:在核心配置文件中指定maper.xml文件的位置

mybatis-plus:
  configuration:
    #mybatisplus日志配置
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #指定xml的位置
  mapper-locations: /mapper/**/**.xml  #也是默认位置

mybatis-plus默认位置是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agYj5RcG-1656243259435)(img\1656130959680.png)]

第四步:编写控制器测试

    // 自定义功能
    @Test
    void map(){
        // 根据ID查询用户信息,并且返回Map方式
        //  select * from user where id=?;
        Map<String, Object> map = userMapper.getUserById(1l);
        System.out.println(map);
        // 返回值:{name=Jone, id=1, age=18, [email protected]}
    }

BaseMapper源码

/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * 

这个 Mapper 支持 id 泛型

* * @author hubin * @since 2016-01-23 */
public interface BaseMapper<T> extends Mapper<T> { /** * 插入一条记录 * * @param entity 实体对象 */ int insert(T entity); /** * 根据 ID 删除 * * @param id 主键ID */ int deleteById(Serializable id); /** * 根据实体(ID)删除 * * @param entity 实体对象 * @since 3.4.4 */ int deleteById(T entity); /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */ int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); /** * 根据 entity 条件,删除记录 * * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) */ int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 删除(根据ID或实体 批量删除) * * @param idList 主键ID列表或实体列表(不能为 null 以及 empty) */ int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList); /** * 根据 ID 修改 * * @param entity 实体对象 */ int updateById(@Param(Constants.ENTITY) T entity); /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); /** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); /** * 查询(根据 columnMap 条件) * * @param columnMap 表字段 map 对象 */ List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); /** * 根据 entity 条件,查询一条记录 *

查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常

* * @param queryWrapper 实体对象封装操作类(可以为 null) */
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) { List<T> ts = this.selectList(queryWrapper); if (CollectionUtils.isNotEmpty(ts)) { if (ts.size() != 1) { throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records"); } return ts.get(0); } return null; } /** * 根据 Wrapper 条件,判断是否存在记录 * * @param queryWrapper 实体对象封装操作类 * @return */ default boolean exists(Wrapper<T> queryWrapper) { Long count = this.selectCount(queryWrapper); return null != count && count > 0; } /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 entity 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类(可以为 null) */ List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录 *

注意: 只返回第一个字段的值

* * @param queryWrapper 实体对象封装操作类(可以为 null) */
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 entity 条件,查询全部记录(并翻页) * * @param page 分页查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */ <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); /** * 根据 Wrapper 条件,查询全部记录(并翻页) * * @param page 分页查询条件 * @param queryWrapper 实体对象封装操作类 */ <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); }

通用Service

1.Service接口CRUD

说明:

  • 通用 Service CRUD 封装[IService (opens new window)])接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
  • 泛型 T 为任意实体对象
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
  • 对象 Wrapper 为 [条件构造器])

A.IService接口

MyBatis-plus中有一个接口IService和其实现类ServiceImpl,封装了常见业务层逻辑详情查看源码IServiceSerivceImpl

所以我们可以自定义Serivce接口和实现类,继承和实现相关的接口和类

第一步:定义UserSerivce继承Iservice接口

/**
 * UserService继承IService模板提供的基础功能
 */
public interface UserService extends IService<User> {
}

第二步:定义UserServiceImpl实现类

/**
 * ServiceImpl实现了IService,提供了IService中基础功能的实现
 * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
 */
 
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

第三步:编写测试类

@SpringBootTest
public class MyBatisPlusServiceTest {

    @Autowired
    UserService userService;

    // 查询总记录数
    @Test
    void count(){
        // SELECT COUNT( * ) FROM user;
        long count = userService.count();
        System.out.println(count);
    }

    // 保存数据
    @Test
    void save(){
        // 保存一条记录
        // INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
        boolean save = userService.save(new User(9l, "湛江", 21, "[email protected]"));
        System.out.println(save);

        // 批量保存多条数据
        // INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
        List<User> asList = Arrays.asList(new User(10l, "西安", 22, "123qq.com"),
                new User(11l, "南宁", 22, "123qq.com"),
                new User(12l, "新疆", 23, "123qq.com"));
        boolean batch = userService.saveBatch(asList);
        System.out.println(batch);
    }

    // 修改数据
    @Test
    void update(){
        // 修改一条数据
        // UPDATE user SET name=?, age=?, email=? WHERE id=?
        boolean update = userService.updateById(new User(12l, "新疆", 20, "[email protected]"));
        System.out.println(update);
    }

    // 删除操作
    @Test
    void remove(){
        // DELETE FROM user WHERE id=?
        // 根据Id删除
        boolean remove = userService.removeById(12l);
        System.out.println(remove);
    }
}



这两个方法即有添加也有修改的功能,在没有Id时,表示添加,有Id时,表示修改



`public boolean saveOrUpdate(T entity)`
`public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize)`

更多操作请求参照官方文档 CRUD 接口 | MyBatis-Plus (baomidou.com)



ServiceImpl源码

/**
 * IService 实现类( 泛型:M 是 mapper 对象,T 是实体 )
 *
 * @author hubin
 * @since 2018-06-23
 */
@SuppressWarnings("unchecked")
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    protected M baseMapper;

    @Override
    public M getBaseMapper() {
        return baseMapper;
    }

    protected Class<T> entityClass = currentModelClass();

    @Override
    public Class<T> getEntityClass() {
        return entityClass;
    }

    protected Class<M> mapperClass = currentMapperClass();

    /**
     * 判断数据库操作是否成功
     *
     * @param result 数据库操作返回影响条数
     * @return boolean
     * @deprecated 3.3.1
     */
    @Deprecated
    protected boolean retBool(Integer result) {
        return SqlHelper.retBool(result);
    }

    protected Class<M> currentMapperClass() {
        return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), ServiceImpl.class, 0);
    }

    protected Class<T> currentModelClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), ServiceImpl.class, 1);
    }


    /**
     * 批量操作 SqlSession
     *
     * @deprecated 3.3.0
     */
    @Deprecated
    protected SqlSession sqlSessionBatch() {
        return SqlHelper.sqlSessionBatch(entityClass);
    }

    /**
     * 释放sqlSession
     *
     * @param sqlSession session
     * @deprecated 3.3.0
     */
    @Deprecated
    protected void closeSqlSession(SqlSession sqlSession) {
        SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(entityClass));
    }

    /**
     * 获取 SqlStatement
     *
     * @param sqlMethod ignore
     * @return ignore
     * @see #getSqlStatement(SqlMethod)
     * @deprecated 3.4.0
     */
    @Deprecated
    protected String sqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.table(entityClass).getSqlStatement(sqlMethod.getMethod());
    }

    /**
     * 批量插入
     *
     * @param entityList ignore
     * @param batchSize  ignore
     * @return ignore
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    /**
     * 获取mapperStatementId
     *
     * @param sqlMethod 方法名
     * @return 命名id
     * @since 3.4.0
     */
    protected String getSqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(mapperClass, sqlMethod);
    }

    /**
     * TableId 注解存在更新记录,否插入一条记录
     *
     * @param entity 实体对象
     * @return boolean
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdate(T entity) {
        if (null != entity) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            Object idVal = tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
            return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
        }
        return false;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
            Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal)
                || CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
        }, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }

    @Override
    public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {
        if (throwEx) {
            return baseMapper.selectOne(queryWrapper);
        }
        return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
    }

    @Override
    public Map<String, Object> getMap(Wrapper<T> queryWrapper) {
        return SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper));
    }

    @Override
    public <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
        return SqlHelper.getObject(log, listObjs(queryWrapper, mapper));
    }

    /**
     * 执行批量操作
     *
     * @param consumer consumer
     * @since 3.3.0
     * @deprecated 3.3.1 后面我打算移除掉 {@link #executeBatch(Collection, int, BiConsumer)} }.
     */
    @Deprecated
    protected boolean executeBatch(Consumer<SqlSession> consumer) {
        return SqlHelper.executeBatch(this.entityClass, this.log, consumer);
    }

    /**
     * 执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @param        泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
    }

    /**
     * 执行批量操作(默认批次提交数量{@link IService#DEFAULT_BATCH_SIZE})
     *
     * @param list     数据集合
     * @param consumer 执行方法
     * @param       泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, BiConsumer<SqlSession, E> consumer) {
        return executeBatch(list, DEFAULT_BATCH_SIZE, consumer);
    }

    @Override
    public boolean removeById(Serializable id) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
            return removeById(id, true);
        }
        return SqlHelper.retBool(getBaseMapper().deleteById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<?> list) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        if (tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill()) {
            return removeBatchByIds(list, true);
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
    }

    @Override
    public boolean removeById(Serializable id, boolean useFill) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        if (useFill && tableInfo.isWithLogicDelete()) {
            if (!entityClass.isAssignableFrom(id.getClass())) {
                T instance = tableInfo.newInstance();
                tableInfo.setPropertyValue(instance, tableInfo.getKeyProperty(), id);
                return removeById(instance);
            }
        }
        return SqlHelper.retBool(getBaseMapper().deleteById(id));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<?> list, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        return removeBatchByIds(list, batchSize, tableInfo.isWithLogicDelete() && tableInfo.isWithUpdateFill());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<?> list, int batchSize, boolean useFill) {
        String sqlStatement = getSqlStatement(SqlMethod.DELETE_BY_ID);
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        return executeBatch(list, batchSize, (sqlSession, e) -> {
            if (useFill && tableInfo.isWithLogicDelete()) {
                if (entityClass.isAssignableFrom(e.getClass())) {
                    sqlSession.update(sqlStatement, e);
                } else {
                    T instance = tableInfo.newInstance();
                    tableInfo.setPropertyValue(instance, tableInfo.getKeyProperty(), e);
                    sqlSession.update(sqlStatement, instance);
                }
            } else {
                sqlSession.update(sqlStatement, e);
            }
        });
    }

}

IService源码

/**
 * 顶级 Service
 *
 * @author hubin
 * @since 2018-06-23
 */
public interface IService<T> {

    /**
     * 默认批次提交数量
     */
    int DEFAULT_BATCH_SIZE = 1000;

    /**
     * 插入一条记录(选择字段,策略插入)
     *
     * @param entity 实体对象
     */
    default boolean save(T entity) {
        return SqlHelper.retBool(getBaseMapper().insert(entity));
    }

    /**
     * 插入(批量)
     *
     * @param entityList 实体对象集合
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean saveBatch(Collection<T> entityList) {
        return saveBatch(entityList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 插入(批量)
     *
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     */
    boolean saveBatch(Collection<T> entityList, int batchSize);

    /**
     * 批量修改插入
     *
     * @param entityList 实体对象集合
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
        return saveOrUpdateBatch(entityList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 批量修改插入
     *
     * @param entityList 实体对象集合
     * @param batchSize  每次的数量
     */
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    default boolean removeById(Serializable id) {
        return SqlHelper.retBool(getBaseMapper().deleteById(id));
    }

    /**
     * 根据 ID 删除
     *
     * @param id      主键(类型必须与实体类型字段保持一致)
     * @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    default boolean removeById(Serializable id, boolean useFill) {
        throw new UnsupportedOperationException("不支持的方法!");
    }

    /**
     * 根据实体(ID)删除
     *
     * @param entity 实体
     * @since 3.4.4
     */
    default boolean removeById(T entity) {
        return SqlHelper.retBool(getBaseMapper().deleteById(entity));
    }

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    default boolean removeByMap(Map<String, Object> columnMap) {
        Assert.notEmpty(columnMap, "error: columnMap must not be empty");
        return SqlHelper.retBool(getBaseMapper().deleteByMap(columnMap));
    }

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    default boolean remove(Wrapper<T> queryWrapper) {
        return SqlHelper.retBool(getBaseMapper().delete(queryWrapper));
    }

    /**
     * 删除(根据ID 批量删除)
     *
     * @param list 主键ID或实体列表
     */
    default boolean removeByIds(Collection<?> list) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
    }

    /**
     * 批量删除
     *
     * @param list    主键ID或实体列表
     * @param useFill 是否填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean removeByIds(Collection<?> list, boolean useFill) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        if (useFill) {
            return removeBatchByIds(list, true);
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list 主键ID或实体列表(主键ID类型必须与实体类型字段保持一致)
     * @return 删除结果
     * @since 3.5.0
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean removeBatchByIds(Collection<?> list) {
        return removeBatchByIds(list, DEFAULT_BATCH_SIZE);
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list    主键ID或实体列表(主键ID类型必须与实体类型字段保持一致)
     * @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean removeBatchByIds(Collection<?> list, boolean useFill) {
        return removeBatchByIds(list, DEFAULT_BATCH_SIZE, useFill);
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list      主键ID或实体列表
     * @param batchSize 批次大小
     * @return 删除结果
     * @since 3.5.0
     */
    default boolean removeBatchByIds(Collection<?> list, int batchSize) {
        throw new UnsupportedOperationException("不支持的方法!");
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list      主键ID或实体列表
     * @param batchSize 批次大小
     * @param useFill   是否启用填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    default boolean removeBatchByIds(Collection<?> list, int batchSize, boolean useFill) {
        throw new UnsupportedOperationException("不支持的方法!");
    }

    /**
     * 根据 ID 选择修改
     *
     * @param entity 实体对象
     */
    default boolean updateById(T entity) {
        return SqlHelper.retBool(getBaseMapper().updateById(entity));
    }

    /**
     * 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
     *
     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
     */
    default boolean update(Wrapper<T> updateWrapper) {
        return update(null, updateWrapper);
    }

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象
     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
     */
    default boolean update(T entity, Wrapper<T> updateWrapper) {
        return SqlHelper.retBool(getBaseMapper().update(entity, updateWrapper));
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     */
    @Transactional(rollbackFor = Exception.class)
    default boolean updateBatchById(Collection<T> entityList) {
        return updateBatchById(entityList, DEFAULT_BATCH_SIZE);
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     * @param batchSize  更新批次数量
     */
    boolean updateBatchById(Collection<T> entityList, int batchSize);

    /**
     * TableId 注解存在更新记录,否插入一条记录
     *
     * @param entity 实体对象
     */
    boolean saveOrUpdate(T entity);

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    default T getById(Serializable id) {
        return getBaseMapper().selectById(id);
    }

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表
     */
    default List<T> listByIds(Collection<? extends Serializable> idList) {
        return getBaseMapper().selectBatchIds(idList);
    }

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    default List<T> listByMap(Map<String, Object> columnMap) {
        return getBaseMapper().selectByMap(columnMap);
    }

    /**
     * 根据 Wrapper,查询一条记录 
*

结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")

* * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
default T getOne(Wrapper<T> queryWrapper) { return getOne(queryWrapper, true); } /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param throwEx 有多个 result 是否抛出异常 */ T getOne(Wrapper<T> queryWrapper, boolean throwEx); /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ Map<String, Object> getMap(Wrapper<T> queryWrapper); /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param mapper 转换函数 */ <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); /** * 查询总记录数 * * @see Wrappers#emptyWrapper() */ default long count() { return count(Wrappers.emptyWrapper()); } /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default long count(Wrapper<T> queryWrapper) { return SqlHelper.retCount(getBaseMapper().selectCount(queryWrapper)); } /** * 查询列表 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default List<T> list(Wrapper<T> queryWrapper) { return getBaseMapper().selectList(queryWrapper); } /** * 查询所有 * * @see Wrappers#emptyWrapper() */ default List<T> list() { return list(Wrappers.emptyWrapper()); } /** * 翻页查询 * * @param page 翻页对象 * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) { return getBaseMapper().selectPage(page, queryWrapper); } /** * 无条件翻页查询 * * @param page 翻页对象 * @see Wrappers#emptyWrapper() */ default <E extends IPage<T>> E page(E page) { return page(page, Wrappers.emptyWrapper()); } /** * 查询列表 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper) { return getBaseMapper().selectMaps(queryWrapper); } /** * 查询所有列表 * * @see Wrappers#emptyWrapper() */ default List<Map<String, Object>> listMaps() { return listMaps(Wrappers.emptyWrapper()); } /** * 查询全部记录 */ default List<Object> listObjs() { return listObjs(Function.identity()); } /** * 查询全部记录 * * @param mapper 转换函数 */ default <V> List<V> listObjs(Function<? super Object, V> mapper) { return listObjs(Wrappers.emptyWrapper(), mapper); } /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default List<Object> listObjs(Wrapper<T> queryWrapper) { return listObjs(queryWrapper, Function.identity()); } /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param mapper 转换函数 */ default <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) { return getBaseMapper().selectObjs(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList()); } /** * 翻页查询 * * @param page 翻页对象 * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */ default <E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> queryWrapper) { return getBaseMapper().selectMapsPage(page, queryWrapper); } /** * 无条件翻页查询 * * @param page 翻页对象 * @see Wrappers#emptyWrapper() */ default <E extends IPage<Map<String, Object>>> E pageMaps(E page) { return pageMaps(page, Wrappers.emptyWrapper()); } /** * 获取对应 entity 的 BaseMapper * * @return BaseMapper */ BaseMapper<T> getBaseMapper(); /** * 获取 entity 的 class * * @return {@link Class} */ Class<T> getEntityClass(); /** * 以下的方法使用介绍: * * 一. 名称介绍 * 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作 * 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的 * 二. 支持介绍 * * 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作 * 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作 * * 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推 * 1. 根据条件获取一条数据: `query().eq("column", value).one()` * 2. 根据条件删除一条数据: `update().eq("column", value).remove()` * */ /** * 链式查询 普通 * * @return QueryWrapper 的包装类 */ default QueryChainWrapper<T> query() { return ChainWrappers.queryChain(getBaseMapper()); } /** * 链式查询 lambda 式 *

注意:不支持 Kotlin

* * @return LambdaQueryWrapper 的包装类 */
default LambdaQueryChainWrapper<T> lambdaQuery() { return ChainWrappers.lambdaQueryChain(getBaseMapper()); } /** * 链式查询 lambda 式 * kotlin 使用 * * @return KtQueryWrapper 的包装类 */ default KtQueryChainWrapper<T> ktQuery() { return ChainWrappers.ktQueryChain(getBaseMapper(), getEntityClass()); } /** * 链式查询 lambda 式 * kotlin 使用 * * @return KtQueryWrapper 的包装类 */ default KtUpdateChainWrapper<T> ktUpdate() { return ChainWrappers.ktUpdateChain(getBaseMapper(), getEntityClass()); } /** * 链式更改 普通 * * @return UpdateWrapper 的包装类 */ default UpdateChainWrapper<T> update() { return ChainWrappers.updateChain(getBaseMapper()); } /** * 链式更改 lambda 式 *

注意:不支持 Kotlin

* * @return LambdaUpdateWrapper 的包装类 */
default LambdaUpdateChainWrapper<T> lambdaUpdate() { return ChainWrappers.lambdaUpdateChain(getBaseMapper()); } /** *

* 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作) *

* * @param entity 实体对象 */
default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) { return update(entity, updateWrapper) || saveOrUpdate(entity); } }

常用注解


### 1.`@TableName`

经过以上的测试,在使用MyBatis-puls实现基本的crud时,我们并没有指定要操作表,只是在Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为User

由此得出结论,MyBatis-plus确定操作的表时,由BaseMapper的泛型决定,即实体类型决定,且默认操作的表名和实体类型的类名一致


问题:若实体类类型的类名和要操作表的表名不一致时,会出现什么问题?

如:要操作的表名是t_user,操作实体类是User

我们将表user更名为t_user,测试查询功能 程序抛出异常,Table ‘mybatis_plus.user’ doesn’t exist,因为现在的表名为t_user,而默认操作 的表名和实体类型的类名一致,即user表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ArevEfMA-1656243741230)(img\1656142412193.png)]

解决方案:

方式一:使用@TableName作用在实体类上

@Data
@AllArgsConstructor
@NoArgsConstructor
// 设置要操作的表名 
@TableName("t_user")
public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

方式二:在全局配置文件中配置表名的前缀,将对所有操作实体生效

mybatis-plus:
  #设置表名前缀
  global-config:
    db-config:
      table-prefix: t_

@TableName注解相关属性

属性 类型 必须指定 默认值 描述
value String “” 表名
schema String “” schema
keepGlobalPrefix boolean false 是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMap String “” xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMap boolean false 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludeProperty String[] {} 需要排除的属性名 @since 3.3.1

关于 autoResultMap 的说明:

MP 会自动构建一个 resultMap 并注入到 MyBatis 里(一般用不上),请注意以下内容:

因为 MP 底层是 MyBatis,所以 MP 只是帮您注入了常用 CRUD 到 MyBatis 里,注入之前是动态的(根据您的 Entity 字段以及注解变化而变化),但是注入之后是静态的(等于 XML 配置中的内容)。

而对于 typeHandler 属性,MyBatis 只支持写在 2 个地方:

  1. 定义在 resultMap 里,作用于查询结果的封装
  2. 定义在 insertupdate 语句的 #{property} 中的 property 后面(例:#{property,typehandler=xxx.xxx.xxx}),并且只作用于当前 设置值

除了以上两种直接指定 typeHandler 的形式,MyBatis 有一个全局扫描自定义 typeHandler 包的配置,原理是根据您的 property 类型去找其对应的 typeHandler 并使用。


2.@TableId

经过以上的测试,MyBatis-Plus在实现crud时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id所以是默认就是id作为主键列,当我们表中主键不是id时【如是uid,并且此时表中主键也是uid】将会报错,因了解决这个问题需要在实体类上使用@TableId,标明那个字段作为主键


主键名不是id问题

若实体类和表中表示主键的不是id,而是其他字段,例如uid,MyBatis-Plus会自动识别uid为主 键列吗? 我们实体类中的属性id改为uid,将表中的字段id也改为uid,测试添加功能

程序抛出异常,Field ‘uid’ doesn’t have a default value,说明MyBatis-Plus没有将uid作为主键 赋值

在这里插入图片描述


通过@TableId解决问题

在实体中uid属性上通过@TableId将其标识为主键,即可成功执行SQL语句


案例:

public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    // 表明`uid`是主键,而默认`id`不是主键,防止报错
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}

@TableIdvalue属性

若实体类中主键对应的属性为id,而表中表示主键的字段为uid,此时若只在属性id上添加注解 @TableId,则抛出异常Unknown column ‘id’ in ‘field list’,即MyBatis-Plus仍然会将id作为表的 主键操作,而表中表示主键的是字段uid 此时需要通过@TableId注解的value属性,指定表中的主键字段,@TableId(“uid”)或 @TableId(value=“uid”)

注意是:当我们实体类主键名和表中主键名不一致时,可以使用@TableId注解中的value属性进行指定,表中的字段【因为是在mybatis-plus中第一步是:抽取属性名作为表中的字段进行查询的】

public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    // 通过value属性指定表中的ID
    @TableId(value = "uid")
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
@TableIdtype属性

type属性用来定义主键策略【就是用来定义主键是采用雪花算法【默认方式】还是使用自动递增方式】

type属性值

描述
idType.AssIGN_ID(默认值) 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关【注意点】
idType.AUTO 使用数据库的自增策略,注意是:该类型请确保数据库必须设置主键自增,否则直接报错
public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    // 通过value属性指定表中的ID
    // IdType.AUTO表示设置主键自增
    @TableId(value = "uid",type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
配置全局主键策略
mybatis-plus:
  #设置表名前缀
  global-config:
    db-config:
      #配置`mybatis-plus`的主键策略
      id-type: auto

3.@FableField

@FableFidld注解是作用实体类属性名与表中的字段名不一致时,使用的

经过以上的测试,我们可以发现,MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和 表中的字段名一致 如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?

解决方式一:在mybatis-plus中默认是开启驼峰命名风格的

若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格 例如实体类属性userName,表中字段user_name 此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格 相当于在MyBatis中配置

解决方式二:使用@TableField注解

若实体类中的属性和表中的字段不满足情况1 例如实体类属性name,表中字段username 此时需要在实体类属性上使用@TableField(“username”)设置属性所对应的字段名

案例:

例如:在表中的字段名为user_name,而在实体中属性名name,此时就可以使用@TabelField注解解决

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7zOleUUA-1656243741231)(img\1656148471751.png)]


public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    // 通过value属性指定表中的ID
    // IdType.AUTO表示设置主键自增
    @TableId(value = "uid",type = IdType.AUTO)
    private Long id;
    @TableField("user_name")
    private String name;
    private Integer age;
    private String email;
}

属性 类型 必须指定 默认值 描述
value String “” 数据库字段名
exist boolean true 是否为数据库表字段
condition String “” 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考(opens new window)
update String “” 字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)
insertStrategy Enum FieldStrategy.DEFAULT 举例:NOT_NULL insert into table_a(column) values (#{columnProperty})
updateStrategy Enum FieldStrategy.DEFAULT 举例:IGNORED update table_a set column=#{columnProperty}
whereStrategy Enum FieldStrategy.DEFAULT 举例:NOT_EMPTY where column=#{columnProperty}
fill Enum FieldFill.DEFAULT 字段自动填充策略
select boolean true 是否进行 select 查询
keepGlobalFormat boolean false 是否保持使用全局的 format 进行处理
jdbcType JdbcType JdbcType.UNDEFINED JDBC 类型 (该默认值不代表会按照该值生效)
typeHandler Class UnknownTypeHandler.class 类型处理器 (该默认值不代表会按照该值生效)
numericScale String “” 指定小数点后保留的位数

关于jdbcTypetypeHandler以及numericScale的说明:

numericScale只生效于 update 的 sql. jdbcTypetypeHandler如果不配合@TableName#autoResultMap = true一起使用,也只生效于 update 的 sql. 对于typeHandler如果你的字段类型和 set 进去的类型为equals关系,则只需要让你的typeHandler让 Mybatis 加载到即可,不需要使用注解



FieldStrategy(opens new window)

描述
IGNORED 忽略判断
NOT_NULL 非 NULL 判断
NOT_EMPTY 非空判断(只对字符串类型字段,其他类型字段依然为非 NULL 判断)
DEFAULT 追随全局配置

FieldFill

描述
DEFAULT 默认不处理
INSERT 插入时填充字段
UPDATE 更新时填充字段
INSERT_UPDATE 插入和更新时填充字段

4.逻辑删除

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

逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录

使用场景:可以进行数据恢复

注意是:逻辑删除是假的删除,只是修改了值,在查询时,也不能查询出逻辑删除中的数据

实现逻辑删除步骤

第一步:在数据库表中创建逻辑删除状态列,并设置默认值为0

MyBatis-Puls一篇就够_第3张图片



第二步:在实体类添加表示逻辑删除状态属性
public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    // 通过value属性指定表中的ID
    // IdType.AUTO表示设置主键自增
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @TableField("user_name")
    private String name;
    private Integer age;
    private String email;
    
    // 表示逻辑删除状态属性,默认是0,1表示删除
    @TableLogic
    private Integer isDeleted;
}

第三步:编写测试方法

    // 测试逻辑查询,可以看出逻辑删除后,状态码会变成1,所以不会被查询出来
    @Test
    void select(){
        // 当传入null时,表示查询所有信息
        // SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
        List users = userMapper.selectList(null);
        users.forEach(user-> System.out.println(user));
    }

    // 测试逻辑删除
    @Test
    void delete(){
        // 根据主键删除
        // UPDATE t_user SET is_deleted=1 WHERE id IN ( ? , ? , ? , ? ) AND is_deleted=0
        List asList = Arrays.asList(8l, 9l, 10l, 11l);
        int ids = userMapper.deleteBatchIds(asList);
        System.out.println(ids);
    }

测试 :测试删除功能

真正执行的是修改 UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0

测试查询功能,被逻辑删除的数据默认不会被查询 SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0

条件构造器和常用接口

1.wrapper介绍【记住下图】

MyBatis-Puls一篇就够_第4张图片

层级关系

  • Wrapper : 条件构造抽象类,最顶端父类
    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : 查询条件封装
      • UpdateWrapper : Update 条件封装
      • AbstractLambdaWrapper : 使用Lambda 语法
        • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper

注意是:在mybatis-plus中条件之间默认是使用and来连接的,如果需要使用连接时,就需要使用到or方法



2.QueryWrapper

A.组装查询条件

@SpringBootTest
public class WrapperMyBatisPlus {

    @Autowired
    private UserMapper userMapper;

    // 使用条件查询数据
    @Test
    void select(){
        // 查询用户名包含`a`,年龄在20到30之间,网邮箱信息不为null的用户信息
        // SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 
        // AND (user_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("user_name","a")
                .between("age",20,30)
                .isNotNull("email");

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(user-> System.out.println(user));
    }
}

B.组装排序条件

    // 排序查询
    @Test
    void orderSelect(){
        // 查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序
        // SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("age")
                .orderByAsc("id");

        // 调用方法
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

C.组装删除条件

    // 组装删除条件
    @Test
    void delete(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 删除邮箱为空并且用户名为 大同 的用户信息
        // 由于添加是逻辑删除,所以sql语句如下 :
        // UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL AND user_name = ?)
        queryWrapper.isNull("email")
                .eq("user_name","大同");

        //条件构造器也可以构建删除语句的条件
        int delete = userMapper.delete(queryWrapper);

        System.out.println(delete);
    }

D.条件的优先级【必须理解重点】

例如1:将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改

例如2: 将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r3EbdI7U-1656244086258)(D:\typora笔记\mybatisplus\img\1656160680935.png)]

案例1

    @Test
    void test1(){
        // 将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
        // 使用QueryWrapper封装用户信息,用于查询,查询到再进行修改
        // UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("age",20)
                .like("user_name","a")
                .or()
                .isNull("email");

        // 封装需要修改的信息
        User user = new User();
        user.setName("西藏");// 将用户名修改成西藏
        user.setEmail("[email protected]");// 将邮箱修改成[email protected]
        int result = userMapper.update(user, queryWrapper);
        System.out.println(result);
    }

案例2

    @Test
    void test2(){
        //  将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
        //==>  Preparing: UPDATE t_user SET user_name=?, age=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
        //==> Parameters: 杭州(String), 18(Integer), %a%(String), 20(Integer)
        
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("user_name","a")
                .and(i->i.gt("age",20)
                .or()
                .isNull("email"));

        // 封装要修改成的信息
        User user = new User();
        user.setName("杭州");
        user.setAge(18);

        int update = userMapper.update(user, queryWrapper);

        System.out.println(update);
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ju9e3HHE-1656244086258)(img\1656161345042.png)]

说明在mybatis-plus中如果需要优先执行,则可以使用andor【原因是它们本质就是lambda表达式,在mybatis-pluslambda优先执行】

UPDATE t_user SET user_name=?, age=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))

可以看出:有小括号的会优先执行该条件,在sql中会优先执行有小括号中的条件,小括号越多,优先级越高


E.组装SELECT子句

当有很多字段时,此方法可以设置只查询字段,如现在只需要查询`user-name`   `age`  `email`,只需要传入这个三个参数即可
public QueryWrapper<T> select(String... columns) {}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s5LxI33w-1656244086259)(D:\typora笔记\mybatisplus\img\1656162773011.png)]

    @Test
    void test3(){
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 现在只需要查询`user-name`  `age`  `email`
        // SELECT user_name,age,email FROM t_user WHERE is_deleted=0
        queryWrapper.select("user_name","age","email");

        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

F.子查询

子查询使用是``

#例:查询id小于等于9的用户信息
SELECT * FROM t_user WHERE id in (
	SELECT id FROM WHERE id<=9
);

案例

    @Test
    void test4(){
        /**
         * #例:查询id小于等于9的用户信息
         * SELECT * FROM t_user WHERE id in (
         * 	SELECT id FROM WHERE id<=9
         * );
         */

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 第一个参数:id是子查询返回的字段,第二个参数该值来至于该sql语句
        // SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (id IN (select id from t_user where id<=9))
        queryWrapper.inSql("id","select id from t_user where id<=9");

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

注意是:QueryWrapper即可完成查询功能的条件封装,也可以完成修改功能的条件封装,可以在修改功能中,还需要创建对应实体类封装修改信息,可以直接UpdateWrapper可以直接调用方法设置修改那些字段,减去创建对应实体类封装修改信息的步骤


3.@UpdateWrapper

例如2: 将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改

使用@UpdateWrapper简化创建对应 实体类封装修改信息的步骤

案例:

    @Test
    void test5(){
        //例如2: 将用户名中包含有b并且(年龄大于20或邮箱为null)的用户信息修改
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.like("user_name","b")
                .and(i->i.gt("age",20)
                .or()
                .isNull("email"));

        // 设置修改的字段
        updateWrapper.set("email","[email protected]");//表示将email字段修改为[email protected]
        updateWrapper.set("user_name","洛朗");//表示user_name字段修改为洛朗

        // 由于可以直接设置修改的字段,所以在实体类传入null,从而减去创建操作对应实体类封装数据步骤
        /**
         * ==>  Preparing: UPDATE t_user SET email=?,user_name=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
         * ==> Parameters: [email protected](String), 洛朗(String), %b%(String), 20(Integer)
         */
        userMapper.update(null,updateWrapper);
    }
模拟开发中组装条件的情况
例如:判断用户传入的`user_name`不能为空字符串或`null`或空白符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kdkjaL56-1656244086259)(img\1656204011016.png)]

    @Test
    void test6(){
        String user_name = "";
        Integer ageBegin = 18;
        Integer ageEnd = 108;

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        // 判断user_name 不为空字符串或null或空白符
        /*
        ==>  Preparing: SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
        ==> Parameters: 18(Integer), 108(Integer)
         */
        if(StringUtils.isNotBlank(user_name)){
            queryWrapper.like("user_name",user_name);
        }

        // 判断是否为空
        if (ageBegin!=null){
            queryWrapper.ge("age",ageBegin);
        }

        // 判断是否为空
        if (ageEnd!=null){
            queryWrapper.le("age",ageEnd);
        }

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

condition

在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因此我们在组装 这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响sql执行的结果

    @Test
    void test7(){

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        String user_name = "";
        Integer ageBegin = 18;
        Integer ageEnd = 108;

        /**
         * ==>  Preparing: SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
         * ==> Parameters: 18(Integer), 108(Integer)
         */
        queryWrapper.like(StringUtils.isNotBlank(user_name),"user_name",user_name)
                .ge(ageBegin!=null,"age",ageBegin)
                .le(ageEnd!=null,"age",ageEnd);

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

4.LambdaQueryWrapper

LambdaQueryWrapper该可以使用函数式编程,可以防止字段名写错,引起报错问题

可以防止字段名写错,引起报错问题public final LambdaQueryWrapper<T> select(SFunction<T, ?>... columns) {}
如:可以在`(SFunction<T, ?>... columns)`中传入,相关的字段名,可以防止字段名写错,引起报错问题
    @Test
    void test8(){
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        String user_name = "";
        Integer ageBegin = 18;
        Integer ageEnd = 108;

        /**
         * ==>  Preparing: SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
         * ==> Parameters: 18(Integer), 108(Integer)
         */
        lambdaQueryWrapper.like(StringUtils.isNotBlank(user_name),User::getName,user_name)
                .ge(ageBegin!=null,User::getAge,ageBegin)
                .le(ageEnd!=null,User::getAge,ageEnd);

        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        users.forEach(System.out::println);
    }

5.LambdaUpdateWrapper

LambdaUpdateWrapper该可以使用函数式编程,可以防止字段名写错,引起报错问题


    @Test
    void test9(){
        //例如2: 将用户名中包含有b并且(年龄大于20或邮箱为null)的用户信息修改
        /**
         * ==>  Preparing: SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL))
         * ==> Parameters: %b%(String), 20(Integer)
         */
        LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.like(User::getName,"b")
                .and(i->i.gt(User::getAge,20)
                .or()
                .isNull(User::getEmail));

        List<User> users = userMapper.selectList(lambdaUpdateWrapper);
        users.forEach(System.out::println);
    }


LambdaQueryWrapperLambdaUpdateWrapper可以在(SFunction... columns)中传入,相关的字段名,可以防止字段名写错,引起报错问题


/**
 * Lambda 语法使用 Wrapper
 *
 * @author hubin miemie HCL
 * @since 2017-05-26
 */
@SuppressWarnings("serial")
public class LambdaQueryWrapper<T> extends AbstractLambdaWrapper<T, LambdaQueryWrapper<T>>
    implements Query<LambdaQueryWrapper<T>, T, SFunction<T, ?>> {

    /**
     * 查询字段
     */
    private SharedString sqlSelect = new SharedString();

    public LambdaQueryWrapper() {
        this((T) null);
    }

    public LambdaQueryWrapper(T entity) {
        super.setEntity(entity);
        super.initNeed();
    }

    public LambdaQueryWrapper(Class<T> entityClass) {
        super.setEntityClass(entityClass);
        super.initNeed();
    }

    LambdaQueryWrapper(T entity, Class<T> entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq,
                       Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias,
                       SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
        super.setEntity(entity);
        super.setEntityClass(entityClass);
        this.paramNameSeq = paramNameSeq;
        this.paramNameValuePairs = paramNameValuePairs;
        this.expression = mergeSegments;
        this.sqlSelect = sqlSelect;
        this.paramAlias = paramAlias;
        this.lastSql = lastSql;
        this.sqlComment = sqlComment;
        this.sqlFirst = sqlFirst;
    }

    /**
     * SELECT 部分 SQL 设置
     *
     * @param columns 查询字段
     */
    @SafeVarargs
    @Override
    public final LambdaQueryWrapper<T> select(SFunction<T, ?>... columns) {
        if (ArrayUtils.isNotEmpty(columns)) {
            this.sqlSelect.setStringValue(columnsToString(false, columns));
        }
        return typedThis;
    }

    /**
     * 过滤查询的字段信息(主键除外!)
     * 

例1: 只要 java 字段名以 "test" 开头的 -> select(i -> i.getProperty().startsWith("test"))

*

例2: 只要 java 字段属性是 CharSequence 类型的 -> select(TableFieldInfo::isCharSequence)

*

例3: 只要 java 字段没有填充策略的 -> select(i -> i.getFieldFill() == FieldFill.DEFAULT)

*

例4: 要全部字段 -> select(i -> true)

*

例5: 只要主键字段 -> select(i -> false)

* * @param predicate 过滤方式 * @return this */
@Override public LambdaQueryWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) { if (entityClass == null) { entityClass = getEntityClass(); } else { setEntityClass(entityClass); } Assert.notNull(entityClass, "entityClass can not be null"); this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(entityClass).chooseSelect(predicate)); return typedThis; } @Override public String getSqlSelect() { return sqlSelect.getStringValue(); } /** * 用于生成嵌套 sql *

故 sqlSelect 不向下传递

*/
@Override protected LambdaQueryWrapper<T> instance() { return new LambdaQueryWrapper<>(getEntity(), getEntityClass(), null, paramNameSeq, paramNameValuePairs, new MergeSegments(), paramAlias, SharedString.emptyString(), SharedString.emptyString(), SharedString.emptyString()); } @Override public void clear() { super.clear(); sqlSelect.toNull(); } }

插件

ctlr+p可以查看当前方法需要参数

1.分页插件

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能


实现分页需要两步:

第一步:添加配置类
@Configuration
public class MyBatisPlusConfig {

    // 配置mybatis-plus拦截器
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 创建分页拦截器,并指定数据库类型,如使用是mysql
        PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        // 添加分页拦截器
        interceptor.addInnerInterceptor(innerInterceptor);

        return interceptor;
    }
}

第二步:测试

MyBatis-Puls一篇就够_第5张图片

@SpringBootTest
public class MyBatisPlusPage {

    @Autowired
    private UserMapper userMapper;

    @Test
    void test1(){
        // 设置分页参数
        Page<User> page = new Page<>(1,5);
        userMapper.selectPage(page,null);

        // 获取分页数据
        //  SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ?
        List<User> records = page.getRecords();// 当前页数据
        records.forEach(System.out::println);
        System.out.println("当前页:"+page.getCurrent());//当前页
        System.out.println("每页显示的条数:"+page.getSize());//每页显示大小
        System.out.println("总记录数:"+page.getTotal());//总记录数
        System.out.println("总页数:"+page.getPages());//总页数
        System.out.println("是否有上一页:"+page.hasNext());
        System.out.println("是否有下一页:"+page.hasNext());
    }
}


2.自定义分页

场景:当我们需要在自定义的方法,使用到mybatis-plus的分页插件时,就需要自定义分页方法,

自定义分页方法:必须保证第一个参数是mybatis-plus所提供的分页对象

第一步:在自定义UserMapper接口中定义方法

案例: 通过年龄查询用户信息并分页

@Mapper
public interface UserMapper extends BaseMapper<User> {


    /**
     *  根据年龄查询用户信息,并分页
     * @param page 分页对象,xml中可以从里面进行聚会,传递参数Page自动分页,必须放在第一位参数
     * @param age 年龄
     * @return
     */
    Page<User> selectPageVo(@Param("page")Page<User> page,@Param("age") Integer age);
}

我们可以参考官方编写的方法

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
第三步:定义UserMapper.xml文件
<!--
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
    void test2(){
        // 设置分页参数
        Page<User> page = new Page<>(1,5);
        // 查询年龄大于20的用户信息,并实现分页
        // select * from t_user where age>? LIMIT ?
        userMapper.selectPageVo(page,20);
        // 获取分页数据
        //  SELECT id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ?
        List<User> records = page.getRecords();// 当前页数据
        records.forEach(System.out::println);
        System.out.println("当前页:"+page.getCurrent());//当前页
        System.out.println("每页显示的条数:"+page.getSize());//每页显示大小
        System.out.println("总记录数:"+page.getTotal());//总记录数
        System.out.println("总页数:"+page.getPages());//总页数
        System.out.println("是否有上一页:"+page.hasNext());
        System.out.println("是否有下一页:"+page.hasNext());

        /**
         * ==>  Preparing: SELECT COUNT(*) AS total FROM t_user WHERE age > ?
         * ==> Parameters: 20(Integer)
         * <==    Columns: total
         * <==        Row: 12
         * <==      Total: 1
         * ==>  Preparing: select * from t_user where age>? LIMIT ?
         * ==> Parameters: 20(Integer), 5(Long)
         * <==    Columns: id, user_name, age, email, is_deleted
         * <==        Row: 3, Tom, 28, [email protected], 0
         * <==        Row: 4, 西藏, 21, [email protected], 0
         * <==        Row: 5, 洛朗, 24, [email protected], 0
         * <==        Row: 8, 海康, 23, [email protected], 1
         * <==        Row: 9, 湛江, 21, [email protected], 1
         * <==      Total: 5
         * Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fff46bf]
         * User(id=3, name=null, age=28, [email protected], isDeleted=0)
         * User(id=4, name=null, age=21, [email protected], isDeleted=0)
         * User(id=5, name=null, age=24, [email protected], isDeleted=0)
         * User(id=8, name=null, age=23, [email protected], isDeleted=1)
         * User(id=9, name=null, age=21, [email protected], isDeleted=1)
         * 当前页:1
         * 每页显示的条数:5
         * 总记录数:12
         * 总页数:3
         * 是否有上一页:true
         * 是否有下一页:true
         */
    }

IPage源码

/**
 * 分页 Page 对象接口
 */
public interface IPage<T> extends Serializable {

    /**
     * 获取排序信息,排序的字段和正反序
     *
     * @return 排序信息
     */
    List<OrderItem> orders();

    /**
     * 自动优化 COUNT SQL【 默认:true 】
     *
     * @return true 是 / false 否
     */
    default boolean optimizeCountSql() {
        return true;
    }

    /**
     * {@link com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor#isOptimizeJoin()}
     * 两个参数都为 true 才会进行sql处理
     *
     * @return true 是 / false 否
     * @since 3.4.4 @2021-09-13
     */
    default boolean optimizeJoinOfCountSql() {
        return true;
    }

    /**
     * 进行 count 查询 【 默认: true 】
     *
     * @return true 是 / false 否
     */
    default boolean searchCount() {
        return true;
    }

    /**
     * 计算当前分页偏移量
     */
    default long offset() {
        long current = getCurrent();
        if (current <= 1L) {
            return 0L;
        }
        return Math.max((current - 1) * getSize(), 0L);
    }

    /**
     * 最大每页分页数限制,优先级高于分页插件内的 maxLimit
     *
     * @since 3.4.0 @2020-07-17
     */
    default Long maxLimit() {
        return null;
    }

    /**
     * 当前分页总页数
     */
    default long getPages() {
        if (getSize() == 0) {
            return 0L;
        }
        long pages = getTotal() / getSize();
        if (getTotal() % getSize() != 0) {
            pages++;
        }
        return pages;
    }

    /**
     * 内部什么也不干
     * 

只是为了 json 反序列化时不报错

*/
default IPage<T> setPages(long pages) { // to do nothing return this; } /** * 分页记录列表 * * @return 分页对象记录列表 */ List<T> getRecords(); /** * 设置分页记录列表 */ IPage<T> setRecords(List<T> records); /** * 当前满足条件总行数 * * @return 总条数 */ long getTotal(); /** * 设置当前满足条件总行数 */ IPage<T> setTotal(long total); /** * 获取每页显示条数 * * @return 每页显示条数 */ long getSize(); /** * 设置每页显示条数 */ IPage<T> setSize(long size); /** * 当前页 * * @return 当前页 */ long getCurrent(); /** * 设置当前页 */ IPage<T> setCurrent(long current); /** * IPage 的泛型转换 * * @param mapper 转换函数 * @param 转换后的泛型 * @return 转换泛型后的 IPage */ @SuppressWarnings("unchecked") default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) { List<R> collect = this.getRecords().stream().map(mapper).collect(toList()); return ((IPage<R>) this).setRecords(collect); } /** * 老分页插件不支持 *

* MappedStatement 的 id * * @return id * @since 3.4.0 @2020-06-19 */ default String countId() { return null; } }


Page源码

/**
 * 简单分页模型
 */
public class Page<T> implements IPage<T> {

    private static final long serialVersionUID = 8545996863226528798L;

    /**
     * 查询数据列表
     */
    protected List<T> records = Collections.emptyList();

    /**
     * 总数
     */
    protected long total = 0;
    /**
     * 每页显示条数,默认 10
     */
    protected long size = 10;

    /**
     * 当前页
     */
    protected long current = 1;

    /**
     * 排序字段信息
     */
    @Setter
    protected List<OrderItem> orders = new ArrayList<>();

    /**
     * 自动优化 COUNT SQL
     */
    protected boolean optimizeCountSql = true;
    /**
     * 是否进行 count 查询
     */
    protected boolean searchCount = true;
    /**
     * {@link #optimizeJoinOfCountSql()}
     */
    @Setter
    protected boolean optimizeJoinOfCountSql = true;
    /**
     * countId
     */
    @Setter
    protected String countId;
    /**
     * countId
     */
    @Setter
    protected Long maxLimit;

    public Page() {
    }

    /**
     * 分页构造函数
     *
     * @param current 当前页
     * @param size    每页显示条数
     */
    public Page(long current, long size) {
        this(current, size, 0);
    }

    public Page(long current, long size, long total) {
        this(current, size, total, true);
    }

    public Page(long current, long size, boolean searchCount) {
        this(current, size, 0, searchCount);
    }

    public Page(long current, long size, long total, boolean searchCount) {
        if (current > 1) {
            this.current = current;
        }
        this.size = size;
        this.total = total;
        this.searchCount = searchCount;
    }

    /**
     * 是否存在上一页
     *
     * @return true / false
     */
    public boolean hasPrevious() {
        return this.current > 1;
    }

    /**
     * 是否存在下一页
     *
     * @return true / false
     */
    public boolean hasNext() {
        return this.current < this.getPages();
    }

    @Override
    public List<T> getRecords() {
        return this.records;
    }

    @Override
    public Page<T> setRecords(List<T> records) {
        this.records = records;
        return this;
    }

    @Override
    public long getTotal() {
        return this.total;
    }

    @Override
    public Page<T> setTotal(long total) {
        this.total = total;
        return this;
    }

    @Override
    public long getSize() {
        return this.size;
    }

    @Override
    public Page<T> setSize(long size) {
        this.size = size;
        return this;
    }

    @Override
    public long getCurrent() {
        return this.current;
    }

    @Override
    public Page<T> setCurrent(long current) {
        this.current = current;
        return this;
    }

    @Override
    public String countId() {
        return this.countId;
    }

    @Override
    public Long maxLimit() {
        return this.maxLimit;
    }

    /**
     * 查找 order 中正序排序的字段数组
     *
     * @param filter 过滤器
     * @return 返回正序排列的字段数组
     */
    private String[] mapOrderToArray(Predicate<OrderItem> filter) {
        List<String> columns = new ArrayList<>(orders.size());
        orders.forEach(i -> {
            if (filter.test(i)) {
                columns.add(i.getColumn());
            }
        });
        return columns.toArray(new String[0]);
    }

    /**
     * 移除符合条件的条件
     *
     * @param filter 条件判断
     */
    private void removeOrder(Predicate<OrderItem> filter) {
        for (int i = orders.size() - 1; i >= 0; i--) {
            if (filter.test(orders.get(i))) {
                orders.remove(i);
            }
        }
    }

    /**
     * 添加新的排序条件,构造条件可以使用工厂:{@link OrderItem#build(String, boolean)}
     *
     * @param items 条件
     * @return 返回分页参数本身
     */
    public Page<T> addOrder(OrderItem... items) {
        orders.addAll(Arrays.asList(items));
        return this;
    }

    /**
     * 添加新的排序条件,构造条件可以使用工厂:{@link OrderItem#build(String, boolean)}
     *
     * @param items 条件
     * @return 返回分页参数本身
     */
    public Page<T> addOrder(List<OrderItem> items) {
        orders.addAll(items);
        return this;
    }

    @Override
    public List<OrderItem> orders() {
        return this.orders;
    }

    @Override
    public boolean optimizeCountSql() {
        return optimizeCountSql;
    }

    public static <T> Page<T> of(long current, long size, long total, boolean searchCount) {
        return new Page<>(current, size, total, searchCount);
    }

    @Override
    public boolean optimizeJoinOfCountSql() {
        return optimizeJoinOfCountSql;
    }

    public Page<T> setSearchCount(boolean searchCount) {
        this.searchCount = searchCount;
        return this;
    }

    public Page<T> setOptimizeCountSql(boolean optimizeCountSql) {
        this.optimizeCountSql = optimizeCountSql;
        return this;
    }

    @Override
    public long getPages() {
        // 解决 github issues/3208
        return IPage.super.getPages();
    }

    /* --------------- 以下为静态构造方式 --------------- */
    public static <T> Page<T> of(long current, long size) {
        return of(current, size, 0);
    }

    public static <T> Page<T> of(long current, long size, long total) {
        return of(current, size, total, true);
    }

    public static <T> Page<T> of(long current, long size, boolean searchCount) {
        return of(current, size, 0, searchCount);
    }

    @Override
    public boolean searchCount() {
        if (total < 0) {
            return false;
        }
        return searchCount;
    }
}

乐观锁和悲观锁

1.简介

悲观锁:当一个用户获取到锁,这个用户操作完成后,释放锁后,其他用户获取到锁时,才能进行操作,就是就是当一个获取到锁后操作业务,其他用户只能等待

乐观锁:就是通过版本号操作,多个用户可以同时进行操作,当版本不同时,则修改会失败

场景:

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。

乐观锁和悲观锁:

上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。

2.模拟修改冲突

第一步:数据库中增加商品表

CREATE TABLE t_product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);

第二步:添加数据

INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

第三步:创建实体类

@Data
@TableName("t_product")
public class Product {
        private Long id;
        private String name;
        private Integer price;
        private Integer version;
}

第四步:编写ProductMapper接口

@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}

第五步:测试

    @Test
    void test3(){
        //1、小李
        Product p1 = productMapper.selectById(1L);
        System.out.println("小李取出的价格:" + p1.getPrice());
        //2、小王
        Product p2 = productMapper.selectById(1L);
        System.out.println("小王取出的价格:" + p2.getPrice());

        //3、小李将价格加了50元,存入了数据库
        p1.setPrice(p1.getPrice() + 50);
        int result1 = productMapper.updateById(p1);
        System.out.println("小李修改结果:" + result1);
        //4、小王将商品减了30元,存入了数据库
        p2.setPrice(p2.getPrice() - 30);
        int result2 = productMapper.updateById(p2);
        System.out.println("小王修改结果:" + result2);
        //最后的结果
        Product p3 = productMapper.selectById(1L);
        //价格覆盖,最后的结果:70,所以在第三次查询最终的价格为:70
        System.out.println("最后的结果:" + p3.getPrice());
    }

3.乐观锁实现流程【重点】

第一步:在数据库中添加version字段

取出记录时,获取当前version

SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时,version+1,如果where语句中的version版本号不对,则更新失败

UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND
`version`=1

说明:在Mybatis-plus中要想乐观锁生效,在操作前,第一步:必须先查询获取version版本号,第二步:再进行业务操作

第二步:在实现类中的version字段上添加@Version注解
@Data
@TableName("t_product")
public class Product {
        private Long id;
        private String name;
        private Integer price;
        // 表示版本号
        @Version
        private Integer version;
}
第三步:创建一个配置类,添加乐观锁插件
@Configuration
public class MyBatisPlusConfig {

    // 配置mybatis-plus拦截器
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

第四步:测试
    @Test
    public void testConcurrentVersionUpdate() {
        //小李取数据
        Product p1 = productMapper.selectById(1L);
        //小王取数据
        Product p2 = productMapper.selectById(1L);
        //小李修改 + 50
        p1.setPrice(p1.getPrice() + 50);
        int result1 = productMapper.updateById(p1);
        System.out.println("小李修改的结果:" + result1);
        //小王修改 - 30
        p2.setPrice(p2.getPrice() - 30);
        int result2 = productMapper.updateById(p2);
        System.out.println("小王修改的结果:" + result2);
        if(result2 == 0){
            //失败重试,重新获取version并更新
            p2 = productMapper.selectById(1L);
            p2.setPrice(p2.getPrice() - 30);
            result2 = productMapper.updateById(p2);
        }
        System.out.println("小王修改重试的结果:" + result2);
        //老板看价格
        Product p3 = productMapper.selectById(1L);
        System.out.println("老板看价格:" + p3.getPrice());
        // 老板看价格:120
    }

通用枚举

场景:

表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用mybatis-plus的通用枚举来实现

第一步:在数据库中添加字段sex


MyBatis-Puls一篇就够_第6张图片


第二步:创建枚举类,并且使用@EnumValue注解,那个字段添加到数据库中


@Getter//由于是枚举所以,只需要提交get方法,获取即可
public enum SexEnum {

    // 创建枚举对象
    MALE(0,"男"),
    FEMALE(1,"女");

    @EnumValue
    private Integer sex;
    private String sexName;

    // 提供有参构造器
     SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}


第三步:在实体类中添加枚举字段

public class User {
    // 注意是:由于在表中使用的bigint类型,所以需要使用long类型
    // 通过value属性指定表中的ID
    // IdType.AUTO表示设置主键自增
    @TableId(value = "id",type = IdType.AUTO)
    private Long id;
    @TableField("user_name")
    private String name;
    private Integer age;
    private String email;

    // 表示逻辑删除状态属性,默认是0,1表示删除
    @TableLogic
    private Integer isDeleted;
    // 添加枚举属性
    private SexEnum sex;


}

第四步:配置扫描通用枚举

mybatis-plus:
  type-enums-package: com.haikang.plus.myenum

第五步:测试

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

    @Test
    void test1(){
        User user = new User();
        user.setName("大朗");
        user.setId(20l);
        user.setAge(23);
        user.setSex(SexEnum.MALE);

        /**
         * ==>  Preparing: INSERT INTO t_user ( id, user_name, age, sex ) VALUES ( ?, ?, ?, ? )
         * ==> Parameters: 20(Long), 大朗(String), 23(Integer), 0(Integer)
         * <==    Updates: 1
         */

        int result = userMapper.insert(user);
        System.out.println("结果为:"+result);
    }
}

代码生成器

第一步:添加依赖

    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-generatorartifactId>
        <version>3.5.1version>
        dependency>
    <dependency>
        <groupId>org.freemarkergroupId>
        <artifactId>freemarkerartifactId>
        <version>2.3.31version>
    dependency>

第二步:创建类

public class FastAutoGeneratorTest {
	public static void main(String[] args) {

        FastAutoGenerator.create("url", "username", "password")
        .globalConfig(builder -> {
            builder.author("haikang") // 设置作者
                .enableSwagger() // 开启 swagger 模式
                .fileOverride() // 覆盖已生成文件
                .outputDir("D://mybatis_plus"); // 指定输出目录
        })
        .packageConfig(builder -> {
            builder.parent("com.haikang.mybatis") // 设置父包名
                .moduleName("haikang") // 设置父包模块名
                .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); // 设置mapperXml生成路径
        })
        .strategyConfig(builder -> {
            builder.addInclude("t_simple") // 设置需要生成的表名
                .addTablePrefix("t_", "c_"); // 设置过滤表前缀
        })
        .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
        .execute();
	}
}

多数据源

特性:

  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密 ENC()。
  • 支持每个数据库独立初始化表结构schema和数据库database。
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
  • 支持 自定义注解 ,需继承DS(3.2.0+)。
  • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
  • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
  • 提供 自定义数据源来源 方案(如全从数据库加载)。
  • 提供项目启动后 动态增加移除数据源 方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
  • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
  • 提供 基于seata的分布式事务方案。
  • 提供 本地多数据源事务方案。

场景:

适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等 目前我们就来模拟一个纯粹多库的一个场景,其他场景类似 场景说明: 我们创建两个库,分别为:mybatis_plus(以前的库不动)与mybatis_plus_1(新建),将 mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例 分别获取用户数据与商品数据,如果获取到说明多库模拟成功

1.创建数据库及

创建数据库mybatis_plus_1和表product

CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus_1`;
CREATE TABLE product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

#删除mybatis_plus库product表
use mybatis_plus;
DROP TABLE IF EXISTS product;

2.引入依赖

        mybatis-plus依赖
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.5.1version>
        dependency>


        多数据源依赖
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>dynamic-datasource-spring-boot-starterartifactId>
            <version>3.5.0version>
        dependency>

3.配置多数据源

注意是:如果在项目中有之前的数据源的配置,想要测试该功能

注释掉之前的数据库连接,添加新配置

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

4.创建对应的Mapper接口和xml文件

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


@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haikang.datasource.mapper.UserMapper">

mapper>



DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haikang.datasource.mapper.ProductMapper">

mapper>

5.创建对应的Service接口和实现类

并且指定该组件使用@DS(指定使用那个数据源)

使用 @DS 切换数据源。

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

注解 结果
没有@DS 默认数据源
@DS(“dsName”) dsName可以为组名也可以为具体某个库的名称
public interface UserService extends IService<User> {
}


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


public interface ProductService extends IService<Product> {
}

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

6.测试

@SpringBootTest
class MybatisPlusDatasourceApplicationTests {

    @Autowired
    private UserService userService;
    @Autowired
    private ProductService productService;

    @Test
    void contextLoads() {

        /**
         * ==>  Preparing: SELECT id,user_name,age,email,sex,is_deleted FROM t_user WHERE id=?
         * ==> Parameters: 1(Integer)
         * <==    Columns: id, user_name, age, email, sex, is_deleted
         * <==        Row: 1, Jone, 18, [email protected], 0, 0
         * <==      Total: 1
         */
        System.out.println(userService.getById(1));

        /**
         * ==>  Preparing: SELECT id,name,price,version FROM product WHERE id=?
         * ==> Parameters: 1(Integer)
         * <==    Columns: id, name, price, version
         * <==        Row: 1, 外星人笔记本, 100, 0
         * <==      Total: 1
         */
        System.out.println(productService.getById(1));
    }

}




MyBatisX快速开发插件

第一步:创建SpringBoot工程

MyBatis-Puls一篇就够_第7张图片


注意是:由于在创建SpringBoot工程时,我们没有添加mybatis-plus场景启动器,所以需要我们手动添加


       mybatis-plus场景启动器
       <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.5.1version>
        dependency>
        多数据源场景启动器
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>dynamic-datasource-spring-boot-starterartifactId>
            <version>3.5.0version>
        dependency>


第二步:配置相关的数据源配置

#修改端口号
server:
  port: 80

#配置数据源信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    username: root
    password: root

第三步:下载MybatisX

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。



MyBatis-Puls一篇就够_第8张图片


第四步:在IDEA连接数据库生成对应文件

MyBatis-Puls一篇就够_第9张图片

MyBatis-Puls一篇就够_第10张图片

MyBatis-Puls一篇就够_第11张图片

MyBatis-Puls一篇就够_第12张图片

MyBatis-Puls一篇就够_第13张图片

MyBatis-Puls一篇就够_第14张图片



第五步:编写方法进行测试


编写方法名后,alt+enter自动生成方法返回值xml文件



MyBatis-Puls一篇就够_第15张图片


A.添加方法测试

MyBatis-Puls一篇就够_第16张图片

    // 添加方法
    int insertSelective(User user);
    <insert id="insertSelective">
        insert into t_user
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">id,if>
            <if test="userName != null">user_name,if>
            <if test="age != null">age,if>
            <if test="email != null">email,if>
            <if test="sex != null">sex,if>
            <if test="isDeleted != null">is_deleted,if>
        trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id,jdbcType=BIGINT},if>
            <if test="userName != null">#{userName,jdbcType=VARCHAR},if>
            <if test="age != null">#{age,jdbcType=INTEGER},if>
            <if test="email != null">#{email,jdbcType=VARCHAR},if>
            <if test="sex != null">#{sex,jdbcType=INTEGER},if>
            <if test="isDeleted != null">#{isDeleted,jdbcType=INTEGER},if>
        trim>
    insert>

编写测试方法

@SpringBootTest
class SpringbootMybatisXApplicationTests {

    @Autowired
    UserMapper userMapper;

    @Test
    void insert() {
        User user = new User(21l,"西京",22,"[email protected]",1,0);
        int selective = userMapper.insertSelective(user);
        /**
         * ==>  Preparing: insert into t_user ( id, user_name, age, email, sex, is_deleted ) values ( ?, ?, ?, ?, ?, ? )
         * ==> Parameters: 21(Long), 西京(String), 22(Integer), [email protected](String), 1(Integer), 0(Integer)
         * <==    Updates: 1
         */
        System.out.println(selective);
    }
}

注意是:如果要继续添加条件使用And,根据条件查询则要By

B.修改用户信息

MyBatis-Puls一篇就够_第17张图片

    // 修改方法//updateUserNameAndAgeAndEmailById根据方法名可以得知:
    // 需要修改用户名,年龄,邮箱信息,是根据用户ID进行修改的
    int updateUserNameAndAgeAndEmailById(@Param("userName") String userName, @Param("age") Integer age, @Param("email") String email, @Param("id") Long id);

    <update id="updateUserNameAndAgeAndEmailById">
        update t_user
        set user_name = #{userName,jdbcType=VARCHAR},
            age       = #{age,jdbcType=NUMERIC},
            email     = #{email,jdbcType=VARCHAR}
        where id = #{id,jdbcType=NUMERIC}
    update>

测试

    @Test
    void update(){
        // 修改用户信息
        /**
         * ==>  Preparing: update t_user set user_name = ?, age = ?, email = ? where id = ?
         * ==> Parameters: 安吉(String), 19(Integer), [email protected](String), 1(Long)
         * <==    Updates: 1
         */
        int id = userMapper.updateUserNameAndAgeAndEmailById("安吉", 19, "[email protected]", 1l);
        System.out.println(id);
    }

C.查询用户信息

MyBatis-Puls一篇就够_第18张图片

    // 查询用户信息//selectByUserNameAndAge根据方法名可以得知
    // 需要根据用户名和年龄查询用户信息
    List<User> selectByUserNameAndAge(@Param("userName") String userName, @Param("age") Integer age);

    <select id="selectByUserNameAndAge" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from t_user
        where
        user_name = #{userName,jdbcType=VARCHAR}
        AND age = #{age,jdbcType=NUMERIC}
    select>

测试:

    @Test
    void select(){
        // 查询用户信息,根据用户名和年龄
        /**
         * ==>  Preparing: select id,user_name,age, email,sex,is_deleted from t_user where user_name = ? AND age = ?
         * ==> Parameters: 海康(String), 23(Integer)
         */
        List<User> list = userMapper.selectByUserNameAndAge("海康", 23);
        list.forEach(System.out::println);
        // User(id=8, userName=海康, age=23, [email protected], sex=0, isDeleted=1)
    }
D.删除用户信息

MyBatis-Puls一篇就够_第19张图片

    // 删除用户信息//deleteByUserNameAndEmail根据方法名可以得知
    // 根据用户名和邮箱删除用户信息
    int deleteByUserNameAndEmail(@Param("userName") String userName, @Param("email") String email);
    <delete id="deleteByUserNameAndEmail">
        delete
        from t_user
        where user_name = #{userName,jdbcType=VARCHAR}
          AND email = #{email,jdbcType=VARCHAR}
    delete>

测试

    @Test
    void delete(){
        // 根据用户名和年龄删除用户信息
        /**
         * ==>  Preparing: delete from t_user where user_name = ? AND email = ?
         * ==> Parameters: 安吉(String), [email protected](String)
         */
        int delete = userMapper.deleteByUserNameAndEmail("安吉", "[email protected]");
        System.out.println(delete);
    }

你可能感兴趣的:(mybatis,spring,boot,后端)