目录
使用步骤
引入依赖
在XXXMapper接口里实现BaseMapper<>接口(找爸爸)
之后就可以在项目中直接使用XXXMapper内定义的crud方法了
使用mabatis-plus时常用的注解
常见配置
核心功能
条件构造器
查询数据
修改数据
条件构造器的用法:
使用Mybatis-plus时遵循分层架构原则的示例
Mapper层
Service层
controller层
Service接口
MP的Service接口使用流程
Lambda方法实现复杂条件的查询与修改(lambdaQuery 和 lambdaUpdate)
查询
修改
批量新增(rewriteBatchedStatements=true)
MybatisPlus插件
静态工具
Db
逻辑删除
注意
枚举处理器
Json处理器
分页查询插件
分页查询相关方法的封装
使用步骤
引入依赖
<!-- MybatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
MyBatisPlus官方提供了starter,其中集成了Mybatis和MybatisPlus的所有功能,并且实现了自动装配效果。因此我们可以用MybatisPlus的starter代替Mybatis的starter。
在XXXMapper接口里实现BaseMapper<>接口(找爸爸)
package com.itheima.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
void saveUser(User user);
void deleteUser(Long id);
void updateUser(User user);
User queryUserById(Long id);
List<User> queryUserByIds(@Param("ids") List<Long> ids);
}
- 泛型里的类名驼峰转下划线作为表名
- 名为id的字段作为主键(所以设计的数据库表一定要有id主键)
- 变量名驼峰转下划线作为表的字段名
之后就可以在项目中直接使用XXXMapper内定义的crud方法了
void testQueryByIds() {
List<User> users = userMapper.queryUserByIds(Arrays.asList(1L, 2L, 3L, 4L));
users.forEach(System.out::println);
}
使用mabatis-plus时常用的注解
注解配置 | MyBatis-Plus
MybatisPlus中比较常用的几个注解如下(这几个注解加到对应于数据库字段的实体类):
-
@TableName:用来指定表名
-
@TableId:用来指定表中的主键字段信息
-
IdType枚举:
-
AUTO:数据库自增长
-
INPUT:通过set方法自行输入
-
ASSIGN_ID:分配 ID,将由IdentifierGenerator的方法nextId来生成id,默认实现是为DefaultIdentifierGenerator雪花算法
-
-
-
@TableField:用来指定表中的普通字段信息
-
使用@TableField的常见场景:
-
成员变量名与数据库字段名不一致
-
成员变量名以is开头,且是布尔值
-
成员变量名与数据库关键字冲突
-
成员变量不是数据库字段
-
-
常见配置
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
mapper-locations: "classpath:/mapper/**/*.xml" # Mapper.xml 文件地址,默认值
configuration:
map-underscore-to-camel-case: true # 是否开启下划线转驼峰的映射
cache-enabled: false # 是否开启二级缓存
global-config:
db-config:
id-type: assign_id # id 为雪花算法生成,但是全局没有注解配置的级别高
update-strategy: not_null # 更新策略:只更新非空字段
以上配置除了type-aliases-package其他都是默认的,可以不用配置
使用配置 | MyBatis-Plus
核心功能
条件构造器
条件构造器 | MyBatis-Plus 中文
- 基本条件:
eq()
: 等于ne()
: 不等于gt()
: 大于lt()
: 小于ge()
: 大于等于le()
: 小于等于
- 模糊查询:
like()
: 包含某个字符串notLike()
: 不包含某个字符串
- 逻辑条件:
or()
: 或and()
: 与
- 集合查询:
in()
: 在某个集合中notIn()
: 不在某个集合中
查询数据
@Test
void testQueryWrapper() {
// 构造查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "info") // 指定要查询的字段
.like("username", "o") // 指定查询条件的字段要求
.like("info", "女"); // 指定查询条件的字段要求
// 查询,传入查询条件
List<User> users = userMapper.selectList(wrapper);
for(User user : users){
System.out.println(user);
}
}
// lambda查询
@Test
void testlambdaQueryWrapper() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getInfo)
.like(User::getUsername, "o")
.like(User::getInfo, "女");
// 查询
List<User> users = userMapper.selectList(wrapper);
for(User user : users){
System.out.println(user);
}
}
修改数据
@Test
void testUpdateWrapper() {
List<Long> ids = Arrays.asList(2L, 3L, 4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", ids);
userMapper.update(null,wrapper);
}
// 或者使用LambdaUpdateWrapper
@Test
void TestLambdaUpdateWrapper() {
List<Long> ids = Arrays.asList(2L, 3L, 4L);
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
.setSql("balance = balance - 200")
.in(User::getId, ids);
userMapper.update(null,wrapper);
}
条件构造器的用法:
- QueryWrapper和LambdaQueryWrapper 通常用来构建
select
、delete
、update
的where
条件部分。 - UpdateWrapper和LambdaUpdateWrapper 通常只在
set
语句(修改数据)中比较特殊才使用。 - 尽量使用 LambdaQueryWrapper 和 LambdaUpdateWrapper,避免直接写数据库里的字段名称。
使用Mybatis-plus时遵循分层架构原则的示例
Mapper层
-
提供基础的数据库操作方法。
-
不涉及业务逻辑。
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
Service层
-
构造查询条件。
-
调用 Mapper 层完成数据操作。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserByName(String name) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", name); // 按姓名查找
return userMapper.selectOne(queryWrapper); // 使用 selectOne 获取单个用户
}
}
controller层
-
负责接收 HTTP 请求和参数。
-
调用 Service 层。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserByName(String name) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", name); // 按姓名查找
return userMapper.selectOne(queryWrapper); // 使用 selectOne 获取单个用户
}
}
Service接口
MP的Service接口使用流程
-
自定义Service接口继承IService<>接口:
public interface IUserService extends IService<User> {}
-
自定义Service实现类,实现自定义接口并继承ServiceImpl<XXXMapper, XXX>类:
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { // 实现具体的方法 }
使用该接口
@SpringBootTest class IUserServiceTest { @Autowired private IUserService iUserService; @Test void UserServiceTest(){ List<Long> ids = Arrays.asList(2L, 3L, 4L); LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>() .setSql("balance = balance - 200") .in(User::getId, ids); iUserService.update(null,wrapper); } }
Lambda方法实现复杂条件的查询与修改(
lambdaQuery
和lambdaUpdate
)
由于是在service层的实现类里使用lambdaQuery
和 lambdaUpdate方法,所以下面的类和接口有这些关系
以下的sql语句可以通过service层的lambda语句来实现
查询
上面等价于
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername,name)
.eq(status != null, User::getStatus,status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
}
Lambda 查询的最后,可以根据查询的结果类型和需求选择不同的方法:
查询单个对象:.one()
查询多个对象:.list()
分页查询:.page(Page<T> page)
查询总数:count()
查询是否存在:.count()
方法结合 > 0
修改
@Override
@Transactional // 添加事务
public void DedMoneyById(Long id, Integer money) {
User user = getById(id);
if(user.getStatus() == null || user.getStatus() == 2){
throw new RuntimeException("账号状态异常");
}
if(user.getBalance() < money){
throw new RuntimeException("余额不足");
}
Integer rest = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, rest)
.set(rest == 0,User::getStatus, 2)
.eq(User::getId,id)
.eq(User::getBalance, user.getBalance()) // 乐观锁
.update(); // 最后加上update
}
乐观锁通过在更新时检查记录的当前状态(如余额)来确保只有在数据未被其他线程修改的情况下才会进行更新。
批量新增(rewriteBatchedStatements=true)
application.yml文件的url项最后加一个&rewriteBatchedStatements=true
启用此参数后,JDBC 驱动程序会将多个 SQL 语句批量重写为单个 SQL 语句,这样可以显著减少网络往返次数,从而提高性能,尤其是在执行大量插入或更新操作时。
MybatisPlus插件
下载完插件之后
在工具选项中点击Config Database来使用
连接上数据库之后点开工具里的 Code Generator选项卡
点击生成文件和代码之后,项目内就生成了对应的entity、service、controller、mapper等
静态工具
Db
@Override
public UserVO GetUsersAndAddressById(Long id) {
// 查询用户
User user = getById(id);
if(user == null || user.getStatus() == 2){
throw new RuntimeException("用户状态异常");
}
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
// 查询地址(查询另一张数据库表)
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
if(addresses == null){
return userVO;
}
userVO.setAddressVOList(BeanUtil.copyToList(addresses, AddressVO.class));
return userVO;
}
//涉及查询另一张数据库表——使用Db.lambdaQuery(XXX.class)
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
逻辑删除
逻辑删除是一种数据管理策略,用于标记数据为“已删除”而不是物理上从数据库中移除。这种方法通常在需要保持数据历史记录或避免数据丢失时使用。
yml文件中的配置
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名,字段类型可以是 boolean, integer
logic-delete-value: 1 # 逻辑删除的值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
注意
逻辑删除本身也有自己的问题,例如:
- 会导致数据库表垃圾数据越来越多,影响查询效率。
- SQL 中全部需要对逻辑删除字段做判断,影响查询效率。
因此,我不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的方法。
枚举处理器
yml中配置枚举处理器
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
定义枚举类:新建enums文件添加UserStatus枚举类
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FROZEN(2, "异常"),;
@EnumValue
private final int value;
@JsonValue // 加到谁身上就向前端返回什么数据
private final String desc; // 返回“正常”or“异常”
UserStatus(int value, String desc){
this.value = value;
this.desc = desc;
}
}
使用定义的枚举
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
// user.getStatus() == UserStatus.FROZEN 注意枚举这样比较
if(user == null || user.getStatus() == UserStatus.FROZEN){
throw new RuntimeException("用户状态异常");
}
return lambdaQuery()
.like(name != null, User::getUsername,name)
// 传入的Integer类型的status与数据库status字段对应的数值比较
.eq(status != null, User::getStatus,status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
}
Json处理器
为了将后端数据库里的Json字段返回前端时不展现为一个字符串(带有引号的转义字符:/"),
而是具体展现为一个Json数据,
第一步,将实体类那个Json数据(String类型)改为实体类的类型,所以先创建这个实体类,注意加这三个注解
@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
第二步、将对应于数据库的实体类加上@TableName注解,同时在嵌套的实体类字段上加上@TableField(typeHandler = JacksonTypeHandler.class)注解
@Data
@TableName(value = "user", autoResultMap = true)
public class User {
private Long id;
private String username;
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
}
第三步、返回给前端的VO类里改为实体类字段
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private UserStatus status;
}
分页查询插件
首先配置分页查询的插件,创建config目录下的MybatisPlusConfig配置类
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1. 创建分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 2. 设置最大限制
paginationInnerInterceptor.setMaxLimit(1000L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
@Test
void testPageQuery() {
// 查询第二页,每页两条
int pageNo = 2, pageSize = 2;
// 1. 准备分页条件
// 1.1. 分页条件
Page<User> page = Page.of(pageNo, pageSize);
// 1.2. 排序条件
page.addOrder(new OrderItem("balance",true));
page.addOrder(new OrderItem("id", true));
// 2. 分页查询
Page<User> p = iUserService.page(page);
// 3. 解析
long total = p.getTotal();// 总条数
System.out.println("total = " + total);
long pages = p.getPages();// 总页数
System.out.println("pages = " + pages);
List<User> users = p.getRecords();
users.forEach(System.out::println);
}
业务中具体实现分页查询操作
@Override
public PageDTO<UserVO> queryPageUsers(UserQuery query) {
// 分页的页码,每页的数量
Page<User> page = Page.of(query.getPageNo(), query.getPageSize());
// 1.2. 排序条件
if (page != null) {
// 不为空 指定排序的字段和规则
page.addOrder(new OrderItem(query.getSortBy(), query.getIsAsc()));
} else {
// 为空,默认为按更新时间排序
page.addOrder(new OrderItem("update_time", false));
}
// 2. 分页查询
Page<User> page1 = lambdaQuery()
.like(query.getName() != null, User::getUsername, query.getName())
.eq(query.getStatus() != null, User::getStatus, query.getStatus())
.page(page);
PageDTO<UserVO> pageDTO = new PageDTO<>(page1.getTotal(), page1.getPages(), BeanUtil.copyToList(page1.getRecords(), UserVO.class));
return pageDTO;
}
@Data
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
// 排序规则相关字段
private String sortBy;
private Boolean isAsc;
// 如果还要添加排序规则,那么可以传入多个OrderItem对象
public <T> Page<T> toMpPage(OrderItem ... orders){
// 1.分页条件
Page<T> p = Page.of(pageNo, pageSize);
// 2.排序条件
// 2.1.先看前端有没有传排序字段
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}
// 2.2.再看有没有手动指定排序字段
if(orders != null){
p.addOrder(orders);
}
return p;
}
// 传入一个规则就可以这样
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
// 以下是直接调用的默认方法
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
return toMpPage("update_time", false);
}
}
分页查询相关方法的封装
分页查询相关的实体类(如果某个实体类要做分页查询,那么这个实体类就继承PageQuery 这个类,对应于接收前端传来的数据格式)
@Data
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
// 排序规则相关字段
private String sortBy;
private Boolean isAsc;
// 如果还要添加排序规则,那么可以传入多个OrderItem对象
public <T> Page<T> toMpPage(OrderItem ... orders){
// 1.分页条件
Page<T> p = Page.of(pageNo, pageSize);
// 2.排序条件
// 2.1.先看前端有没有传排序字段
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}
// 2.2.再看有没有手动指定排序字段
if(orders != null){
p.addOrder(orders);
}
return p;
}
// 传入一个规则就可以这样
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
// 以下是直接调用的默认方法
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
return toMpPage("update_time", false);
}
}
要做分页查询的实体类要继承PageQuery
分页结果对应的实体类,分页的具体对象对应的类型要传入泛型。
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("分页结果")
public class PageDTO<V> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("列表")
private List<V> list;
/**
* 返回空分页结果
* @param p MybatisPlus的分页结果
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> empty(Page<P> p){
return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
}
/**
* 将MybatisPlus分页结果转为 VO分页结果
* @param p MybatisPlus的分页结果
* @param voClass 目标VO类型的字节码
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = BeanUtil.copyToList(records, voClass);
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
/**
* 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
* @param p MybatisPlus的分页结果
* @param convertor PO到VO的转换函数
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
}
@Override
public PageDTO<UserVO> queryPageUsers(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
// 1. 构建分页条件,默认传入前端的排序条件,同时按照更新顺序排列
Page<User> page = query.toMpPageDefaultSortByUpdateTimeDesc();
// 2. 分页查询
Page<User> p = lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.page(page);
// 3. 封装VO结果:Page<User> ---> PageDTO<UserVO>
return PageDTO.of(p, UserVO.class);
}