MyBatis-Puls一篇就够

文章目录

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.框架结构

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


执行的流程是:通过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, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');


2.创建一个SpringBoot工程

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

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

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</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<T> 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,"123@qq.com");
        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","123@qq.com");

        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("123@qq.com");

        // 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 条件,查询一条记录
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @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 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @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","123@qq.com");
        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">
<!--
    // 自定义返回Map集合功能
    // 根据Id查询
    Map<String,Object> getUserById(@Param("id")Long id);
-->
    <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=test1@baomidou.com}
    }

BaseMapper源码

/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 *
 * @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 条件,查询一条记录
     * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
     *
     * @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 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @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, "123@qq.com"));
        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, "123@qq.com"));
        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 <E>       泛型
     * @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 <E>      泛型
     * @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,查询一条记录 <br/>
     * <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
     *
     * @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<T>}
     */
    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 式
     * <p>注意:不支持 Kotlin </p>
     *
     * @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 式
     * <p>注意:不支持 Kotlin </p>
     *
     * @return LambdaUpdateWrapper 的包装类
     */
    default LambdaUpdateChainWrapper<T> lambdaUpdate() {
        return ChainWrappers.lambdaUpdateChain(getBaseMapper());
    }

    /**
     * <p>
     * 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
     * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作)
     * </p>
     *
     * @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注解相关属性

属性类型必须指定默认值描述
valueString“”表名
schemaString“”schema
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMapString“”xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludePropertyString[]{}需要排除的属性名 @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;
}

属性类型必须指定默认值描述
valueString“”数据库字段名
existbooleantrue是否为数据库表字段
conditionString“”字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s}参考(opens new window)
updateString“”字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)
insertStrategyEnumFieldStrategy.DEFAULT举例:NOT_NULL insert into table_a(column) values (#{columnProperty})
updateStrategyEnumFieldStrategy.DEFAULT举例:IGNORED update table_a set column=#{columnProperty}
whereStrategyEnumFieldStrategy.DEFAULT举例:NOT_EMPTY where column=#{columnProperty}
fillEnumFieldFill.DEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
jdbcTypeJdbcTypeJdbcType.UNDEFINEDJDBC 类型 (该默认值不代表会按照该值生效)
typeHandlerClass<? extends TypeHandler>UnknownTypeHandler.class类型处理器 (该默认值不代表会按照该值生效)
numericScaleString“”指定小数点后保留的位数

关于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

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



第二步:在实体类添加表示逻辑删除状态属性
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<User> 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<Long> 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介绍【记住下图】

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

层级关系

  • 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("123@qq.com");// 将邮箱修改成123@qq.com
        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","123@qq.com");//表示将email字段修改为123@qq.com
        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: 123@qq.com(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<T, ?>... 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;
    }

    /**
     * 过滤查询的字段信息(主键除外!)
     * <p>例1: 只要 java 字段名以 "test" 开头的             -> select(i -&gt; i.getProperty().startsWith("test"))</p>
     * <p>例2: 只要 java 字段属性是 CharSequence 类型的     -> select(TableFieldInfo::isCharSequence)</p>
     * <p>例3: 只要 java 字段没有填充策略的                 -> select(i -&gt; i.getFieldFill() == FieldFill.DEFAULT)</p>
     * <p>例4: 要全部字段                                   -> select(i -&gt; true)</p>
     * <p>例5: 只要主键字段                                 -> select(i -&gt; false)</p>
     *
     * @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
     * <p>故 sqlSelect 不向下传递</p>
     */
    @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;
    }
}

第二步:测试

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

@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, test3@baomidou.com, 0
         * <==        Row: 4, 西藏, 21, 123@qq.com, 0
         * <==        Row: 5, 洛朗, 24, 123@qq.com, 0
         * <==        Row: 8, 海康, 23, 123@qq.com, 1
         * <==        Row: 9, 湛江, 21, 123@qq.com, 1
         * <==      Total: 5
         * Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fff46bf]
         * User(id=3, name=null, age=28, email=test3@baomidou.com, isDeleted=0)
         * User(id=4, name=null, age=21, email=123@qq.com, isDeleted=0)
         * User(id=5, name=null, age=24, email=123@qq.com, isDeleted=0)
         * User(id=8, name=null, age=23, email=123@qq.com, isDeleted=1)
         * User(id=9, name=null, age=21, email=123@qq.com, 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;
    }

    /**
     * 内部什么也不干
     * <p>只是为了 json 反序列化时不报错</p>
     */
    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 <R>    转换后的泛型
     * @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);
    }

    /**
     * 老分页插件不支持
     * <p>
     * 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

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


第二步:创建枚举类,并且使用@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.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.1</version>
        </dependency>
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.31</version>
    </dependency>
第二步:创建类
public class FastAutoGeneratorTest {
	public static void main(String[] args) {

        FastAutoGenerator.create("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.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>


        多数据源依赖
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </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> {
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haikang.datasource.mapper.UserMapper">

</mapper>


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.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, test1@baomidou.com, 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工程

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


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


       mybatis-plus场景启动器
       <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        多数据源场景启动器
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </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 搜索并安装。



在这里插入图片描述


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

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

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

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

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

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

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



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


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



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


A.添加方法测试

在这里插入图片描述

    // 添加方法
    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,"123@qq.com",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), 123@qq.com(String), 1(Integer), 0(Integer)
         * <==    Updates: 1
         */
        System.out.println(selective);
    }
}

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

B.修改用户信息

在这里插入图片描述

    // 修改方法//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), 123@qq.com(String), 1(Long)
         * <==    Updates: 1
         */
        int id = userMapper.updateUserNameAndAgeAndEmailById("安吉", 19, "123@qq.com", 1l);
        System.out.println(id);
    }

C.查询用户信息

在这里插入图片描述

    // 查询用户信息//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=123@qq.com, sex=0, isDeleted=1)
    }
D.删除用户信息

在这里插入图片描述

    // 删除用户信息//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), 123@qq.com(String)
         */
        int delete = userMapper.deleteByUserNameAndEmail("安吉", "123@qq.com");
        System.out.println(delete);
    }
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值