MyBatisPlus学习

目录

一.简介

1、Mybatis-Plus介绍

2、特性

 3、搭建环境

a、数据库

b、创建springboot项目

c、测试

d、 mybatis-plus 日志

4.框架结构

二、基本crud 

1.BaseMapper 方法

2. 简单 CRUD测试

a. insert

b、delete

c、update

d、select

3、自定义功能

4.Service接口

a、说明

b、创建 UserService

c、service方法测试

三.常用注解

 1. @TableName

2. @TableId

a. value属性:

b. type属性:

c. 全局配置

3. 雪花算法

a.数字意义

b.优点

4. @TableField

5.@TableLogic

a.逻辑删除

b.实现逻辑删除

c、执行删除测试

四、Wrapper、条件构造器

1.简介

 2.QueryWrapper

a、wrapper查询

 b、wrapper排序

 c、wrapper删除

 d、wrapper修改

e、条件优先级

  f、wrapper select

 g、wrapper 子查询

3、update Wrapper

4、condition 动态sql

a、if 判断

b、condition

5、lambdaQueryWrapper

6、LambdaUpdateWrapper

五、插件

1、分页插件

a、配置

b、测试

2、自定义分页

a、userMapper 接口中定义方法

b、userMapper.xml 中编写sql

c、测试

3、乐观锁

a、场景

b、乐观锁与悲观锁

c、模拟冲突修改

d、乐观锁实现流程

e、mybatis_plus 实现乐观锁

六、通用枚举

a、数据库添加sex字段方便测试

b、创建枚举类型

c、包扫描配置

d、测试

七、代码生成器

a、引入依赖

b、复制代码

c、生成的目录

 八、多数据源

场景

 1、创建数据库和表

2、新建项目

 3、引入依赖

4、配置多数据源

5、创建service

 a、创建 userservice

b、创建 productService

6、测试成功

九、MyBatisX 插件

1、安装

 2、使用

a、idea连接数据

b、点击表右键—>MybatisX-Generator 

c、配置

 3、CRUD


一.简介

1、Mybatis-Plus介绍

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

官网:https://mybatis.plus/ 或 https://mp.baomidou.com/

2、特性

简介 | MyBatis-Plus (baomidou.com)

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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、搭建环境

a、数据库

创建表

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

添加数据

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

b、创建springboot项目

需要依赖:mysql、mybatis-plus、lombok

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

User类

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

 mapper接口

//继承 BaseMapper 泛型参数是pojo(要操作的类)
@Repository
public interface UserMapper extends BaseMapper<User> {

}

c、测试

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

d、 mybatis-plus 日志

# m-p 日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.框架结构

 

二、基本crud 

1.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);
}

2. 简单 CRUD测试

a. insert

    @Test
    void insert() {
        User cc = new User(null, "臭臭", 3, "123456789@qq.com");
        int count = userMapper.insert(cc);
        System.out.println("count = " + count);
        System.out.println("cc = " + cc.getId());
    }

b、delete

    @Test
    void delete() {
        // deleteById   id后面加个 L (lang类型)
        int i = userMapper.deleteById(1672633330522226690L);
        
        // 通过多个条件删除,并且的关系 and
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","张三");
        map.put("age",12);
        int count1 = userMapper.deleteByMap(map);
    
        // 根据 id 批量删除   where id in(....)
        List<Long> list = Arrays.asList(1L, 2L, 3L, );
        int count2 = userMapper.deleteBatchIds(list);
    }

c、update

    @Test
    void update(){
        // 通过实体修改  updata user set name=?,age=? where id=?
        User user = new User();
        user.setId(1L);
        user.setName("李四");
        user.setAge(23);
        int i = userMapper.updateById(user);
    }

d、select

    @Test
    void select() {
        //根据id查询用户信息
        //SELECT id,name,age,email FROM user WHERE id=?
        User user = userMapper.selectById(4L);
    
        //根据多个id查询多个用户信息
        //SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
        List<Long> idList = Arrays.asList(4L, 5L);
        List<User> list = userMapper.selectBatchIds(idList);
    
        //通过map条件查询用户信息
        //SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
        Map<String, Object> map = new HashMap<>();
        map.put("age", 22);
        map.put("name", "admin");
        List<User> users = userMapper.selectByMap(map);
    
        //查询所有用户信息
        //SELECT id,name,age,email FROM user
        List<User> alluser = userMapper.selectList(null);
    }

3、自定义功能

mapper:

//继承 BaseMapper 泛型参数是pojo(要操作的类)
@Repository
public interface UserMapper extends BaseMapper<User> {

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

}


mapper.xml
    <select id="selectByUID" parameterType="String" resultType="map">
        SELECT * FROM user WHERE id = ${id}
    </select>


test:
          // 自定义查询
        Map<String, Object> map = userMapper.selectByUID(1L);
        System.out.println("map = " + map);

4.Service接口

a、说明

  • 接口 public interface IService<T> {} 里面定义了很多常用方法
  • IService<T> 的实现类:(M 是指 mapper,T 是指要操作的类(查询的表))
  • public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {}

b、创建 UserService

1. 新建UserService 接口 继承 IService,这样就能使用 IService 中的方法了

public interface UserService extends IService<User> {
}    
   

2. 新建 UserService 的实现类 UserServiceImpl 实现 UserService

public class UserServiceImpl  implements UserService {

}

3. 这个时候 UserServiceImpl 还是会报错,因为需要实现 IService中的方法,我们不去实现方法,而是继承 IService 的实现类 ServiceImpl(这里面实现了方法)

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

}

c、service方法测试

1.首先注入 service 

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

2. 测试

@SpringBootTest
public class MPServiceTest {
    
    @Autowired
    private UserServiceImpl userService;
    
    @Test
    void testGetCount() {
        // 查询总记录数
        // SELECT COUNT( * ) FROM user
        long count = userService.count();
        System.out.println("count = " + count);
    }
    
    @Test
    void batchInsert() {
        // 批量插入
        // 循环 insert into 单条sql语句
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i <10; i++) {
            User user = new User();
            user.setAge(i+1);
            user.setName("batch"+i);
            users.add(user);
        }
        boolean b = userService.saveBatch(users);
    }
}

三.常用注解

 1. @TableName

当实体类名为 User,数据库表名为 t_user 的时候

就可以使用 此注解表明该类对应的表是 xxx

@TableName("t_user")
public class User {

}

2. @TableId

  • mybatis-plus 默认将 字段名为 id 的字段当成主键
  • 如果字段名为 uid 就会报错,就可以使用 此注解显示指定主键
    @TableId
    private Long uid;

a. value属性:

实体类 和 数据库字段名不一样,使用 value 属性表示数据库字段名

    // 一个属性的时候 可以省略value 直接写字符串   
    @TableId(value = "uid") 
    private Long id;

b. type属性:

设置主键生成策略

IdType.ASSIGN_ID基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
IdType.AUTO使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效

c. 全局配置

# m-p 日志
mybatis-plus:
  # 全局配置
  global-config:
    db-config:
      # 实体类对应表的统一前缀
      table-prefix: # t_
       # 设置统一的主键生成策略
      id-type: auto # 数据库递增

3. 雪花算法

雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。.

a.数字意义

长度共64bit (一个ong型)
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截- 开始时间截),结果约等于69.73年。10bit作为机器的ID (5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。

12bit作为毫秒内的流水号 (意味着每个节点在每毫秒可以产生 4096 个D)。

b.优点

整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。

4. @TableField

指定属性所对应的 数据库字段名

m-p 中默认可以驼峰对应下划线(类字段名:userName,数据库字段名:user_name)

    // 和数据库中的 user_name 字段对应
    @TableField("user_name")
    private String name;

5.@TableLogic

a.逻辑删除

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
  • 使用场景:可进行数据恢复

b.实现逻辑删除

step1:数据库中 创建逻辑删除状态列,设置默认值为0

step2:实体类中新增对应的字段 、并且添加 @TableLogic

@TableLogic
private int isDeleted;

c、执行删除测试

使用了逻辑删除后。所有的查询都会添加 is_deleted = 0 条件

删除操作 不会去删除数据,而是将这个字段的值改成 0 ,删除sql如下:

删除后再查询就查不出来被逻辑删除的数据了 ,查询sql如下

四、Wrapper、条件构造器

1.简介

 2.QueryWrapper

a、wrapper查询

    @Test
    void test01() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name", "o")  //用户名包含 o
                .between("age", 20, 30)     // 年龄 20 - 30
                .isNotNull("email");    // 邮箱不为 null
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

sql:

 b、wrapper排序

    @Test
    void testSort() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("age") // 按照年龄的降序排序
                .orderByAsc("id"); // 若年龄相同按照 id 升序排序
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }

sql: 

 c、wrapper删除

    @Test
    void testDelete() {
        // 删除邮箱为 null 的数据
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.isNull("email");
        int count = userMapper.delete(wrapper);
        System.out.println("count = " + count);
    }

使用了逻辑删除,所以是 update

sql:

 d、wrapper修改

    @Test
    void testUpdate() {
        // 修改 (年龄大于20且用户名中包含a) 或邮箱为 null 的用户信息
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age",20)
                .like("name","a")
                .or()   // 和前面的两个条件是或者关系
                .isNull("email");
        // 修改以下内容
        User user = new User();
        user.setName("小明");
        user.setAge(18);
        int result = userMapper.update(user, wrapper);
        System.out.println("result = " + result);
    }

sql

e、条件优先级

lambda中的条件会优先执行
    @Test
    void testCondition() {
        // 修改 用户名中包含a 且 (年龄大于20且 或 邮箱为 null) 的用户信息
        // lambda中的条件会优先执行
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name","a")
                // lambda 表达式 
                .and(wrapper -> wrapper.gt("age",20).or().isNull("email"));
    
        // 修改以下内容
        User user = new User();
        user.setName("小明");
        user.setAge(18);
        int result = userMapper.update(user, queryWrapper);
        System.out.println("result = " + result);
    }

sql 

  f、wrapper select

    @Test
    void testSelect() {
        // 查询用户名是 臭臭 的 用户名、年龄、邮箱
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.select("name","age","email")
                .eq("name","臭臭");
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);
    }

 sql

==>  Preparing: SELECT name,age,email FROM user WHERE (name = ?)

 g、wrapper 子查询

演示

    @Test
    // 演示 子查询
    void test03() {
        // 查询id小于等于100 的用户信息
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.inSql("id","select id from user where id <= 100");
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
        
//==>  Preparing: SELECT id,name,age,email FROM user WHERE (id IN (select id from user where id <= 100))
    }

3、update Wrapper

    @Test
    void testUpdateWrapper() {
        // 将用户名中包含 a 并且 (年龄大于20或者邮箱为 null) 的用户信息修改
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        // 修改条件
        updateWrapper.like("name","a")
                .and(
                        wrapper -> wrapper.gt("age",20)
                                        .or().isNull("email")
                );
        // 修改内容
        updateWrapper.set("name","小黑").set("email","123@qq.com");
        int result = userMapper.update(null, updateWrapper);
        System.out.println("result = " + result);
//  UPDATE user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))

    }

4、condition 动态sql

a、if 判断

    @Test
    void testDynamic() {
        // 动态sql
        String name = "";
        Integer ageStar = 20;
        Integer ageEnd = 30;
    
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // com.baomidou.mybatisplus.core.toolkit.StringUtils
        if (StringUtils.isNotBlank(name)){
            // isNotBlank 某个字段不为空、不为 null,不为空白符
            wrapper.like("name",name);
        }
        if(ageStar != null){
            wrapper.ge("age",ageStar);
        }
        if (ageEnd != null){
            wrapper.le("age",ageEnd);
        }
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    
        // SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
    }

b、condition

.ge() 方法多一个条件参数 (ageStar != null)

    @Test
    void testDynamic02() {
        // 动态sql
        String name = "";
        Integer ageStar = 20;
        Integer ageEnd = 30;
        
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        
        wrapper.like(StringUtils.isNotBlank(name),"name",name)
                .ge(ageStar != null,"age",ageStar)
                .le(ageEnd != null,"age",ageEnd);

        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
        
        // SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
    }

5、lambdaQueryWrapper

    @Test
    // 这样字段名不会写错
    void testLambda() {
        String name = "";
        Integer ageStar = 20;
        Integer ageEnd = 30;
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // User::getName 表示 User 类的 name属性
        wrapper.like(StringUtils.isNotBlank(name),User::getName,name)
                .ge(ageStar != null,User::getAge,ageStar)
                .le(ageEnd != null,User::getAge,ageEnd);
    
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    
        // SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
    }

6、LambdaUpdateWrapper

    @Test
    void testLambdaUpdateWrapper() {
        // 将用户名中包含 a 并且 (年龄大于20或者邮箱为 null) 的用户信息修改
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        // 修改条件
        updateWrapper.like(User::getName,"a")
                .and(
                        wrapper -> wrapper.gt(User::getAge,20)
                                .or().isNull(User::getEmail)
                );
        // 修改内容
        updateWrapper.set(User::getName,"小黑").set(User::getEmail,"123@qq.com");
        int result = userMapper.update(null, updateWrapper);
        System.out.println("result = " + result);
        
        //  UPDATE user SET name=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
    }

五、插件

1、分页插件

a、配置

@Configuration
// 可以把 MapperScan 写在配置类里
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MPConfig {
    // 配置 mp 分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加一个内部插件
        // 设置数据库类型  mysql
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

b、测试

    @Test
    void testPage() {
        // 1:当前页起始下标,3: 每页数据条数
        // User 是需要查询的类
        Page<User> page = new Page<>(2, 3);
        // null是 wrapper参数
        userMapper.selectPage(page, null);
        // 查询出的数据
        System.out.println(page.getRecords());
        
//        ==>  Preparing: SELECT id,name,age,email FROM user LIMIT ?,?
//        ==> Parameters: 3(Long), 3(Long)
    }

2、自定义分页

a、userMapper 接口中定义方法

    /**
     * 根据年龄查询用户信息 并且分页
     * @param page  page 参数sql中用不到,但是如果想要使用分页功能第一个参数必须是Page
     * @param age
     * @return  返回值必须是 Page<T>
     * VO=view object
     */
    Page<User> selectPageVO(@Param("page") Page<User> page, @Param("age") Integer age);

b、userMapper.xml 中编写sql

    <!--Page<User> selectPageVO(@Param("page") Page<User> page, @Param("age") Integer age);-->
    <select id="selectPageVO" resultType="User">
        select id,name,age,email from user where age > #{age}
    </select>

c、测试

    @Test
    void testPageVO() {
        Integer age = 12;
        // 和内置的分页一样使用
        Page<User> page = new Page<>(2, 3);
        userMapper.selectPageVO(page,age);
        
        System.out.println(page.getRecords());
        
//        ==>  Preparing: select id,name,age,email from user where age > ? LIMIT ?,?
//        ==> Parameters: 12(Integer), 3(Long), 3(Long)
    }

3、乐观锁

a、场景

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

b、乐观锁与悲观锁

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

c、模拟冲突修改

c.1 新建表

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

c.2 添加数据

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

c.3 添加实体

package com.atguigu.mybatisplus.pojo;

import lombok.Data;

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

c.4 添加mapper

public interface ProductMapper extends BaseMapper<Product> {
}

c.5 测试

    @Test
    public void testConcurrentUpdate() {
        //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
        System.out.println("最后的结果:" + p3.getPrice());
    }

d、乐观锁实现流程

e、mybatis_plus 实现乐观锁

e.1 实体类 version 字段添加  @Version 注解

    @Version  // 标识乐观锁版本号字段
    private Integer version;

e.2 配置类添加乐观锁插件

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        // 设置数据库类型  mysql
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        // 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }

e.3 修改代码逻辑

在小王修改后 添加 if 判断:最后结果:120

    @Test
    public void testConcurrentUpdate() {
        //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);
        
        if (result2 == 0){
            // 操作失败、重试
            Product pNew = productMapper.selectById(1L);
            pNew.setPrice(pNew.getPrice() - 30);
            productMapper.updateById(pNew);
        }
        
        //最后的结果
        Product p3 = productMapper.selectById(1L);
        //价格覆盖,最后的结果:70
        System.out.println("最后的结果:" + p3.getPrice());
    }

六、通用枚举

a、数据库添加sex字段方便测试

b、创建枚举类型

@Getter、@EnumValue 

@Getter
public enum SexEnum {
    
    MALE(1,"男"),
    FEMALE(0,"女");
    @EnumValue // 此注解可以将属性的值存到数据库而不是存名称(MALE)
    private Integer sex;
    private String sexName;
    
    SexEnum(Integer sex, String sexName) {
        this.sex = sex;
        this.sexName = sexName;
    }
}

c、包扫描配置

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

d、测试

    @Test
    void testEnum() {
        User user = new User();
        user.setName("赵云");
        user.setAge(122);
        user.setSex(SexEnum.MALE);
        int result = userMapper.insert(user);
        System.out.println("result = " + result);
    }

七、代码生成器

a、引入依赖

版本要和 m-p 一致

        <!--m-p 代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- freemarker -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>

b、复制代码

根据需要修改


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

c、生成的目录

 八、多数据源

场景

新建测试环境:

创建两个库,分别为: mybatis_plus (以前的库不动) 与mybatis_plus_1 (新建),

将mybatis_plus库的 product 表移动到mybatis_plus_1库,

mybatis_plus:有 user表、mybatis_plus_1有 product 表

这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功

 1、创建数据库和表

-- 建库建表
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);


--删除原有的表 (如果有)
use mybatis_plus;
DROP TABLE IF EXISTS product;

创建完成后删除 原 mybatis_plus 数据库中的 product表

2、新建项目

注意检查创建完成后的 jdk版本,这里我把springboot版本也修改成了 2.7.12 和原项目一样

 3、引入依赖

        <!--多数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>

        
        <!-- 还需要复制 mysql、lombok、mybatis-plus-boot-starter 依赖 -->

4、配置多数据源

spring:
  # 配置数据源信息
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
      # 是否严格匹配数据源,默认false.,如果设定是false未匹配到就使用默认数据源master ,true未匹配到指定数据源时抛异常
      strict: false
      datasource:
        # master数据源
        master:   # 这个名字和上面的要一样
          url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 1234
        # 从数据源
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 1234

5、创建service

目录:与原项目一样

 a、创建 userservice

@DS("")

@Service
@DS("master") //指定所操作的数据源        user 表在 master 数据源中
public class UserServiceImpl
        extends ServiceImpl<UserMapper, User>
        implements UserService {
    
}

b、创建 productService

@Service
@DS("slave_1")  //指定所操作的数据源     product 表在 slave_1 数据源中
public class ProductServiceImpl
        extends ServiceImpl<ProductMapper, Product>
        implements IProductService   {
}

6、测试成功

    @Autowired
    private UserService userService;
    @Autowired
    private IProductService productService;
    
    @Test
    void contextLoads() {
        // service 层的查询方法是以 get 开头、mapper是以 select开头
        System.out.println("userService.getById(1) = " + userService.getById(1));
        System.out.println("productService.getById(1) = " + productService.getById(1));
//        userService.getById(1) = User(id=1, name=Jone, age=18, email=test1@baomidou.com, sex=null)
//        productService.getById(1) = Product(id=1, name=外星人笔记本, price=100, version=0)
    }

九、MyBatisX 插件

MybatisX快速开发插件 | MyBatis-Plus (baomidou.com)

1、安装

 2、使用

a、idea连接数据

b、点击表右键—>MybatisX-Generator 

c、配置

生成的代码:

 3、CRUD

MybatisX 会根据我们写的方法名称自动创建 sql 语句

但是方法名称要见名知意,查以select开头、删以delete... 如下的智能提示

 组合键:alt + enter 、选择第二个

 创建号的方法: 如果是自己随意写的方法名会报错没有 sql 文件

 自动写的sql :

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值