环境准备
建库建表
#创建用户表
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, 'fucklife@qq.com', NULL, '2019-11-23 14:20:20'),
(2, '搞笑梁', 25, 'iamajoke@163.com', 1, '2019-10-01 11:12:22'),
(3, '美丽孙', 28, 'beauty@126.com', 1, '2019-09-28 08:31:16'),
(4, '隔壁老汪', 25, 'goodman@163.com', 6, '2019-10-01 09:15:15'),
(5, '老实成', 30, 'yesido@163.com', 6, '2019-01-01 09:48:16'),
(6, '程煦妧', 30, 'chengxu@163.com', 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
CRUD实战
插入
@Test
public void insertUser(){
User user = new User();
user.setId(6L);
user.setName("程旭辕");
user.setAge(20);
user.setEmail("code@163.com");
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("chengxu@163.com");
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), chengxu@163.com(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插件不会为静态变量创建getter
和setter
方法所以我们需要手动创建。
- 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中字段不全部出现
在上面的测试中不难发现全部字段都给查询出来了,可是很多情况下我们只需要部分字段。
- 名字中包含孙并且年龄小于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);
}
- 名字中包含孙并且年龄小于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<>#{%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=1234@qq.com, age=3},
* {deleted=0, create_time=2019-10-01 11:12:22.0, manager_id=1, name=搞笑梁, id=2, version=1, email=1234@163.com, age=108},
* {deleted=0, create_time=2019-09-28 08:31:16.0, manager_id=1, name=美丽孙, id=3, version=1, email=123565@qq.com, age=18},
* {deleted=0, create_time=2019-10-01 09:15:15.0, manager_id=6, name=隔壁老汪, id=4, version=1, email=afeafaf@163.com, age=25},
* {deleted=0, create_time=2019-01-01 09:48:16.0, manager_id=6, name=老实成, id=5, version=1, email=faefadsf@163.com, age=30},
* {deleted=0, create_time=2019-11-23 21:27:14.0, manager_id=2, name=程煦妧, id=6, version=1, email=chengxu@163.com, 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>
接口,此接口提供了诸如list
,one
等方法。
/**
* 获取集合
*
* @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("123@qq.com");
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("243@qq.com");
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("243@qq.com");
user.setAge(25);
user.setManagerId(2L);
user.setVersion(2);
boolean success = user.insertOrUpdate();
System.out.println(success);
}