mp简介
mybatis的优点
- 自己写sql语句,更灵活,可通过sql调优提升性能
- 业务逻辑与数据访问逻辑分离(sql语句与java代码分离),易于阅读、维护
- 可编写动态sql
mybatis的缺点
- 需要编写大量的sql语句,开发速度略慢
- 一些常用功能没有提供实现,例如分页(但mybatis支持第三方插件)
mp在MyBatis的基础上只做增强、不做改变,保留了mybatis的原有功能,并进行了大量扩展,简化了数据访问层的开发、提高了开发效率。
springboot整合mp
与springboot整合mybatis基本相同,不同的有2点
1、pom.xml中的mybatis依赖换成mp依赖
<!-- 已经包含了mybatis的依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
2、yml中的mybatis配置换成mp配置
mybatis-plus:
#指定xml映射文件位置,多个时逗号分隔
mapper-locations: classpath:mapper/**.xml
#配置实体类别名。不带s 不是复数,不能指定多个包,如果有多个包,写成父包
type-aliases-package: com.chy.mall.userserver.entity
configuration:
#下划线自动转驼峰
map-underscore-to-camel-case: true
#打印sql语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
BaseMapper
mp提供了通用的mapper接口,自带了一些通用的crud方法,自动提供了相应的方法实现。BaseMapper简化了dao层开发、提升了开发效率,一般都要继承。
mapper接口
/**
* 继承BaseMapper,指定实体类
*/
public interface UserMapper extends BaseMapper<User> {
}
可以在里面添加一些自定义的方法。
xml映射文件
- 如果在mapper接口没写自定义的方法,可以不要xml映射文件。
- 如果在mapper接口中写了自定义的方法,在xml映射文件中实现自定义的方法即可。
实体类
@Data
//默认将实体类类名全小写、单词之间下划线连接作为默认表名,eg. GoodsInfo => goods_info
@TableName("tb_user") //显示指定表名
public class User {
//如果数据表设置了主键自增,需要用@TableId指定主键字段、指定主键策略为数据库自增,不然调用BaseMapper的save()方法插入记录时会报错,插入时主键字段的值会回写到实体中
@TableId(type = IdType.AUTO) //标识主键、主键策略
private Integer id;
//默认将成员变量名全小写、单词之间下划线连接作为字段名,eg. googsName => goods_name
@TableField("username") //@TableId、@TableField都可以显式指定字段名
private String userName;
private String password;
private String tel;
//默认会映射所有实例成员变量(非静态成员变量),如果某些成员变量在数据表中没有对应字段,或者不想映射某些成员变量,可以将exist设置为false
@TableField(exist = false) //不映射此字段
private List<Order> orderList;
}
执行BaseMapper中的增改方法时,如果传入的实体中某些字段为空,则sql语句中不会包含该字段。
yml
mybatis-plus:
configuration:
#实体类成员变量camel、数据表字段下划线是否自动转换,mp默认true。mybatis也有此配置项,但默认false
# map-underscore-to-camel-case: true
global-config:
db-config:
#设置全局主键策略为数据库自增。设置之后主键字段只需加上@TableId,不用设置属性值
id-type: auto
#表名前缀,实体类映射表名时会自动加上前缀,eg. User => tb_user
# table-prefix: tb_
QueryWrapper
HashMap<String, Object> map = new HashMap<>(3);
map.put("username", "chy");
map.put("age", 20);
List<User> userList = userMapper.selectByMap(map);
selectByMap()可以查询多个条件,但只能是相等关系,相当于where username=“chy” and age=20,不太灵活。
QueryWrapper功能强大,支持where中多种条件、order、group by、having,查询条件可以连写,也可以分开写
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
// 或者 QueryWrapper<User> userQueryWrapper = Wrappers.query();
// 使用的是数据表的字段名,不是成员变量名
userQueryWrapper.like("username", "chy").ge("age", 20);
// 可以连写,也可以分开写
// userQueryWrapper.like("username", "chy");
// userQueryWrapper.gt("age", 20);
List<User> userList = userMapper.selectList(userQueryWrapper);
condition
//前端传递的查询参数
String username = "";
//StringUtils.isNotBlank(username)为true才会加like("username",username)
userQueryWrapper.like(StringUtils.isNotBlank(username), "username", username);
基本上加查询条件的方法都可以带condition参数,当条件为true才会加上条件。
and、or
//lambda表达式传入的参数是当前使用的wrapper
//相当于 where ... and (...),会把lambda中的条件作为一个整体
userQueryWrapper.gt("age", 20).and(queryWrapper -> {
queryWrapper.lt("age", 40);
});
//相当于 where ... or .... 一个or()连一个条件
userQueryWrapper.lt("age", 20).or().gt("age", 40);
//相当于 where ... and (...) 里面可以写多个条件,会作为一个整体
userQueryWrapper.lt("age", 20).or(queryWrapper -> {
queryWrapper.gt("age", 40);
});
可以同时使用多个and、or
nested
userQueryWrapper.nested(queryWrapper -> {
queryWrapper.gt("age", 20).eq("gender", 1);
});
nested是加(),把内容放在小括号中。
select
默认选取全部字段,可以指定选取的字段
//只选取指定字段
userQueryWrapper.select("username", "age");
//可以指定别名
userQueryWrapper.select("avg(age) avg_age", "min(age) min_age");
//只选取满足lambda表达式的字段。第一个参数指定实体类,lambda表达式传入的参数是单个字段,会遍历实体类的所有字段
//此处是选取除username、tel之外的所有字段
userQueryWrapper.select(User.class, field ->
!field.getColumn().equals("username") && field.getColumn().equals("tel")
);
实体作为wrapper的参数
User user = new User();
user.setUsername("chy");
user.setAge(20);
//传入实体
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(user);
//userQueryWrapper也可以拼接其它查询条件
会自动把实体类的非空属性添加到查询条件中,相当于where username=“chy” and age=20 …
默认使用的条件是相等,可以在实体类中指定默认条件
//使用SqlCondition中的常量指定
@TableField(condition = SqlCondition.LIKE)
private String username;
//如果SqlCondition中没有,可以自定义,%s表示当前字段名,#{%s}表示字段值
@TableField(condition = "%s<#{%s}")
private Integer Age;
不推荐这样做,不同的使用地方需求可能不一样,不要在实体类上加太多限制。
allEq
HashMap<String, Object> map = new HashMap<>();
map.put("username", "chy");
map.put("age", null);
// 默认true,会拼接map中值是null的字段,where username="chy" and age is null
// userQueryWrapper.allEq(map);
// 不拼接map中值是null的字段,where username="chy"
userQueryWrapper.allEq(map, false);
// 可以自定义规则,满足指定规则的项才拼接。此处是要求value不能是null、空串
// userQueryWrapper.allEq((key, value) -> value != null && !"".equals(value), map);
List<User> userList = userMapper.selectList(userQueryWrapper);
其它常用方法
//返回匹配的记录数
Integer count = userMapper.selectCount(userQueryWrapper);
//结果集中最多只有一条记录时可以用selectOne()
User user = userMapper.selectOne(userQueryWrapper);
//一条记录封装为一个map,常用于select选取了实体类中没有的字段时,比如select选取聚合函数
List<Map<String, Object>> mapList = userMapper.selectMaps(userQueryWrapper);
分页
//当前页码(从1开始),每页记录数
Page<User> userPage = new Page<>(1, 10);
//mp的分页会查2次,第一次是select count()只查满足匹配的记录总数,第二次才是查指定分页上的结果集
//默认true,不需要获取满足匹配的总记录数时,可以设置为false,以提高效率
Page<User> userPage = new Page<>(1, 10, false);
//分页结果
userPage = userMapper.selectPage(userPage, userQueryWrapper);
//满足匹配的总记录数
long total = userPage.getTotal();
//总页数
long pages = userPage.getPages();
//指定分页上的结果集
List<User> userList = userPage.getRecords();
生成的 countSql 会在 left join 的表不参与 where 条件的情况下,把 left join 优化掉,所以建议任何带有 left join 的sql都写成标准sql,即给表起别名,使用 表别名.字段名
更新
User user = new User();
user.setId(1);
user.setUsername("chy");
user.setTel("127xxxxxxxx");
//实体中需要设置主键字段的值,根据主键更新实体中不为空的字段 set username="chy",tel="127xxxxxxx" where id=1
int count = userMapper.updateById(user);
//实体类指定set更新的字段,不为空的字段都会出现在set中
User user = new User();
user.setUsername("chy");
user.setTel("127xxxxxxxx");
//UpdateWrapper指定where条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("id", 1);
//set username="chy",tel="127xxxxxx" where id=1
int count = userMapper.update(user, userUpdateWrapper);
//可以不要实体,直接在updateWrapper中设置set字段
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper.eq("id", 1).set("username","chy").set("tel","127xxxxxx");
//实体传入null
int count = userMapper.update(null, userUpdateWrapper);
删除
//传入主键字段的值即可
int count = userMapper.deleteById(1);
//根据id批量删除记录
List<Integer> idList = Arrays.asList(1, 2, 5);
int count = userMapper.deleteBatchIds(idList);
//map中设置where条件
HashMap<String, Object> map = new HashMap<>();
map.put("username", "chy");
map.put("age", 20);
//所有满足条件的记录都会被删除
int count = userMapper.deleteByMap(map);
//QueryWrapper中指定where条件
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username", "chy");
//所有满足条件的记录都会被删除
int count = userMapper.delete(userQueryWrapper);
总结
除了以上功能,mp还提供了代码生成器、乐观锁、多租户、逻辑删除、自动填充、动态表名等功能。
mp简化了dao层开发、提升了单表crud操作的开发速度,但存在一些问题
- 和jpa、hibernate一样,在实体类上做了很多处理,代码有些混乱,通用mapper提供的方法也有些冗杂。
- 在mybatis的基础上,做了大量的拦截处理,查询使用的QueryWrapper也偏重,包含大量的方法调用,性能不高。
- 只简化了单表的crud操作,有些鸡肋
如果追求开发速度,后续升级、扩展频率低,可以考虑使用mp、jpa、hibernate,复杂查询直接在xml中写sql语句,不要使用QueryWrapper。