MyBatis-Plus

一、简述

MyBatis-Plus 简称 MP,是 MyBatis 的

一个增强工具,只做增强不做改变,可

以简化开发、提高效率

2.  特性

① 无侵入、损耗小

② 强大的 CRUD 操作:内置通用 Mapper、

    通用 Service,仅仅通过少量配置即可实

    现单表大部分 CRUD 操作,更有强大的

    条件构造器,满足各类使用需求

③ 支持主键自动生成

④ 支持 Lambda 形式调用:通过 Lambda

     表达式,方便的编写各类查询条件,

     需再担心字段写错

⑤ 支持 ActiveRecord 模式:实体类只需

     承 Model 类即可进行强大的 CRUD 操作

⑥ 支持自定义全局通用操作

⑦ 内置代码生成器:采用代码或者 Maven

    插件可快速生成 Mapper、Model、

    Service、Controller 层代码,支持模板引

    擎,还有超多自定义配置

⑧ 分页插件,分页插件支持多种数据库

⑨ 内置性能分析插件:可输出 SQL 语句以

     及其执行时间,建议开发测试时启用该

     功能,能快速揪出慢查询

⑩ 内置全局拦截插件:提供全表 delete、

     update 操作智能分析阻断,也可自定义

     拦截规则,预防误操作

二、入门案例

1. 建库建表

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; 
use `mybatis_plus`; 
CREATE TABLE `user` ( 
    `id` bigint(20) NOT NULL COMMENT '主键ID', 
    `name` varchar(30) DEFAULT NULL COMMENT '姓名', 
    `age` int(11) DEFAULT NULL COMMENT '年龄', 
    `email` varchar(50) DEFAULT NULL COMMENT '邮箱', 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user (id, name, age, email) VALUES 
(1, 'Jone', 18, 'test1@123.com'), 
(2, 'Jack', 20, 'test2@123.com'), 
(3, 'Tom', 28, 'test3@123.com'), 
(4, 'Sandy', 21, 'test4@123.com'), 
(5, 'Billie', 24, 'test5@123.com');

2. 创建工程

① 使用 Spring Initializer 快速初始化

    一个 Spring Boot 工程

 

 ② 引入 MyBatis-Plus 的依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

 ③ 安装 Lombok 插件

是一个在Java开发过程中用注解

方式,简化了 JavaBean 的编写

避免了冗余和样板式代码而出现的

插件,让编写的类更加简洁

 3. 配置编码

① 配置 application.yml 文件

#配置端口
server:
  port: 80

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

#MyBatis-Plus相关配置
mybatis-plus:
  configuration:
    #配置日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

② 在SpringBoot 启动类中添加 @MapperScan

    注解,扫描 Mapper 文件夹

@SpringBootApplication
@MapperScan("指定Mapper接口所在的包")
public class MybatisPlusDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(MybatisPlusDemoApplication.class, args);
	}
}

③ 编写实体类 User.Java (此处用了 Lombok

     简化代码)

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

④ 编写 Mapper 包下的 UserMapper 接口

public interface UserMapper extends BaseMapper<User> {}

4. 测试查询

① 编写一个测试类 MyBatisTest.java

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

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

② 控制台打印查询结果

三、CRUD

1. BaseMapper<T>

通用 CRUD 封装 BaseMapper 接口,

为 MP 启动时自动解析实体表关系映

射转换为 Mybatis 内部对象注入容器

① 泛型 T 为任意实体对象

② 参数 Serializable 为任意类型主键,

     MP 不推荐使用复合主键,约定每

     一张表都有自己的唯一 id 主键

③ 对象 Wrapper 条件构造器 

MP 中的基本 CRUD 在内置的

BaseMapper 中都已得到了实现,因

此我们继承该接口以后可以直接使用

BaseMapper<T> 中的 CRUD:

(1) 增加:Insert

// 插入一条记录
int insert(T entity);

(2) 删除:Delete

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

(3) 修改:Update

// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);

(4) 查询:Select

// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

2. 调用 Mapper 层实现 CRUD

(1) 插入

/**
  * 测试插入一条数据
  * MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
  */
@Test
public void testInsert(){
    User user = new User();
    user.setName("Vz");
    user.setAge(21);
    user.setEmail("vz@oz6.cn");
    int result = userMapper.insert(user);
    System.out.println(result > 0 ? "添加成功!" : "添加失败!");
    System.out.println("受影响的行数为:" + result);
    //1527206783590903810(当前 id 为雪花算法自动生成的id)
    System.out.println("id自动获取" + user.getId());
}

(2) 删除

① 根据 ID 删除数据

调用方法:int deleteById(serializable id);

/**
  * 测试根据id删除一条数据
  */
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(1527206783590903810L);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

② 根据 ID 批量删除数据

调用方法:int deleteBatchIds(@Param(

Constants.COLLECTION) Collection<?

extends Serializable> idList);

/**
  * 测试通过id批量删除数据
  */
@Test
public void testDeleteBatchIds(){
    List<Long> ids = Arrays.asList(6L,7L,8L);
    int result = userMapper.deleteBatchIds(ids);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

③ 根据 Map 条件删除数据

调用方法:int deleteByMap(@Param(

Constants.COLUMN_MAP) Map<String

,Object> columnMap);

/**
   * 测试根据Map集合中所设置的条件删除数据
   */
@Test
public void testDeleteByMap(){
    //当前演示为根据name和age删除数据
    //执行SQL为:DELETE FROM user WHERE name = ? AND age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("name","Vz");
    map.put("age",21);
    int result = userMapper.deleteByMap(map);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

(3) 修改

调用方法:int updateById(@Param(

Constants.ENTITY) T entity)

/**
  * 测试根据id修改用户信息
  */
@Test
public void testUpdateById(){
    //执行SQL为: UPDATE user SET name=?, age=?, email=? WHERE id=?
    User user = new User();
    user.setId(6L);
    user.setName("VzUpdate");
    user.setAge(18);
    user.setEmail("Vz@sina.com");
    int result = userMapper.updateById(user);
    System.out.println(result > 0 ? "修改成功!" : "修改失败!");
    System.out.println("受影响的行数为:" + result);
}

(4) 查询

① 根据 ID 查询用户信息

调用方法:T selectById(Serializable id);

/**
  * 测试根据id查询用户数据
  */
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

② 根据多个 ID 查询多个用户信息

调用方法:List selectBatchIds(@Parm(

Constants.COLLECTION) Collection<?

extends Serializable> idList

/**
  * 根据多个id查询用户数据
  */
@Test
public void testSelectBatchIds(){
    //执行SQL为:SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
    List<Long> ids = Arrays.asList(1L,2L,3L);
    List<User> users = userMapper.selectBatchIds(ids);
    users.forEach(System.out::println);
}

③ 根据 Map 条件查询用户信息

调用方法:List selectByMap(@Param(

Constants.COLUMN_MAP) MAP<String,

Object> columnMap);

/**
  * 根据Map所设置的条件查询用户
  */
@Test
public void testSelectByMap(){
    //执行SQL为:SELECT id,name,age,email FROM user WHERE age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("age",18);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

④ 查询所有用户信息

调用方法:List selectList(@Param(

Constants.WRAPPER) Wrapper

queryWrapper);

/**
  * 测试查询所有数据
  */
@Test
void testSelectList(){
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

3. 通用 Service

通用 Service CRUD 封装 IService 接口

进一步封装 CRUD 采用 get 查询单行 

remove 删除 list 查询集合 page 分页

前缀命名方式区分 Mapper 层避免混淆

① 泛型 T 为任意实体对象

② 建议如果存在自定义通用 Service 方

法的可能,请创建自己的 IBaseService

继承 MP 提供的基类

③ 对象 Wrapper 为条件构造器

MP 中有一个接口 IService 和其实现类

ServiceImpl,封装了常见的业务层逻辑,

因此我们在使用的时候仅需在自己定义的

Service 接口中继承 IService 接口,在自

己的实现类中实现自己的 Service 并继承

ServiceImpl 即可

IBaseService 中的 CRUD:

(1) 增加:Save、SaveOrUpdate

// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);

// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

(2) 删除:Remove

// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);

(3) 修改:Update

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);

(4) 查询:Get、List、Count

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);


// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);

(5) 分页:Page

// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

4. 调用 Service 层操作数据

① 创建 UserService 并继承 IService

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

② 创建 UserService 的实现类并继承

    ServiceImpl

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

③ 测试查询记录数

调用方法:int count(); 

@Test
public void testGetCount(){
    //查询总记录数
    //执行的SQL为:SELECT COUNT( * ) FROM user
    long count = userService.count();
    System.out.println("总记录数:" + count);
}

④ 测试批量插入数据

调用方法:boolean saveBatch(Collection entityList);

@Test
public void test(){
    List<User> list = new ArrayList<>();
    for (int i = 1; i <= 10; i++) {
        User user = new User();
        user.setName("Vz"+i);
        user.setAge(20+i);
        list.add(user);
    }
    boolean b = userService.saveBatch(list);
    System.out.println(b ? "添加成功!" : "添加失败!");
}

四、常用注解

MP 提供的注解可以帮我们解决一

些数据库与实体之间相互映射的问

1. @TableName

MP 在确定操作的表时,由

BaseMapper 的泛型决定,即实体类

型决定,且默认操作的表名和实体类

型的类名一致

若实体类类型的类名和要操作的表的表名

不一致,程序会抛出异常:

Table ‘mybatis_plus.user’ doesn’t exist

解决方法:

① 使用注解解决

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

② 使用全局配置解决

为实体类所对应的表名设置默认的前缀

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

2. @TableId

MP 在实现 CRUD 时,会默认将

id 作为主键列,并在插入数据时,

默认基于雪花算法的策略生成 id

若实体类和表中表示主键的不是 id

而是其他字段,程序会抛出异常:

Field ‘uid’ doesn’t have a default value

解决方法:

在实体类中 uid 属性上通过 @TableId

将其标识为主键,即可成功执行 SQL

语句

@Date
public class User {
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}

(2) @TableId 的 value 属性

若实体类中主键对应的属性为 id,而

表中表示主键的字段为 uid,此时若

只在属性 id 上添加注解 @TableId,

则抛出异常

Unknown column ‘id’ in ‘field list’

即 MP 仍然会将 id 作为表的主键操

作,而表中表示主键的是字段 uid

此时需要通过 @TableId 注解的

value 属性,指定表中的主键字段,

@TableId("uid")@TableId(value

="uid")

(3) @TableId 的 type 属性

type 属性用来定义主键策略:默认

雪花算法

常用的主键策略:

值        描述
IdType.ASSIGN_ID (默认)

基于雪花算法的策略生成数据 id,

与数据库 id 是否设置自增无关

IdType.AUTO

使用数据库的自增策略,注意,该

类型请确保数据库设置了 id 自增

配置全局主键策略:

#MyBatis-Plus相关配置
mybatis-plus:
  configuration:
    #配置日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #配置mp的主键策略为自增
      id-type: auto
      # 设置实体类所对应的表的统一前缀
      table-prefix: t_

3. @TableField

MP 在执行 SQL 语句时,要保证实

体类中的属性名和表中的字段名

不一致的情况:

(1) 若实体类中的属性使用的是驼峰命名

     风格,而表中的字段使用的是下划线

     命名风格

例如:实体类属性 userName,表中

           字段 user_name

此时 MP 会自动下划线命名风格

化为驼峰命名风格相当于在 MyBatis

中配置

(2) 若实体类中的属性和表中的字段不满

     足情况1

例如:实体类属性 name,表中字段

           username

此时需要在实体类属性上使用

@TableField("username") 设置属性

所对应的字段名

public class User {
    @TableId("uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
}

4. @TableLogic

(1) 逻辑删除

物理删除:真实删除,将对应数据从

                 数据库中删除,之后查询

                 不到此条被删除的数据

逻辑删除假删除,将对应数据中代

                 表是否被删除字段的状态

                 修改为“被删除状态”,之后

                 在数据库中仍旧能看到此

                 条数据记录

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

(2) 实现逻辑删除

① 数据库中创建逻辑删除状态列,设置默

    认值为 0

 实体类中添加逻辑删除属性 

③ 测试删除功能,真正执行的是修改 

public void testDeleteById(){
    int result = userMapper.deleteById(1527472864163348482L);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

 ④ 此时执行查询方法,查询的结果为

     自动添加条件 is_deleted=0

五、条件构造器

1. Wrapper 介绍

 Wrapper条件构造抽象类,最顶端父类
AbstractWrapper

用于查询条件封装,生成 sql 的

where 条件

QueryWrapper查询条件封装
UpdateWrapperUpdate 条件封装
AbstractLambdaWrapper使用 Lambda 语法
LambdaQueryWrapper

用于 Lambda 语法使用的查询

Wrapper

LambdaUpdateWrapperLambda 更新封装 Wrapper

2. QueryWrapper

组装查询条件

SELECT uid AS id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
public void test01(){
    //查询用户名包含a,年龄在20到30之间,邮箱信息不为null的用户信息
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("username","a").between("age",20,30).isNotNull("email");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

组装排序条件

SELECT uid AS id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
public void test02(){
    //查询用户信息,按照年龄的降序排序,若年龄相同,则按照id升序排序
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("age").orderByAsc("id");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

组装删除条件

UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
public void test03(){
    //删除邮箱地址为null的用户信息
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNull("email");
    int result = userMapper.delete(queryWrapper);
    System.out.println(result > 0 ? "删除成功!" : "删除失败!");
    System.out.println("受影响的行数为:" + result);
}

④ 条件的优先级

UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)
public void test04(){
    //将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.gt("age",20).like("username","a").or().isNull("email");
    User user = new User();
    user.setName("Oz");
    user.setEmail("test@oz6.com");

    int result = userMapper.update(user, updateWrapper);
    System.out.println(result > 0 ? "修改成功!" : "修改失败!");
    System.out.println("受影响的行数为:" + result);
}
UPDATE t_user SET username=?, email=? WHERE is_deleted=0 AND (username LIKE ? AND (age > ? OR email IS NULL))
public void test05(){
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.like("username","a").and(i->i.gt("age",20).or().isNull("email"));
    User user = new User();
    user.setName("Vz7797");
    user.setEmail("test@ss8o.com");

    int result = userMapper.update(user, updateWrapper);
    System.out.println(result > 0 ? "修改成功!" : "修改失败!");
    System.out.println("受影响的行数为:" + result);
}

⑤ 组装 select 子句

SELECT username,age,email FROM t_user WHERE is_deleted=0
public void test06(){
    //查询用户的用户名、年龄、邮箱信息
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("username","age","email");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}

⑥ 实现子查询

SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 100))
public void test07(){
    //查询id小于等于100的用户信息
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.inSql("uid", "select uid from t_user where uid <= 100");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

3. UpdateWrapper

UpdateWrapper 不仅拥有 QueryWrapper

的组装条件功能,还提供了 set 方法进行

修改对应条件的数据库信息

public void test08(){
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.like("username","a").and( i -> i.gt("age",20).or().isNull("email")).set("email","svip@qq.com");
    int result = userMapper.update(null, updateWrapper);
    System.out.println(result > 0 ? "修改成功!" : "修改失败!");
    System.out.println("受影响的行数为:" + result);
}

4. condition

在真正开发的过程中,组装条件是

常见的功能,而这些条件数据来源

于用户输入,是可选的,因此我们

在组装这些条件时,必须先判断

户是否选择了这些条件,若选择则

需要组装该条件,若没有选择则一

定不能组装,以免影响SQL执行的

结果

方案一:

SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age <= ?)
 public void test09(){
     String username = "a";
     Integer ageBegin = null;
     Integer ageEnd = 30;
     QueryWrapper<User> queryWrapper = new QueryWrapper<>();
     if(StringUtils.isNotBlank(username)){
         //isNotBlank判断某个字符创是否不为空字符串、不为null、不为空白符
         queryWrapper.like("user_name", username);
     }
     if(ageBegin != null){
         queryWrapper.ge("age", ageBegin);
     }
     if(ageEnd != null){
         queryWrapper.le("age", ageEnd);
     }
     List<User> list = userMapper.selectList(queryWrapper);
     list.forEach(System.out::println);
 }

方案二:

上面的实现方案没有问题,但是代码

比较复杂,我们可以使用带 condition

参数的重载方法构建查询条件,简化

代码的编写

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

5. LambdaQueryWrapper

功能等同于 QueryWrapper,提供了

Lambda 表达式的语法可以避免填错

列名

public void test11(){
    String username = "a";
    Integer ageBegin = null;
    Integer ageEnd = 30;
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
        .ge(ageBegin != null, User::getAge, ageBegin)
        .le(ageEnd != null, User::getAge, ageEnd);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

6. LambdaUpdateWrapper 

功能等同于UpdateWrapper,提供了Lambda表达式的语法可以避免填错列名

public void test12(){
    //将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
    LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.like(User::getName, "a")
        .and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));
    updateWrapper.set(User::getName, "小黑").set(User::getEmail,"abc@atguigu.com");
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result:"+result);
}

六、常用插件

1. 分页插件

MyBatis Plus 自带分页插件,只要

简单的配置即可实现分页功能

(1) 添加配置类 MyBatisPlusConfig

@Configuration
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

(2) 编写测试方法

@Test
public void testPage(){
    //new Page()中的两个参数分别是当前页码,每页显示数量
    Page<User> page = userMapper.selectPage(new Page<>(1, 2), null);
    List<User> users = page.getRecords();
    users.forEach(System.out::println);
}

2. 自定义分页

(1) 在 UserMapper 接口中定义一个方法

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

(2) 在 UserMapper.xml 中编写SQL实现该方法

<select id="selectPageVo" resultType="User">
    select id,username as name,age,email from t_user where age > #{age}
</select>

(3) 编写测试方法

@Test
public void testPageVo(){
    Page<User> page = userMapper.selectPageVo(new Page<User>(1,2), 20);
    List<User> users = page.getRecords();
    users.forEach(System.out::println);
}

3. 乐观锁

(1) 作用:

当要更新一条记录的时候,希望

这条记录没有被别人更新

(2) 实现

① 取出记录时,获取当前 version

更新时,带上这个 version

③ 执行更新时, set version =

   newVersion where version =

   oldVersion

④ 如果 version 不对,就更新失败

(3) 乐观锁与悲观锁

1) 乐观锁

乐观锁:乐观锁总是假设最好的情况,

认为共享资源每次被访问的时候不会

出现问题,线程可以不停地执行,无

需加锁也无需等待,只是在提交修改

的时候去验证对应的资源(也就是数据)

是否被其它线程修改了(具体方法可

以使用版本号机制或 CAS 算法)

2) 悲观锁  

悲观锁:悲观锁总是假设最坏的情况,

认为共享资源每次被访问的时候就会

出现问题(比如共享数据被修改),所

以每次在获取资源操作的时候都会上

锁,这样其他线程想拿到这个资源就

会阻塞直到锁被上一个持有者释放。

也就是说,

共享资源每次只给一个线程使用,其

它线程阻塞,用完后再把资源转让给

其它线程

3) CAS 算法 

CAS 的全称是 Compare And Swap 

(比较与交换) ,用于实现乐观锁,被

广泛应用于各大框架中。

CAS 的思想很简单,就是用一个预期

和要更新的变量值进行比较,两值

相等才会进行更新

CAS 是一个原子操作,底层依赖于一

条CPU的原子指令。原子操作即最小

不可拆分的操作,也就是说操作一旦

开始,就不能被打断,直到操作完成

    CAS 涉及到三个操作数:

V :要更新的变量值 (Var)
E :预期值 (Expected)
N :拟写入的新值(New)
当且仅当 V 的值等于 E 时,CAS 通过

原子方式用新值 N更新 V 的值。

如果不等,说明已经有其它线程更新了

V,则当前线程放弃更新

4) 乐观锁存在 ABA 问题

如果一个变量 V 初次读取的时候是 A 值,

它的值可能在准备赋值的之前被改为其

他值,然后又改回 A,那 CAS 操作就会

误认为它从来没有被修改过。这个问题

被称为 CAS 操作的 "ABA" 问题。

ABA 问题的解决思路是在变量前面追加

上版本号或者时间戳

JDK 1.5 以后的 AtomicStampedReference

类就是用来解决 ABA 问题的,其中的

compareAndSet() 方法就是首先检查当前

引用是否等于预期引用,并且当前标志是

否等于预期标志,如果全部相等,则以原

子方式将该引用和该标志的值设置为给定

的更新值

(4) 乐观锁解决问题

① 实体类 version 字段添加注解 @Version

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    @Version
    private Integer version;
}

② 添加乐观锁插件配置

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //添加分页插件
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    //添加乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

七、枚举

表中的有些字段值是固定的,例如性

别(男或女),此时我们可以使用 MP

的通用枚举来实现

① 数据库表添加字段 sex

② 创建通用枚举类型

@Getter
public enum SexEnum {
    MALE(1, "男"),
    FEMALE(2, "女");

    @EnumValue //将注解所标识的属性的值存储到数据库中
    private int sex;
    private String sexName;

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

③ User 实体类中添加属性 sex

public class User {
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;

    @TableLogic
    private int isDeleted;  //逻辑删除

    private SexEnum sex;
}

④ 配置扫描通用枚举

#MyBatis-Plus相关配置
mybatis-plus:
  #指定mapper文件所在的地址
  mapper-locations: classpath:mapper/*.xml
  configuration:
    #配置日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    banner: off
    db-config:
      #配置mp的主键策略为自增
      id-type: auto
      # 设置实体类所对应的表的统一前缀
      table-prefix: t_
  #配置类型别名所对应的包
  type-aliases-package: com.atguigu.mybatisplus.pojo
  # 扫描通用枚举的包
  type-enums-package: com.atguigu.mybatisplus.enums

⑤ 执行测试方法

@Test
public void test(){
    User user = new User();
    user.setName("admin");
    user.setAge(33);
    user.setSex(SexEnum.MALE);
    int result = userMapper.insert(user);
    System.out.println("result:"+result);
}

八、多数据源

适用于多种场景:纯粹多库、 读写

分离、 一主多从、 混合模式等

场景说明:

创建两个库,分别为: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. 新建工程引入依赖

自行新建一个Spring Boot工程并

选择MySQL驱动及Lombok依赖

引入MP的依赖及多数据源的依赖

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

4. 创建实体类

① 新建一个 User 实体类(如果数据

    库表名有t_前缀记得配置)

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

 ② 新建一个实体类 Product

@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    private Integer version;
}

5. 创建 Mapper 及 Service

① 新建接口 UserMapper

public interface UserMapper extends BaseMapper<User> {}

② 新建接口 ProductMapper

public interface ProductMapper extends BaseMapper<Product> {}

③ 新建 Service 接口 UserService

    定操作的数据源

@DS("master") //指定操作的数据源,master为user表
public interface UserService extends IService<User> {}

④ 新建 Service 接口 ProductService

    指定操作的数据源

@DS("slave_1")
public interface ProductService extends IService<Product> {}

⑤ 自行建立 Service 的实现类

6. 编写测试方法

记得在启动类中添加注解 @MapperScan()

class TestDatasourceApplicationTests {
	@Resource
	UserService userService;

	@Resource
	ProductService productService;

	@Test
	void contextLoads() {
		User user = userService.getById(1L);
		Product product = productService.getById(1L);
		System.out.println("User = " + user);
		System.out.println("Product = " + product);
	}

}

九、MyBatisX 插件

MP 为我们提供了强大的 mapper

service 模板,能够大大的提高开发

效率

但是在真正开发过程中,MP 并不能

为我们解决所有问题,例如一些复杂

的SQL,多表联查,我们就需要自己

去编写代码和SQL语句,我们该如何

快速的解决这个问题呢,这个时候可

以使用MyBatisX插件

MyBatisX 是一款基于 IDEA 的快速开

发插件,为效率而生

1. 安装 MyBatisX 插件

打开 IDEA,File-> Settings->Plugins

->MyBatisX,搜索栏搜索 MyBatisX

然后安装

2. 快速生成代码

① 新建一个Spring Boot项目引入依赖

(创建工程时记得勾选 lombok mysql

驱动)

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

② 配置数据源信息

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    username: root
    password: 132537

③ 在 IDEA 中与数据库建立链接

④ 填写数据库信息并保存

 ⑤ 找到我们需要生成的表点击右键

⑥ 填写完信息以后下一步 

⑦ 继续填写信息

 ⑧ 大功告成

 

3. 快速生成 CRUD

MyBaitsX 可以根据我们在 Mapper

中输入的方法名快速帮我们生成对

应的 sql 语句

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值