MyBatis Plus

干掉SQL!!!

是一个MyBatis的增强工具,在MyBatis基础上只做增强,不做改变,可以在不编写SQL

语句的情况下,对单表完成各种操作

官网:https://baomidou.com/

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

mybatis plus提供了一个通用的增删改查的Mapper,可以通过自定义Mapper接口继承BaseMapper<T>完成基础的增删改查

BaseMapper

使用:

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

没有复杂条件的增删改:

@Test
void insert() {
    System.out.println("\n\n\n\n插入前");
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));


    User user = new User();
    user.setName("admin3");
    user.setEmail("6666@qqq.qqq");
    user.setAge(11);
    System.out.println("mapper.insert(user) = " + mapper.insert(user));


    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    System.out.println("插入后\n\n\n\n");
}

@Test
void updateByPrimaryKey() {
    System.out.println("\n\n\n\n修改前");
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));


    User user = new User();
    user.setId(1580877194695786497L);
    user.setName("admin");
    user.setEmail("6666@qqq.qqq");
    user.setAge(101);
    System.out.println("mapper.updateById(user) = " + mapper.updateById(user));


    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    System.out.println("修改后\n\n\n\n");
}

@Test
void deleteByPrimaryKey() {
    System.out.println("\n\n\n\n删除前");
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));


    User user = new User();
    user.setId(1580878736567980034L);
    user.setName("admin");
    user.setEmail("6666@qqq.qqq");
    user.setAge(101);

    Map<String, Object> map = new HashMap<>();
    map.put("id", 1580879257869725698L);
    map.put("name", "admin3");
    // 通过Map进行删除
    mapper.deleteByMap(map);

    // 通过对象中的主键进行删除
    mapper.deleteById(user);
    // 直接指定id
    System.out.println("mapper.deleteById(1580877194695786497L) = " + mapper.deleteById(1580877194695786497L));


    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    System.out.println("删除后\n\n\n\n");
}

@Test
void deleteBatch() {
    System.out.println("\n\n\n\n批量删除前");
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));

    System.out.println("mapper.deleteBatchIds(List.of()) = "
            + mapper.deleteBatchIds(List.of(1580880626341011458L, 1580880653503369218L, 1580880677410893825L,
            1580880702023098369L, 1580880729009229825L, 1580880758923018242L)));

    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    System.out.println("批量删除后\n\n\n\n");
}

没有复杂条件的查询

@Test
void simpleSelect() {
    System.out.println("mapper.selectById(1) = " + mapper.selectById(1));
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    System.out.println("mapper.selectCount(null) = " + mapper.selectCount(null));
    System.out.println("mapper.selectMaps(null) = " + mapper.selectMaps(null));
    System.out.println("mapper.selectObjs(null) = " + mapper.selectObjs(null));
    System.out.println("mapper.selectBatchIds(List.of(2, 3, 5)) = " + mapper.selectBatchIds(List.of(2, 3, 5)));
    HashMap<String, Object> columnMap = new HashMap<>();
    columnMap.put("name", "Sandy");
    System.out.println("mapper.selectByMap(columnMap) = " + mapper.selectByMap(columnMap));
    System.out.println("mapper.exists(null) = " + mapper.exists(null));
}

IService

一个通用的Service接口,MyBatis Plus提供了一个实现类为:ServiceImpl<M extends BaseMapper<T>, T>

例如:

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

}

Service简单的通用增删改查的使用,MapperService对应关系为:

  • get 查询单行
  • remove 删除
  • list 查询集合
@Test
void simpleSelect() {
    System.out.println("service.count() = " + service.count());
    System.out.println("service.list() = " + service.list());
    System.out.println("service.listMaps() = " + service.listMaps());
    System.out.println("service.listObjs() = " + service.listObjs());
    System.out.println("service.listByIds(List.of(1, 3, 5)) = " + service.listByIds(List.of(1, 3, 5)));
    System.out.println("service.getById(1580903310957416450L) = " + service.getById(1580903310957416450L));
}

@Test
void insert() {
    User user = new User();
    user.setName("姓名");
    user.setEmail("name@kjk.aa");
    user.setAge(56);
    service.save(user);
}

@Test
void insertBatch() {
    System.out.println("service.list() = " + service.list());
    service.saveBatch(List.of(new User(null, "ming", "a@a", 11),
            new User(null, "afasd", "g@a", 152), new User(null, "cfe", "a@a", 211)));
    System.out.println("service.list() = " + service.list());
}

// 第二个参数为数量,从0开始
@Test
void insertBatchCount() {
    System.out.println("service.list() = " + service.list());
    service.saveBatch(List.of(new User(null, "1111", "a@a", 11),
            new User(null, "2222", "g@a", 152), new User(null, "3333", "a@a", 211)), 1);
    System.out.println("service.list() = " + service.list());
}

// 根据主键进行插入或者更新某条记录,如果数据库中不存在这个主键,那么将会进行插入;如果存在这个主键,就进行更新这条记录
@Test
void insertUpdate() {
    System.out.println("service.list() = " + service.list());
    User user = new User(9999L, "9999", "g@a", 152);
    service.saveOrUpdate(user);
    System.out.println("service.list() = " + service.list());
}

@Test
void insertUpdateBatch() {
    System.out.println("service.list() = " + service.list());
    service.saveOrUpdateBatch(List.of(new User(1580903310957416450L, "姓名-更新", "name@kjk.aa", 56),
            new User(1580903370369757185L, "姓名-sxx", "name@kjk.aa", 56)));
    System.out.println("service.list() = " + service.list());
}

@Test
void update() {
    System.out.println("service.getById(1580903310957416450L) = " + service.getById(1580903310957416450L));
    service.updateById(new User(1580903310957416450L, "姓名-更新-更新", "name@kjk.aa", 56));
    System.out.println("service.getById(1580903310957416450L) = " + service.getById(1580903310957416450L));
}

@Test
void updateOther() {
    System.out.println("service.list() = " + service.list());
    service.updateBatchById(List.of(new User(1580903310957416450L, "姓名0-更新", "name@kjk.aa", 56),
            new User(1580903370369757185L, "姓名1-sxx", "name@kjk.aa", 56)));
    System.out.println("service.list() = " + service.list());
}

@Test
void delete() {
    System.out.println("service.list() = " + service.list());
    service.removeById(1580906572498501634L);
    System.out.println("service.list() = " + service.list());
    service.removeById(new User(1580905782966972420L, null, null, 0));
    System.out.println("service.list() = " + service.list());
    service.removeByIds(List.of(1580905782966972419L, 1580905782966972418L));
    System.out.println("service.list() = " + service.list());
    service.removeBatchByIds(List.of(1580905523557658625L, 1580905523549270018L));
    System.out.println("service.list() = " + service.list());
    HashMap<String, Object> map = new HashMap<>();
    map.put("id", 1580905523519909890L);
    service.removeByMap(map);
    System.out.println("service.list() = " + service.list());
}

配置表名前缀

如果表名的前缀都一样时,可以添加表名的前缀,例如:

mybatis-plus:
  global-config:
    db-config:
      table-prefix: 前缀 

常用注解

@TableName("表名")在类名和表名不一致的情况下,可以在类名上添加此注解指定表名:

@TableId("id名称")可以在属性上指定id所在的字段,在id和表中的id不一致时,可以在括号中,@TableId(type = IdType.XXX)可以指定插入主键值的生成策略,默认为雪花算法:

  • AUTO自动递增

  • ASSIGN_ID为雪花算法

  • NONE为默认值,将会使用在配置文件中指定的全局配置

  • 可以在yml中配置全局的生成策略:

    • mybatis-plus:
        global-config:
          db-config:
            table-prefix: t_
            id-type: auto
      

@TableField("属性名称")如果类中的字段和数据库中的字段不一致时,可以通过在属性上添加这个注解并指定

逻辑删除

  • 添加一个数据库字段,类型为integer或者Boolean,设置好默认值为0
  • 在实体类上添加这个属性,并添加@TableLogic注解

这时候再调用删除方法时,只是相当于把这个字段的值设置为了1

条件构造器 wrapper

wrapper中文为包装

类的结构:

image-20221017193508487

QueryWrapper的使用:

    @Test
    void test() {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
//         小于
//        queryWrapper.lt("id", 3);

//        小于等于
//        queryWrapper.le("id", 3);

//        大于
//        queryWrapper.gt("id", 2);

//        大于等于
//        queryWrapper.ge("id", 3);

//        等于
//        queryWrapper.eq("id", 3);

//        不等于
//        queryWrapper.ne("id", 2);

//        联合起来写,代表大于2且小于5,且不等于3
//        queryWrapper.ge("id", 2)
//                        .le("id", 5)
//                                .ne("id", 3);

//        between条件
//        queryWrapper.between("id", 2, 4);

//        like条件
//        queryWrapper.like("name", "J%");

//        not like
//        queryWrapper.notLike("name", "%a%");

//        查询某几个字段
//        queryWrapper.select("id", "name");

        /*
        也可以使用group by,select中也可以写别名
        */
//        queryWrapper.groupBy("age");
//        queryWrapper.select("age 年龄", "count(*) 数量");
//        queryWrapper.select("age as 年龄", "count(*) as 数量");

//        in的用法
//        queryWrapper.in("id", 2, 3, 4);
//        queryWrapper.in("id", List.of(2, 3, 4));

//        not in
//        queryWrapper.notIn("id", 2, 3, 5);
//        queryWrapper.notIn("id", List.of(2, 3, 5));

//        排序
//        正序
//        queryWrapper.orderBy(true, true, "id");
//        queryWrapper.orderByAsc("id");
//        倒序
//        queryWrapper.orderBy(true, true, "id");
//        queryWrapper.orderByDesc("id");

//        SELECT id, name, email, age, del FROM user WHERE del = 0 AND(id >= ? OR age >= ? AND age <= ?)
//        queryWrapper.ge("id", 5)
//                .or()
//                .ge("age", 25)
//                .le("age", 100);

        
//        SELECT id,name,email,age,del FROM user WHERE del=0 AND (id >= ? OR (age >= ? AND age <= ?))
//        queryWrapper.ge("id", 5)
//                .or(wrapper -> wrapper.ge("age", 25).le("age", 100));
        
//                queryWrapper.isNull("列名").isNotNull("列名")
        System.out.println("mapper.selectList(queryWrapper) = " + mapper.selectList(queryWrapper));
    }

queryWrapper也可以用于删除:

@Test
void delete() {
    QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
    queryWrapper.ge("age", 100);
    mapper.delete(queryWrapper);
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}

也可以进行子查询:

    @Test
    void subQuery() {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
//        SELECT id,name,email,age,del FROM user WHERE del=0 AND (id IN (select id where id > 100))
        queryWrapper.inSql("id", "select id where id > 100");
        mapper.selectList(queryWrapper);
    }

可以使用UpdateWrapper进行更新:

@Test
void update() {
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
    updateWrapper.set("age", 888);
    updateWrapper.eq("age", 999);
    mapper.update(null, updateWrapper);
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}

条件组装

例如现在有这么个需求:

  • 如果名字不为空,会按照名称进行查询
  • 如果最小年龄不为空,同时也会将这个条件加进去
  • 如果最大年龄不会空,也会将这个条件加进去

根据这个条件,代码可以表示为:

@Test
void condition() {
    QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
    String name = null;
    Integer minAge = 10, maxAge = 150;
    if(name != null) {
        queryWrapper.eq("name", name);
    }
    if(minAge != null) {
        queryWrapper.ge("age", minAge);
    }
    if(maxAge != null) {
        queryWrapper.le("age", maxAge);
    }
    mapper.selectList(queryWrapper);
}

Mybatis plus提供了条件组装,只有条件为true时才会组装这条sql

    @Test
    void condition() {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        String name = "名称";
        Integer minAge = 10, maxAge = 1500;
        queryWrapper.eq(name != null, "name", name);
        queryWrapper.ge(minAge != null, "age", minAge);
        queryWrapper.le(maxAge != null, "age", maxAge);
        mapper.selectList(queryWrapper);
    }

LambdaQueryWrapper

在之前的条件中,有许多手打的字段名,手打的字段名很容易出错,这个使用可以使用LambdaQueryWrapper<T>,通常每个方法中都带有SFunction<T, ?> column类型的参数,在这个参数中可以使用实体类的Lambda表达式,例如:

@Test
void lambdaQueryWrapper() {
    LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
    String name = "名称";
    Integer minAge = 10, maxAge = 1500;
    queryWrapper.select(UserInfo::getId, UserInfo::getEmail);
    queryWrapper.eq(name != null, UserInfo::getName, name);
    queryWrapper.ge(minAge != null, UserInfo::getAge, minAge);
    queryWrapper.le(maxAge != null, UserInfo::getAge, maxAge);
    mapper.selectList(queryWrapper);
}

LambdaUpdateWrapper

同样也是使用Lambda表达式来控制列:

@Test
void lambdaUpdateWrapper() {
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.eq(UserInfo::getAge, 888);
    updateWrapper.set(UserInfo::getAge, 999);
    mapper.update(null, updateWrapper);
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}

分页

需要配置分页插件:

  • 新建配置类,配置类中提供返回值为MyBatisPlusInterceptor的方法
  • 方法中实例化MyBatisPlusInterceptor的对象
  • 并调用addInnerIntercepter的方法添加PaginationInnerInterceptor(DbType.数据库类型)进行添加配置插件
  • 最后返回这个对象

使用:

  • 通过调用selectPage(page, queryWrapper)等一系列的方法进行使用

  • 需要提供Page<>实例

    • 在实例化时,指定页数和每页多少个元素
    • 当页数为0或者1时,查询出来的效果一样,两者执行的SQL语句也都一样,都是select .. from ... limit 每页个数
  • 最后查出来的数据将会放到Page<>实例中

  • 可以通过Spring Boot中集成的Jackson的在测试时查看Page中的内容

    • 需要注入ObjectMapper,通过writeValueAsString(page)可以查看具体的内容
  • @Test
    void pages() throws JsonProcessingException {
        mapper.selectList(null);
        Page<UserInfo> page = new Page<>(1, 2);
        mapper.selectPage(page, null);
        System.out.println("page = " + page);
        // 页数
        System.out.println("page.getPages() = " + page.getPages());
        // 当前页
        System.out.println("page.getCurrent() = " + page.getCurrent());
        // 每页大小
        System.out.println("page.getSize() = " + page.getSize());
        // 总的元素数
        System.out.println("page.getTotal() = " + page.getTotal());
        // 获取查出来的数据的List
        System.out.println("page.getRecords() = " + page.getRecords());
        // 是否有下一页
        System.out.println("page.hasNext() = " + page.hasNext());
        // 是否有上一页
        System.out.println("page.hasPrevious() = " + page.hasPrevious());
        System.out.println("objectMapper.writeValueAsString(page) = " + objectMapper.writeValueAsString(page));
    }
    

Page中的内容为:

  • {
        "records": [
            {
                "id": 1,
                "name": "Jone",
                "email": "test1@baomidou.com",
                "age": 18,
                "del": false
            },
            {
                "id": 2,
                "name": "Jack",
                "email": "test2@baomidou.com",
                "age": 20,
                "del": false
            }
        ],
        "total": 11,
        "size": 2,
        "current": 1,
        "orders": [],
        "optimizeCountSql": true,
        "searchCount": true,
        "countId": null,
        "maxLimit": null,
        "pages": 6
    }
    

自定义SQL使用分页插件

修改Mapper接口:

  • Mapper接口中的返回值设置为Page<类型>
  • 第一个参数设置为Page<类型> page
  • 其他地方不变
  • 需要注意的是,自己写的sql语句末尾一定不要添加;,因为分页是在sql语句末尾进行追加的sql语句
@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
    Page<UserInfo> selectByAge(Page<UserInfo> page, Integer age);
}
<select id="selectByAge" resultType="com.xiaoxu.learn.bean.UserInfo">
    select id, name, age, email from user where age >= #{age} and del = 0
</select>
@Test
void consumerPage() throws JsonProcessingException {
    Page<UserInfo> page = new Page<>(2, 3);
    mapper.selectByAge(page, 10);
    System.out.println("objectMapper.writeValueAsString(page) = " + objectMapper.writeValueAsString(page));
}

乐观锁

需要配置插件:

mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

在数据库表增加一个整型的字段用来表示当前这条记录的版本号

在实体类上相关字段添加@Version注解

例如:

  • 有一件商品,价格为100元,第一个用户和第二个用户同时查询它的价格,第一个用户将其价格减少10元,第二个用户将其价格增加20元。理想情况下得到的最后的价格为110

  • 在没有锁的情况下两个用户同时查,此时两个用户得到的价格都是100,第一个用户减少10元后,此时价格为90,用户2此时存储的价格仍为100,这时候增加价格是,会从100的基础上增加20最后结果为120,与预期的结果不一样

  • 引入乐观锁之后,将会为每条记录带有一个版本号,更新时会附上版本号,因此不会出现这个问题

  • 更新时所执行的sql语句为:UPDATE commodity SET name=?, price=?, version=? WHERE id=? AND version=?

  • @Test
    void lock() {
        Commodity commodity = mapper.selectById(1);
        System.out.println("commodity = " + commodity);
        Commodity commodity2 = mapper.selectById(1);
    
        // 使用commodity更新商品
        commodity.setPrice(commodity.getPrice() - 10);
        mapper.updateById(commodity);
    
        System.out.println("mapper.selectById(1) = " + mapper.selectById(1));
    
        // 再使用commodity2更新商品
        commodity2.setPrice(commodity2.getPrice() + 20);
        mapper.updateById(commodity2);
    
        System.out.println("mapper.selectById(1) = " + mapper.selectById(1));
    
    }
    
  • @Data
    @ToString
    public class Commodity {
        private Integer id;
        private String name;
        private Double price;
        @Version
        private Integer version;
    }
    

带上乐观锁之后,第二个用户修改价格时将不会更新数据库

枚举

有的属性值可能在实体类中使用的是枚举类型的,但枚举类型默认的值是一个String的字符串,如果数据库中使用的是整型或者其他数据类型与之对应,那么,将无法正常查询或者修改

MyBatis Plus中提供了@EnumValue注解,用来指定插入数据的枚举类上相关字段具体的值是哪一个:

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@Getter
public enum Sex {
    /**
     *
     */
    MALE(1, "男"),
    FEMALE(2, "女");
    @EnumValue
    private final int flag;
    private final String name;

    Sex(int flag, String name) {
        this.flag = flag;
        this.name = name;
    }
}
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "user")
public class UserInfo {
    private Long id;
    private String name, email;
    private int age;
    @TableLogic
    private boolean del;

    private Sex sex;

    public UserInfo(Long id, String name, String email, int age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }
}
@Test
void enumTest() {
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
    UserInfo user = new UserInfo(null, "人", "aaa@ewwe.co", 12);
    user.setSex(Sex.FEMALE);
    mapper.insert(user);
    System.out.println("mapper.selectList(null) = " + mapper.selectList(null));
}

多数据源

可以使用:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

作为多数据源

文档:https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611

Q.E.D.


念念不忘,必有回响。