简介
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具包,它在 MyBatis 的基础上进行了一些优化和提升,旨在简化开发,提高开发效率,减少冗余代码
提升与特性
1.无侵入式增强
MyBatis-Plus 是在 MyBatis 基础上的增强,不需要修改现有 MyBatis 的配置和代码,最大程度地保持兼容性和灵活性。
2.增强的 CRUD 操作
MyBatis-Plus 提供了内置的通用 Mapper 和 Service,开发者不再需要编写常见的增删改查(CRUD)方法。通过继承 BaseMapper
接口,开发者可以直接调用现成的方法来进行数据库操作。
常见的 CRUD 方法:
insert()
updateById()
selectById()
deleteById()
selectList()
selectOne()
selectCount()
举个例子:
假设有一个 User
实体类,包含用户信息的字段。
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
Mapper
接口:MyBatis-Plus 提供了 BaseMapper
接口,只需让自己的 Mapper
接口继承 BaseMapper
,就能自动拥有常用的 CRUD 方法。
public interface UserMapper extends BaseMapper<User> {
// 你可以根据需要添加自定义的方法
}
- 插入操作:
@Autowired
private UserMapper userMapper;
public void insertUser() {
User user = new User();
user.setName("John");
user.setAge(30);
user.setEmail("john.doe@example.com");
// 插入用户
userMapper.insert(user);
}
注意: 插入时会自动返回 user
的主键值,如果数据库使用的是自增主键。
- 根据 ID 更新:
updateById()
方法用于根据id
更新某条记录。
public void updateUserById() {
User user = new User();
user.setId(1L); // 更新 ID 为 1 的用户
user.setAge(31); // 更新年龄为 31
// 根据 id 更新记录
userMapper.updateById(user);
}
- 根据 ID 查询:
selectById()
方法根据id
查询单条记录。
public void selectUserById() {
Long userId = 1L;
// 根据 id 查询用户
User user = userMapper.selectById(userId);
System.out.println(user);
}
- 查询所有记录:
selectList()
方法可以查询所有记录,当然也可以通过QueryWrapper
对查询条件进行过滤。
public void selectAllUsers() {
// 查询所有用户
List<User> users = userMapper.selectList(null); // 传 null 表示不加查询条件
users.forEach(System.out::println);
}
- 条件查询:使用
QueryWrapper
来构建查询条件。以下示例查询年龄大于 18 且名字为 “John” 的用户。
public void selectUsersByCondition() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18) // 年龄大于 18
.eq("name", "John"); // 名字为 "John"
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
- 删除操作:
deleteById()
方法根据 ID 删除记录。
public void deleteUserById() {
Long userId = 1L;
// 根据 ID 删除用户
userMapper.deleteById(userId);
}
- 批量插入:
insertBatchSomeColumn()
方法用于批量插入数据。注意,这个方法是 MyBatis-Plus 在 3.x 版本后才引入的,如果需要批量插入,可以使用该方法。
public void insertBatchUsers() {
List<User> users = new ArrayList<>();
users.add(new User(null, "Alice", 25, "alice@example.com"));
users.add(new User(null, "Bob", 28, "bob@example.com"));
// 批量插入用户
userMapper.insertBatchSomeColumn(users);
}
- 分页查询:MyBatis-Plus 提供了分页查询支持,通过
Page
对象来进行分页查询。
public void selectUsersByPage() {
// 当前页和每页条数
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
// 进行分页查询
Page<User> userPage = userMapper.selectPage(page, null);
// 获取分页结果
List<User> users = userPage.getRecords();
users.forEach(System.out::println);
// 获取分页信息
long total = userPage.getTotal(); // 总记录数
System.out.println("Total users: " + total);
}
- 逻辑删除:MyBatis-Plus 支持逻辑删除,通过在实体类中使用
@TableLogic
注解,MyBatis-Plus 将自动进行逻辑删除,而不是物理删除。
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
@TableLogic
private Integer deleted; // 逻辑删除字段
}
使用 deleteById()
时,默认会根据 @TableLogic
注解标记的字段进行逻辑删除。
public void deleteUserLogic() {
Long userId = 1L;
// 执行逻辑删除
userMapper.deleteById(userId);
}
3.条件构造器(Wrapper)
MyBatis-Plus 提供了 QueryWrapper
和 UpdateWrapper
类,用于构建查询条件和更新条件。条件构造器支持链式调用,开发者可以更直观地构建查询和更新条件,减少了手动拼接 SQL 的复杂度。
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age", 25).lt("score", 100);
List<User> users = userMapper.selectList(wrapper);
QueryWrapper
是用于查询的条件构造器,支持多种条件查询,允许通过链式调用来添加各种查询条件。
- 构建查询方法
常用方法
eq()
:等于ne()
:不等于gt()
:大于lt()
:小于ge()
:大于等于le()
:小于等于like()
:模糊匹配between()
:区间查询in()
:集合查询isNull()
:为空orderByAsc()
/orderByDesc()
:排序groupBy()
:分组or()
:逻辑 OR 条件likeLeft()
:左模糊匹配likeRight()
:右模糊匹配
示例
假设我们有一个 User
实体类,包含 id
, name
, age
, 和 email
字段
// 实体类
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
查询年龄大于30且名字为”John”的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 30) // 年龄大于30
.eq("name", "John"); // 名字为"John"
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
模糊查询名字包含”Jo”且邮箱为特定值
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "Jo") // 名字包含"Jo"
.eq("email", "john.doe@example.com"); // 邮箱为特定值
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
查询年龄在25到30岁之间的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age", 25, 30); // 年龄在25到30之间
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
查询名字为”John”或”Jane”的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.in("name", "John", "Jane"); // 名字为"John"或"Jane"
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
查询年龄大于30,并按年龄升序排序
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 30) // 年龄大于30
.orderByAsc("age"); // 按年龄升序排序
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
- 构建更新条件
常用方法
set()
:设置更新的字段值eq()
:等于ne()
:不等于gt()
:大于lt()
:小于ge()
:大于等于le()
:小于等于like()
:模糊匹配isNull()
:为空in()
:集合查询
更新名字为”John”的用户的年龄为35
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("name", "John") // 条件:名字为"John"
.set("age", 35); // 更新:将年龄设置为35
// 执行更新
userMapper.update(null, updateWrapper); // 第一个参数为null表示不更新实体类中的数据
批量更新多个用户的邮箱
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.in("id", 1L, 2L, 3L) // 条件:id为1, 2, 3
.set("email", "new.email@example.com"); // 更新邮箱
// 执行更新
userMapper.update(null, updateWrapper);
将年龄大于30的用户的状态更新为”已成年”
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("age", 30) // 条件:年龄大于30
.set("status", "已成年"); // 更新:将状态设置为"已成年"
// 执行更新
userMapper.update(null, updateWrapper);
- 组合条件查询
在 MyBatis-Plus 中,QueryWrapper
和 UpdateWrapper
支持灵活的逻辑组合条件(如 AND、OR、NOT)。使用 and()
、or()
、nor()
等方法,可以实现更复杂的查询。
查询条件的 AND 组合
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John") // 名字为"John"
.and(wrapper -> wrapper.lt("age", 30).or().gt("age", 40)); // 年龄小于30或大于40
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
查询条件的 OR 组合
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John") // 名字为"John"
.or().eq("name", "Jane"); // 或者名字为"Jane"
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
4.分页查询
MyBatis-Plus 内置了分页插件,使得分页查询更加简单。通过配置分页插件,可以直接使用 Page
对象来进行分页查询。
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
Page<User> userPage = userMapper.selectPage(page, null);
5.自动填充功能
MyBatis-Plus 支持字段的自动填充,例如在插入或更新时自动填充某些字段的值(如创建时间、修改时间等)。通过实现 MetaObjectHandler
接口来配置自动填充逻辑。
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 插入时自动填充字段 "createTime",值为当前时间
this.setFieldValByName("createTime", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时自动填充字段 "updateTime",值为当前时间
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
自动填充的工作原理
- 当 MyBatis-Plus 执行插入(
insert()
)或更新(update()
)操作时,会先检查实体类中是否有需要自动填充的字段。通常,这些字段是一些例如createTime
(创建时间)和updateTime
(更新时间)等字段。 - 在
insert()
操作时,insertFill()
方法会被调用,允许你在插入数据之前设置需要填充的字段(如设置createTime
字段为当前时间)。 - 在
update()
操作时,updateFill()
方法会被调用,允许你在更新数据时自动填充某些字段(如设置updateTime
字段为当前时间)。
以上面的代码为例:
insertFill(MetaObject metaObject)
方法metaObject
参数是 MyBatis-Plus 用来封装实体类的对象,它提供了对实体类字段的访问接口。通过setFieldValByName()
方法设置实体类中某个字段的值。setFieldValByName("createTime", new Date(), metaObject)
这行代码的作用是:通过反射机制,自动将实体类中createTime
字段的值设置为当前的日期和时间(new Date()
)。
updateFill(MetaObject metaObject)
方法metaObject
参数同样用于封装实体类的对象,通过setFieldValByName()
方法来设置字段值。setFieldValByName("updateTime", new Date(), metaObject)
:通过反射机制,自动将实体类中updateTime
字段的值设置为当前的日期和时间(new Date()
)。
6.乐观锁插件
MyBatis-Plus 提供了乐观锁插件,通过在实体类中添加版本号字段(如 version
),并在数据库操作时自动校验版本号,从而实现对数据并发操作的控制。
@Version
private Integer version;
7.代码生成器
MyBatis-Plus 提供了强大的代码生成器,可以根据数据库表结构自动生成实体类、Mapper 接口、Mapper XML 文件、Service 类等,极大提高了开发效率,避免了重复造轮子。
8.自定义 SQL 支持
虽然 MyBatis-Plus 提供了很多通用的操作,但如果需要自定义复杂的 SQL 查询或操作,仍然可以通过 @Select
、@Update
等注解来编写 SQL,也可以直接使用 MyBatis 的原生方式。
9.性能分析
MyBatis-Plus 提供了 SQL 性能分析插件,可以在开发阶段输出 SQL 语句和执行时间,帮助开发者发现性能瓶颈,优化查询。
10.分库分表支持(Sharding)
MyBatis-Plus 提供了对分库分表的支持,方便进行水平拆分和数据库分布式管理。
分库分表 是数据库水平扩展(sharding)的一种常见技术,目的是通过将数据分散到多个数据库或表中,以提高数据库的性能、可扩展性和容错能力。在大型分布式系统中,单一数据库可能面临性能瓶颈或者数据量过大导致无法处理的情况,分库分表通过将数据切分成多个部分,分散到多个存储单元中,解决了这些问题。
11.补充
先看下面两段代码
User user = query().eq("phone", phone).one();
List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
虽然这两段代码调用了 query()
方法,但是 它们查询的是不同的数据库表,这主要是通过 query()
方法所在的服务类 来决定的。
query()
方法所在的服务类
在 MyBatis-Plus 中,query()
方法是 IService<T>
接口中的一个默认方法,它实际上是通过 BaseMapper
执行数据库查询操作。因此,query()
方法是基于当前实例的 BaseMapper
来执行查询的。不同的服务类会使用不同的实体类和 BaseMapper
,从而查询不同的数据库表。
User user = query().eq("phone", phone).one();
- 假设
query()
方法是UserService
类中的方法,这时UserService
会注入UserMapper
,而UserMapper
是与user
表进行交互的。 - 在
query()
方法调用时,BaseMapper
(这里是UserMapper
)会执行对应user
表的查询。
- 假设
List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
- 同样,假设这行代码是在
BlogService
类中调用的query()
,那么此时query()
返回的就是与Blog
实体相关联的BlogMapper
。 BlogMapper
会执行对blog
表的查询。
- 同样,假设这行代码是在
BaseMapper
和query()
方法的关联
每个实体类(如 User
、Blog
)通常会有一个对应的 Mapper,该 Mapper 是通过 BaseMapper<T>
进行扩展的,它提供了常用的 CRUD 操作。而 IService<T>
接口中的 query()
方法依赖于 BaseMapper
来执行实际的查询操作。
BaseMapper<T>
:提供了常用的数据库操作方法,例如selectList()
,selectOne()
,selectById()
等。query()
方法:返回一个QueryChainWrapper
,用于构建查询条件,最终会调用对应的BaseMapper
(如UserMapper
或BlogMapper
)执行查询。
- 如何判断查询哪个数据库?
MyBatis-Plus 在执行查询时是通过 BaseMapper
来决定的,具体如下:
UserService
类:- 如果
query()
方法在UserService
中被调用,那么UserService
的BaseMapper
就是UserMapper
,UserMapper
与user
表对应,查询将发生在user
表中。 - 这里的
query()
方法调用的是UserMapper
的方法。
- 如果
BlogService
类:- 如果
query()
方法在BlogService
中被调用,那么BlogService
的BaseMapper
就是BlogMapper
,BlogMapper
与blog
表对应,查询将发生在blog
表中。 - 这里的
query()
方法调用的是BlogMapper
的方法。
- 如果
每个服务类(如 UserService
、BlogService
)都具有自己的 BaseMapper
,而 BaseMapper
的 selectList()
、selectOne()
等方法会查询不同的数据库表。