MyBatis-Plus实战

环境准备

建库建表

#创建用户表
CREATE TABLE user (
    id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键',
    name VARCHAR(30) DEFAULT NULL COMMENT '姓名',
    age INT(11) DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
    manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id',
    create_time DATETIME DEFAULT NULL COMMENT '创建时间',
	update_time DATETIME DEFAULT NULL COMMENT '修改时间',
	version INT(11) DEFAULT '1' COMMENT '版本',
	deleted INT(1) DEFAULT '0' COMMENT '逻辑删除标识(0.未删除,1.已删除)',
    CONSTRAINT manager_fk FOREIGN KEY (manager_id)
        REFERENCES user (id)
)  ENGINE=INNODB CHARSET=UTF8;

#初始化数据:
INSERT INTO user (id, name, age, email, manager_id, create_time)
VALUES 
(1, '苦逼胡', 40, '[email protected]', NULL, '2019-11-23 14:20:20'),
(2, '搞笑梁', 25, '[email protected]', 1, '2019-10-01 11:12:22'),
(3, '美丽孙', 28, '[email protected]', 1, '2019-09-28 08:31:16'),
(4, '隔壁老汪', 25, '[email protected]', 6, '2019-10-01 09:15:15'),
(5, '老实成', 30, '[email protected]', 6, '2019-01-01 09:48:16'),
(6, '程煦妧', 30, '[email protected]', 1, '2019-01-01 09:48:17');

引入依赖

      基于Springboot进行开发。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--使用lombok简化开发,需要额外安装lombok插件-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

实体类

import lombok.Data;
import java.time.LocalDateTime;
/**
 * @author CatWing
 */
@Data
public class User {
    /**
     * 主键
     */
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 上级ID
     */
    private Long managerId;
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
    /**
     * 版本号
     */
    private Integer version;
    /**
     * 逻辑删除(0.未删除,1.已删除)
     */
    private Integer deleted;
}

Mapper接口

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import edu.xpu.hcp.entity.User;

public interface UserMapper{

}

配置

      配置数据库驱动,url等。

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf-8
    username: name
    password: pwd

logging:
  level:
    root: warn
    包名: trace

mybatis-plus:
  type-aliases-package: 实体类包名
  mapper-locations: classpath:mapper/*.xml

      在启动类上配置@MapperScan 注解,扫描Mapper接口。

@SpringBootApplication
@MapperScan(basePackageClasses = {UserMapper.class})
public class MybatisPlusDemoApplication {...}

小试牛刀

      MyBatis-Plus为开发人员提供了通用的CRUD方法,免去我们开发大量简单,乏味的代码。使用这些通用方法很简单,只需要让Mapper接口继承BaseMapper<T>即可。

public interface UserMapper extends BaseMapper<User> {

}

      通用Mapper提供了许多方法,现在使用selectList方法查询全部数据吧!

@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisPlusDemoApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void selectAll() {
        System.out.println(("----- test selectAll method ------"));
        List<User> userList = userMapper.selectList(null);
        Assert.assertEquals(5, userList.size());
        userList.forEach(System.out::println);
    }

}

      BaseMapper提供的方法如下所示,这些方法在后续全部会进行介绍。

int insert(T entity);//插入一条记录
int deleteById(Serializable id);//根据 ID 删除
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);//根据 columnMap 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);//根据 entity 条件,删除记录
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);//删除(根据ID 批量删除)
int updateById(@Param(Constants.ENTITY) T entity);//根据 ID 修改
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);//根据 whereEntity 条件,更新记录
T selectById(Serializable id);//根据 ID 查询
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);//查询(根据ID 批量查询)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);//查询(根据 columnMap 条件)
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 entity 条件,查询一条记录
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 Wrapper 条件,查询总记录数
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 entity 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 Wrapper 条件,查询全部记录
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 Wrapper 条件,查询全部记录
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 entity 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);//根据 Wrapper 条件,查询全部记录(并翻页)

从传统手艺到MP

传统手艺

传统手艺

如今MP

如今MP

CRUD实战

插入

@Test
public void insertUser(){
    User user = new User();
    user.setId(6L);
    user.setName("程旭辕");
    user.setAge(20);
    user.setEmail("[email protected]");
    user.setManagerId(1L);
    user.setCreateTime(LocalDateTime.now());
    int rows = userMapper.insert(user);
    System.out.println("affect rows : "+rows);
}

      查看日志我们可以发现插入的详细SQL语句,由于user对象没有设置updateTime等属性所以在插入语句的列名中并没有出现,这是MP处理的结果。而且我们可以发现MP默认数据库使用下划线分隔单词的方式对应实体中的驼峰命名

2019-11-23 20:47:27.886 DEBUG 59020 --- [           main] edu.xpu.hcp.mapper.UserMapper.insert     : ==>  Preparing: INSERT INTO user ( id, create_time, name, manager_id, email, age ) VALUES ( ?, ?, ?, ?, ?, ? )

      再次插入一个对象,不同的是没有设置id,查看日志。

@Test
public void insertUser(){
    User user = new User();
    user.setName("程煦");
    user.setAge(23);
    user.setEmail("[email protected]");
    user.setManagerId(2L);
    user.setCreateTime(LocalDateTime.now());
    int rows = userMapper.insert(user);
    System.out.println("affect rows : "+rows);
}

      在上一段测试中不是说了没设置的话语句中不会出现相关列名吗?这怎么出现了呢。这是由于MP默认对id属性默认使用基于雪花算法的自增id进行填充。

2019-11-23 20:51:50.047 DEBUG 58588 --- [           main] edu.xpu.hcp.mapper.UserMapper.insert     : ==>  Preparing: INSERT INTO user ( id, create_time, name, manager_id, email, age ) VALUES ( ?, ?, ?, ?, ?, ? ) 
2019-11-23 20:51:50.072 DEBUG 58588 --- [           main] edu.xpu.hcp.mapper.UserMapper.insert     : ==> Parameters: 1198222622966423554(Long), 2019-11-23T20:51:49.424(LocalDateTime), 程煦(String), 2(Long), [email protected](String), 23(Integer)

常用注解

@TableName

      一般情况下实体名和数据库表明是根据驼峰命名规则相对应的,如:User类对user表,SysUser类对sys_user表。有时候也许我们需要修改数据库名(或类名),如果按照默认的命名规则肯定是对应不上了,而我们又不想再去修改代码了,怎么办?MP提供了@TableName注解指定类和表的对应关系。

@TableName("sys_user")
public class User {...}

@TableId

      在上文测试插入中我们没有指定id值,由MP的默认规则为我们自动填充了主键id的值。注意,这仅仅是在实体类中主键名为id下才起作用,如果将id命名为诸如userId(数据库列表为user_id),此时MP就无法为我们自动填充了。
      如果我们不想更改为id,而确实需要命名成其他字段名难道不可以吗?Luckly!MP为我们提供了@TableId注解,使用它来指定实体中的主键。

/**
 * 主键
 */
@TableId
private Long userId;

      与@TableName功能相类似,列名和实体名也可以不同,我们也可以进行指定。

/**
 * 主键,userId对应着数据库列名id
 */
@TableId(value = "id")
private Long userId;
  • TableId含义
属性 类型 required 默认值 含义
value String “” 数据库对应字段名
type Enum IdType.NONE 主键类型
  • IdType含义
含义
AUTO 数据库自增
INPUT 自行输入
ID_WORKER 分布式全局唯一ID 长整型类型
UUID 32位UUID字符串
NONE 未设置主键类型(将跟随全局)
ID_WORKER_STR 分布式全局唯一ID 字符串类型

@TableField

      @TableField用于非主键字段,同@TableName一样我们可以指定属性名所对应数据库字段名的值。

/**
 * 姓名
 */
@TableField(value = "user_name")
private String name;
排除非表字段

      在实际编程场景中,实体类中的属性名并不是都存在于数据库表中。下面为User类添加note属性。

/**
 * 备注(非表字段)
 */
private String note;

      执行小试牛刀中的selectAll方法。由于MP为属性和字段做了一一映射,新建的note属性不在user表字段中,所有一定会报错。

org.springframework.jdbc.BadSqlGrammarException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column 'note' in 'field list'

      为此,我们需要排除这些非表字段。MP提供了三种排除非表字段的方法:

  • 使用transient关键字
/**
 * 备注(非表字段)
 */
private transient String note;
  • 标注为静态变量
/**
 * 备注(非表字段)
 */
private static String note;

      (使用Lombok插件注意)由于lombok插件不会为静态变量创建gettersetter方法所以我们需要手动创建。

  • exist属性
          使用@TableField注解中的exist属性进行设置。
/**
 * 备注(非表字段)
 */
 @TableField(exist=false)
private String note;

查询

简单查询

/**
 * 通过id进行查询
 */
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}
/**
 * 通过 id 集合进行查询
 */
@Test
public void testSelectBatchIds(){
    /* selectBatchIds 方法所传递的集合不能为null或empty */
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L, 4L));
    users.forEach(System.out::println);
}
/**
 * 使用map进行查询
 */
@Test
public void testSelectByMap(){
    Map<String,Object> map = new HashMap<>();
    map.put("name","美丽孙");
    map.put("age",18);
    /**
     * 注意map是表字段 map 对象,所以key代表的是数据库中的字段名,而不是实体属性名
     */
    List<User> users = userMapper.selectByMap(map);
    /**
     * 生成SQL:SELECT id,deleted,create_time,name,update_time,manager_id,version,email,age FROM user WHERE name = ? AND age = ?
     */
    users.forEach(System.out::println);
}

条件查询

      在使用条件查询前我们需要知道条件构造器,它是用于构建查询条件的类,抽象类Wrapper是所以条件构造器的父类,我们首先使用它的子类QueryWrapper完成条件的构造。下面将根据一系列的题目需求完成条件构造器的学习。

  • 名字中包含孙并且年龄小于20
@Test
public void test1(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * LIKE '%值%'
     * like(R column, Object val)
     * column:使用数据库字段名,而不是实体属性名,其他方法亦如此。
     */
    queryWrapper.like("name","孙").lt("age",20);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 名字中包含孙并且龄大于等于10且小于等于25并且email不为空
@Test
public void test2(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like("name","孙").between("age",10,25).isNotNull("email");
    //method2: queryWrapper.like("name","孙").ge("age",10).le("age",25).isNotNull("email");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 名字为程姓或者年龄大于等于25,按照年龄降序排列,年龄相同按照id升序排列
@Test
public void test3(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * like: '%值%'
     * likeLeft: '%值'
     * lifeRight: '值%'
     */
    queryWrapper.likeRight("name","程").or().ge("age",25)
            .orderBy(true,false,"age")
            .orderBy(true,true,"id");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 创建日期为2019年10月1日并且直属上级为名字为程姓
@Test
public void test4(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * apply(String applySql, Object... params)
     * apply方法可用于数据库函数动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有。
     * apply("date_format(create_time,'%Y-%m-%d') = '2019-10-01'")会有SQL注入风险
     */
    queryWrapper.apply("date_format(create_time,'%Y-%m-%d') = {0}", "2019-10-01").inSql("manager_id","select id from user where name like '程%'");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 名字为程姓并且(年龄小于30或邮箱不为空)
@Test
public void test5(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.likeRight("name","程").and(qw->qw.lt("age",30).or().isNotNull("email"));
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 名字为程姓或者(年龄小于40并且年龄大于20并且邮箱不为空)
@Test
public void test6(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.likeRight("name","程").or(qw->qw.lt("age",40).gt("age",20).isNotNull("email"));
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • (年龄小于40或邮箱不为空)并且名字为程姓
@Test
public void test7(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * nested 用于嵌套
     */
    queryWrapper.nested(qw->qw.lt("age",40).or().isNotNull("email")).likeRight("name","程");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 年龄为18、20、25、30
@Test
public void test8(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.in("age",Arrays.asList(18,20,25,30));
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 只返回满足条件的其中一条语句即可
@Test
public void test9(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * last 方法无视优化规则直接拼接到 sql 的最后
     * 注意,只能调用一次,多次调用以最后一次为准,并且有sql注入的风险,谨慎使用
     */
    queryWrapper.in("age",Arrays.asList(18,20,25,30)).last("limit 1");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • select中字段不全部出现

      在上面的测试中不难发现全部字段都给查询出来了,可是很多情况下我们只需要部分字段。

  1. 名字中包含孙并且年龄小于20(只需要id,name字段)
@Test
public void test10(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("id","name").like("name","孙").lt("age",20);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  1. 名字中包含孙并且年龄小于20(排除age,email字段)
@Test
public void test11(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * euqals中填写的是列名
     */
    queryWrapper.select(User.class,user->!user.getColumn().equals("age")&&!user.getColumn().equals("email"));
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • condition的作用

      如果我们观察仔细会发现所有的方法都是调用了另一个同名方法,只是另外一个方法的第一个参数是boolean condition,它表示该条件是否加入最后生成的sql中,如果置为false,则最后生成的代码中没有此条件。使用场景:多条件查询时可以根据是否传递该属性值来决定SQL的生成。

@Test
public void test12(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * queryWrapper.likeRight("name","程").gt("age",10);
     * 生成SQL:WHERE (name LIKE ? AND age > ?)
     * queryWrapper.likeRight(false,"name","程").gt("age",10);
     * 生成SQL:WHERE (age > ?) 
     */
    queryWrapper.likeRight(false,"name","程").gt("age",10);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}
  • 实体作为条件构造器构造方法的参数

      在上面使用的条件构造器中我们使用的是无参构造方法,MP允许我们传递实体对象给条件构造器,其中实体对象不为null的属性将作为where条件,默认使用等值比较符。注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为。都会出现在where中

@Test
public void test13(){
    User user = new User();
    user.setName("老实成");
    user.setAge(18);
    QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
    /**
     * 没使用其他API方法情况下
     * 生成SQL:WHERE name=? AND age=?
     * 再加上queryWrapper.likeRight("name","程").gt("age",10);
     * 生成SQL:WHERE name=? AND age=? AND (name LIKE ? AND age > ?) 
     */
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

      传入实体对象构造的where条件默认是等值比较的,这恐怕有时候不符合我们的要求,比如name属性我们希望使用like,怎么改变呢?现在需要我们再次使用@TableField注解了,在name上标注@TableField(condition = SqlCondition.LIKE)。改变默认比较条件。现在生成的SQL为:WHERE name LIKE CONCAT('%',?,'%') AND age=?

/**
 * SQL 比较条件常量定义类
 */
public class SqlCondition {
    /**
     * 等于
     */
    public static final String EQUAL = "%s=#{%s}";
    /**
     * 不等于
     */
    public static final String NOT_EQUAL = "%s&lt;&gt;#{%s}";
    /**
     * % 两边 %
     */
    public static final String LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')";
    /**
     * % 左
     */
    public static final String LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})";
    /**
     * 右 %
     */
    public static final String LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";
}

      如果这些条件都不符合我们的要求就自己写吧!只是String类型而已。

  • allEq

第一种allEq方法:

allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
/**
 * params : key为数据库字段名,value为字段值
 * null2IsNull : 为true则在map的value为null时调用 isNull 方法,为false时则忽略value为null的字段
 */
@Test
public void test14(){
    Map<String,Object> map = new HashMap<>();
    map.put("name","老实成");
    map.put("manager_id",6);
    map.put("age",null);
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * 不排除map中value为null的属性
     * WHERE (manager_id = ? AND name = ? AND age IS NULL)
     * 排除map中value为nall的属性:allEq(map,false);
     * WHERE (manager_id = ? AND name = ?)
     */
    queryWrapper.allEq(map,false);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

第二种allEq方法:

allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) 
/**
 * filter : 过滤函数,是否允许字段传入比对条件中
 * params 与 null2IsNull : 同上
 */
@Test
public void test15(){
    Map<String,Object> map = new HashMap<>();
    map.put("name","老实成");
    map.put("manager_id",6);
    map.put("age",null);
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * 过滤掉name字段:allEq((k,v)->!k.equals("name"),map,false);
     * WHERE (manager_id = ?)
     *
     */
    queryWrapper.allEq((k,v)->k.equals("name"),map,false);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

其他查询方法

  • selectMaps

      此方法查询出来的对象使用map封装。

@Test
public void test16(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    List<Map<String, Object>> users = userMapper.selectMaps(queryWrapper);
    System.out.println(users);
}
/**
 * 查询结果:
 * [
 * {deleted=0, create_time=2019-11-23 14:20:20.0, name=苦逼胡, id=1, version=1, [email protected], age=3}, 
 * {deleted=0, create_time=2019-10-01 11:12:22.0, manager_id=1, name=搞笑梁, id=2, version=1, [email protected], age=108}, 
 * {deleted=0, create_time=2019-09-28 08:31:16.0, manager_id=1, name=美丽孙, id=3, version=1, [email protected], age=18}, 
 * {deleted=0, create_time=2019-10-01 09:15:15.0, manager_id=6, name=隔壁老汪, id=4, version=1, [email protected], age=25}, 
 * {deleted=0, create_time=2019-01-01 09:48:16.0, manager_id=6, name=老实成, id=5, version=1, [email protected], age=30}, 
 * {deleted=0, create_time=2019-11-23 21:27:14.0, manager_id=2, name=程煦妧, id=6, version=1, [email protected], age=30}
 * ]
 */

      似乎和使用selectList方法没区别呀!不对,map中没有值为null的属性,这就是它的使用场景了。在使用selectList时我们可以发现返回结果里面许多属性值为null,一点也不雅观,这时候就需要我们selectMaps出场了。

@Test
public void test17(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("id","name");
    List<Map<String, Object>> users = userMapper.selectMaps(queryWrapper);
    System.out.println(users);
    /**
     * 查询结果:
     * [
     * {name=苦逼胡, id=1}, 
     * {name=搞笑梁, id=2}, 
     * {name=美丽孙, id=3}, 
     * {name=隔壁老汪, id=4}, 
     * {name=老实成, id=5}, 
     * {name=程煦妧, id=6}
     * ]
     */
}

      消除值为null的属性只是此方法的一个使用场景。问:如果查询的结果没有对应的实体类怎么办?不可能重新再建吧!这就是selectMaps的另外一个使用场景。

@Test
public void test18(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("avg(age) avg_age","min(age) min_age","max(age) max_age")
            .groupBy("manager_id").having("sum(age) < {0}",500);
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    System.out.println(maps);
    /**
     * [
     * {max_age=3, avg_age=3.0000, min_age=3},
     * {max_age=108, avg_age=63.0000, min_age=18},
     * {max_age=30, avg_age=30.0000, min_age=30},
     * {max_age=30, avg_age=27.5000, min_age=25}
     * ]
     */
}
  • selectObjs

      根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值。

@Test
public void test19(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    List<Object> objects = userMapper.selectObjs(queryWrapper);
    System.out.println(objects);
    /**
     * 只返回了第一列id的值
     * [1, 2, 3, 4, 5, 6]
     */
}
  • selectCount
@Test
public void test20(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    /**
     * 使用selectCount方法不能知道查询的列名
     * 生成SQL:SELECT COUNT( 1 ) FROM user WHERE (age >= ?) 
     */
    queryWrapper.ge("age",25);
    Integer count = userMapper.selectCount(queryWrapper);
    System.out.println(count);
}
  • selectOne

      根据 entity 条件,查询一条记录,返回实体对象。注意使用此方法要确保你的查询只能返回一条数据或者没有数据,否则报错。

@Test
public void test21(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("name","老实成");
    User user = userMapper.selectOne(queryWrapper);
    System.out.println(user);
    /**
     * 如果返回结果有多条,抛异常
     * TooManyResultsException
     */
}

lambda条件构造器

      除了上面的QueryWrapper条件构造器,MP还提供了lambda条件构造器,它能防止我们写错column名称。

@Test
public void test22(){
    /**
     * 三种方式创建lambda条件构造器:
     * LambdaQueryWrapper<User> lambdaWrapper = new QueryWrapper<User>().lambda();
     * LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();不建议直接 new 该实例,使用 Wrappers.lambdaQuery(entity)
     * LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery();
     */
    LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery();
    lambdaWrapper.like(User::getName,"汪").ge(User::getAge,20);
    List<User> users = userMapper.selectList(lambdaWrapper);
    users.forEach(System.out::println);
}

      MP还提供了另外一只lambda条件构造器LambdaQueryChainWrapper,它允许我们链式构造where条件和调用查询方法。

@Test
public void test23(){
    List<User> users = new LambdaQueryChainWrapper<User>(userMapper)
            .like(User::getName, "成")
            .ge(User::getAge, 25).list();
    users.forEach(System.out::println);
}

      查看LambdaQueryChainWrapper源码不难发现其使用方法和上面分开写的形式没有本质区别。查看构造方法可知将外部baseMapper传递给了this.baseMapper,内部新建了LambdaQueryWrapper

public LambdaQueryChainWrapper(BaseMapper<T> baseMapper) {
    super();
    this.baseMapper = baseMapper;
    super.wrapperChildren = new LambdaQueryWrapper<>();
}

      LambdaQueryChainWrapper实现了ChainQuery<T>接口,此接口提供了诸如listone等方法。

/**
 * 获取集合
 *
 * @return 集合
 */
default List<T> list() {
	/**
	 * 等同于:return userMapper.selectList(lambdaWrapper);
	 */
    return getBaseMapper().selectList(getWrapper());
}

使用 Wrapper 自定义SQL

      仅仅依靠有限的条件构造器是无法满足我们的需求的,这时候需要我们自己写SQL语句进行查询。在使用了mybatis-plus之后, 自定义SQL的同时也想使用Wrapper的便利应该怎么办?在mybatis-plus版本3.0.7得到了完美解决 版本需要大于或等于3.0.7。

注解方式
public interface UserMapper extends BaseMapper<User> {
	/**
	 * SQL语句中不要加where
	 */
    @Select("select * from user ${ew.customSqlSegment}")
    List<User> selectAll(@Param(Constants.WRAPPER)Wrapper<User> wrapper);
}
XML方式
<select id="selectAll" resultType="user">
	select * from user ${ew.customSqlSegment}
</select>

分页查询

      上文中的所有查询都不是分页查询,实际编程中基本上是不适合的,需要进行分页查询。MP为我们提供了方便的分页插件。新建MyBatisPlusConfig类。

@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        return paginationInterceptor;
    }
}

      配置完分页插件即可使用分页查询了。BaseMapper提供了selectPage方法进行分页查询。它第一个参数传入一个Page对象,第二个参数为条件构造器。

@Test
public void test25(){
    LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery();
    Page<User> page = new Page<>(1,2);
    lambdaWrapper.ge(User::getAge,18);
    IPage<User> userIPage = userMapper.selectPage(page, lambdaWrapper);
    /**
     * 执行过程查询了两次数据库:
     * ①、查询满足条件的总记录数:SELECT COUNT(1) FROM user WHERE (age >= ?) 
     * ②、查询具体数据:SELECT id,deleted,create_time,name,update_time,manager_id,version,email,age FROM user WHERE (age >= ?) LIMIT ?,? 
     */
    System.out.println("总页数:"+userIPage.getPages());
    System.out.println("总记录数:"+userIPage.getTotal());
    List<User> users = userIPage.getRecords();
    users.forEach(System.out::println);
}

      根据测试我们发现一次分页查询会查询两次数据库,有些场景我们是不需要第一次查询总记录数的,例如下拉加载,我们不需要去构建前端的分页导航条。·Page·类的构造方法中有一个isSearchCount参数,默认为true,即查询总记录数,将其置为false即可。

Page<User> page = new Page<>(1,2,false);
自定义SQL分页

      大多数情况下我们需要为自己自定义SQL实现分页,Mapper中的写法如下所示(查询简单,只是演示)。其他使用方法和上文一样。

/**
 * 查询 : 根据age年龄查询用户列表,分页显示
 *
 * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位(你可以继承Page实现自己的分页对象)
 * @param age 年龄
 * @return 分页对象
 */
IPage<User> selectPageByAge(Page page, @Param("age") Integer age);
<select id="selectPageByAge" resultType="user" parameterType="int">
    select * from user where age > #{age}
</select>
@Test
public void test27(){
    LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery();
    Page<User> page = new Page<>(1,2);
    IPage<User> userIPage = userMapper.selectPageByAge(page, 25);
    System.out.println("总页数:"+userIPage.getPages());
    System.out.println("总记录数:"+userIPage.getTotal());
    List<User> users = userIPage.getRecords();
    users.forEach(System.out::println);
}

更新

根据id更新

@Test
public void test28(){
    User user = new User();
    /* 必须有id */
    user.setId(5L);
    user.setName("成老实");
    user.setAge(23);
    int rows = userMapper.updateById(user);
    System.out.println("影响记录数:"+rows);
}

使用UpdateWrapper

      使用UpdateWrapper构造where条件。

@Test
public void test29(){
    User user = new User();
    user.setId(5L);
    user.setName("成老实");
    user.setAge(23);
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    /**
     * 实体中不为null的变量会出现在set中
     * UPDATE user SET name=?, age=? WHERE (name = ? AND age = ?)
     */
    updateWrapper.eq("name","老实成").eq("age",30);
    int rows = userMapper.update(user,updateWrapper);
    System.out.println("影响记录数:"+rows);
}

      UpdateWrapper可以通过传入实体对象构造where条件,其中值为null的变量不会设置在where中。切记设置了实体对象就不要在设置诸如updateWrapper.eq("name","老实成").eq("age",30);的语句了,会重复出现。

@Test
public void test30(){
    User user = new User();
    user.setId(5L);
    user.setName("成老实");
    user.setAge(23);
    User whereUser = new User();
    whereUser.setName("老成实");
    whereUser.setAge(30);
    whereUser.setId(5L);
    /**
     * 传入的实体中不为null的变量会出现在where中
     * UPDATE user SET name=?, age=? WHERE id=? AND name=? AND age=? AND (name = ? AND age = ?) 
     */
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(whereUser);
    updateWrapper.eq("name","老实成").eq("age",30);
    int rows = userMapper.update(user,updateWrapper);
    System.out.println("影响记录数:"+rows);
}

使用UpdateWrapper的set方法

      有时候我们也许值需要更新少数的字段,可是前面两个方法中我们都是新建了一个实体对象去更新,太麻烦了,MP为方便我们更新少量字段提供了set方法更新。

@Test
public void test31(){
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    /**
     * UPDATE user SET age=? WHERE (name = ? AND age = ?) 
     */
    updateWrapper.eq("name","老实成").eq("age",30).set("age",23);
    int rows = userMapper.update(null,updateWrapper);
    System.out.println("影响记录数:"+rows);
}

使用LambdaUpdateWrapper

      与LambdaQueryWrapper一样,也存在LambdaUpdateWrapper

@Test
public void test32(){
    LambdaUpdateWrapper<User> lambdaUpdateWrapper = Wrappers.lambdaUpdate();
    lambdaUpdateWrapper.eq(User::getName,"老实成").eq(User::getAge,23).set(User::getAge,24);
    int rows = userMapper.update(null,lambdaUpdateWrapper);
    System.out.println("影响记录数:"+rows);
}

使用LambdaUpdateWrapper

@Test
public void test33(){
    boolean update = new LambdaUpdateChainWrapper<User>(userMapper).eq(User::getName, "老实成")
            .eq(User::getAge, 24).set(User::getAge, 20).update();
    System.out.println("更新是否成功:"+update);
}

删除

根据id删除

@Test
public void test34(){
    int rows = userMapper.deleteById(6L);
    System.out.println("影响记录数:"+rows);
}

@Test
public void test35(){
    int rows = userMapper.deleteBatchIds(Arrays.asList(1L,2L,3L));
    System.out.println("影响记录数:"+rows);
}

根据Map删除

@Test
public void test36(){
    Map<String,Object> map = new HashMap<>();
    map.put("name","隔壁老汪");
    map.put("age",26);
    int rows = userMapper.deleteByMap(map);
    System.out.println("影响记录数:"+rows);
}

使用Wrapper删除

@Test
public void test37(){
    LambdaQueryWrapper<User> lambdaQueryWrapper = Wrappers.lambdaQuery();
    lambdaQueryWrapper.eq(User::getName,"老成实");
    int rows = userMapper.delete(lambdaQueryWrapper);
    System.out.println("影响记录数:"+rows);
}

Active Record模式

      Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。MP中对AR模式提供了支持。
      为使用AR模式需要进行如下配置:

/**
 * 1、实体类需要继承Model类
 */
public class User extends Model<User>{
	...
}

/**
 * 2、Mapper需要继承BaseMapper(已经做了)
 */

小试牛刀

/**
 * insert
 */
public void test38(){
    User user = new User();
    user.setName("张三");
    user.setAge(23);
    user.setManagerId(1L);
    user.setEmail("[email protected]");
    user.setCreateTime(LocalDateTime.now());
    boolean success = user.insert();
    System.out.println(success);
}

/**
 * selectById
 */
 @Test
 public void test39(){
    User user = new User();
    user.setId(1L);
    User res = user.selectById();
    System.out.println(res);
}

/**
 * updateById
 */
@Test
public void test40(){
    User user = new User();
    user.setId(1201836662942973954L);
    user.setEmail("[email protected]");
    user.setAge(25);
    user.setManagerId(2L);
    user.setVersion(2);
    boolean success = user.updateById();
    System.out.println(success);
}

/**
 * deleteById
 * MP中删除不存在的数据属于成功
 */
@Test
public void test41(){
    User user = new User();
    user.setId(1201836662942973954L);
    boolean success = user.deleteById();
    System.out.println(success);
}

/**
 * insertOrUpdate
 */
@Test
public void test42(){
    User user = new User();
    /**
     * 设置ID进行update,否则进行insert(如果你设置的id查询不到对象也是insert)
     */
    user.setId(1201836662942973954L);
    user.setEmail("[email protected]");
    user.setAge(25);
    user.setManagerId(2L);
    user.setVersion(2);
    boolean success = user.insertOrUpdate();
    System.out.println(success);
}
原创文章 234 获赞 1294 访问量 23万+

猜你喜欢

转载自blog.csdn.net/qq_25343557/article/details/103215599