MyBatisPlus属于是MyBatis的拓展,不影响原MyBatis框架下的代码运行,并对MyBatis框架进行拓展及优化。
使用步骤:
注意:继承BaseMapper时要填写泛型为要操作的实体类。
基本原理:
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
并且满足下面三点:
·类名驼峰转下划线作为表名
·名为id的字段作为主键
·变量名驼峰转下划线作为表的字段
(createTime -> create_time)
常见注解:
注意:
@TableField注解标注在类的属性上还可以通过变量fill来实现规定方法的字段自动填充
例如:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("order_logistics")
public class OrderLogistics implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单id,与订单表一对一
*/
@TableId(value = "order_id", type = IdType.INPUT)
private Long orderId;
/**
* 物流单号
*/
private String logisticsNumber;
/**
* 物流公司名称
*/
private String logisticsCompany;
/**
* 收件人
*/
private String contact;
/**
* 收件人手机号码
*/
private String mobile;
/**
* 省
*/
private String province;
/**
* 市
*/
private String city;
/**
* 区
*/
private String town;
/**
* 街道
*/
private String street;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
然后编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。
/**
* 元数据对象处理器
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 执行insert语句的时候执行
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充——insertFill");
// 自动填充
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
// 获取session对象
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
/**
* 执行update语句的时候执行
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充——updateFill");
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}
实现创建时间在insert操作时自动填充,更新时间在insert和update操作时自动填充。
常见配置:
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:
注意:除去第一个别名扫描包,其他的配置在默认情况下就是这样配置的。
对MyBatis-Plus中的wrapper进行测试:
@Test
void testLambdaQueryWrapper1() {
//查询用户名中有“o”且余额为1000的用户的id、userName、info、balance
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
@Test
void testLambdaQueryWrapper2() {
//更新jack的余额为2000
User user = new User();
user.setBalance(2000);
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<User>()
.eq(User::getUsername, "jack");
userMapper.update(user, wrapper);
}
@Test
void testLambdaUpdateWrapper() {
//更新id为1,2,4的用户的余额,扣200
/*
update set balance = balance - 200 where id in (1,2,4)
*/
List<Long> ids = List.of(1L, 2L, 4L);
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<User>()
.setSql("balance = balance - 200")
.in(User::getId, ids);
userMapper.update(null, wrapper);
}
自定义SQL:
操作步骤:
Service接口:
使用流程:
实现下面接口:
代码:
Controller:
@RestController
@RequestMapping("/users")
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;
@ApiOperation("新增用户接口")
@PostMapping
public void save(@RequestBody UserFormDTO userDTO) {
User user = BeanUtil.copyProperties(userDTO, User.class);
userService.save(user);
}
@ApiOperation("删除用户接口")
@DeleteMapping("/{id}")
public void delete(@ApiParam("用户id") @PathVariable Long id) {
userService.removeById(id);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("/{id}")
public UserVO get(@ApiParam("用户id") @PathVariable Long id) {
User user = userService.getById(id);
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
return userVO;
}
@ApiOperation("根据id批量查询接口")
@GetMapping
public List<UserVO> list(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
List<User> users = userService.listByIds(ids);
List<UserVO> userVOS = BeanUtil.copyToList(users, UserVO.class);
return userVOS;
}
@ApiOperation("根据id扣减余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deductBalance(@ApiParam("用户id") @PathVariable Long id, @ApiParam("扣减金额") @PathVariable Integer money) {
userService.deductBalance(id, money);
}
}
Service:
public interface IUserService extends IService<User> {
void deductBalance(Long id, Integer money);
}
ServiceImpl:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(@Param("id") Long id, @Param("money") Integer money) {
//1.查询用户信息
User user = getById(id);
//2.判断用户状态
if(user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
//3.判断用户余额
if(user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
//4.扣除余额
baseMapper.deductBalance(id, money);
}
}
Mapper:
public interface UserMapper extends BaseMapper<User> {
void updateBalanceByIds(@Param("ew") LambdaUpdateWrapper<User> wrapper, @Param("amount") int amount);
@Update("update user set balance = balance - #{money} where id = #{id}")
void deductBalance(Long id, Integer money);
}
Lambda查询:
lambda查询功能很强大,除去下面例题使用的list,还有查询一个时使用的one,分页查询使用的page,统计使用的count。
代码演示:
Controller:
@ApiOperation("根据复杂条件查询用户接口")
@GetMapping("/list")
public List<UserVO> queryUsers(UserQuery query) {
//1.查询用户PO
List<User> users = userService.queryUsers(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance());
//2.将PO拷贝到VO
return BeanUtil.copyToList(users, UserVO.class);
}
Service:
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
ServiceImpl:
@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更新
代码演示:
Controller:
@ApiOperation("根据id扣减余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deductBalance(@ApiParam("用户id") @PathVariable Long id, @ApiParam("扣减金额") @PathVariable Integer money) {
userService.deductBalance(id, money);
}
Service:
void deductBalance(Long id, Integer money);
ServiceImpl:
@Override
public void deductBalance(@Param("id") Long id, @Param("money") Integer money) {
//1.查询用户信息
User user = getById(id);
//2.判断用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常!");
}
//3.判断用户余额
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足!");
}
//4.扣除余额
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance)
.set(remainBalance == 0, User::getStatus, 2)
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance())//乐观锁 先比较再更新
.update();
}
在业务层,如果两个线程同时查询同一个用户的信息,并且都扣除100余额,如果不进行加锁那么结果余额只会减少100,所以这里采用乐观锁,先比较数据库中的余额信息与查询到的用户余额信息是否一致,一致再更新数据。
批量新增:
批处理方案:
·普通for循环逐条插入,速度极差,不推荐
·MP的批量新增,基于预编译的批处理,性能不错
·配置jdbc的参数,设置rewriteBatchedStatements为true,性能最好
配置:
DB静态工具:
逻辑删除:
逻辑删除即删除数据时,通过修改一个特殊的字段值,来进行逻辑上的删除,而不是真正的删除数据,比如添加字段deleted,它为false即这条数据没被删除,它为true即这条数据被删除
问题:
枚举处理器:
实现PO类中枚举类型变量与数据库字段的转换:
User和UserVO:
@Data
public class User {
/**
* 用户id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 注册手机号
*/
private String phone;
/**
* 详细信息
*/
private String info;
/**
* 使用状态(1正常 2冻结)
*/
private UserStatus status;
/**
* 账户余额
*/
private Integer balance;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private UserStatus status;
@ApiModelProperty("账户余额")
private Integer balance;
@ApiModelProperty("用户的收货地址")
private List<AddressVO> addresses;
}
①枚举类:
public enum UserStatus {
NORMAL(1,"正常"),
FROZEN(2,"冻结"),
;
@EnumValue //枚举类与数据库进行转换字段
private final int value;
@JsonValue //设置返回值格式
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
②在配置文件中进行配置:
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
JSON处理器:
分页查询:
①先配置插件
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//创建分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(1000L);
//添加分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
②使用插件:
PageQuery:
@Data
@ApiModel(description = "分页查询实体")
public class PageQuery {
@ApiModelProperty("页码")
private Long pageNo = 1L;
@ApiModelProperty("页大小")
private Long pageSize = 5L;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc = true;
public <T> Page<T> toMpPage(OrderItem ... items) {
//1.分页条件
Page<T> page = Page.of(pageNo, pageSize);
//2.排序条件
if (StrUtil.isNotBlank(sortBy)) {
//不为空
page.addOrder(new OrderItem(sortBy, isAsc));
} else if (items != null) {
//为空,默认排序
page.addOrder(items);
}
return page;
}
public <T> Page<T> toMpPage(String defaultSortBy, Boolean defaultAsc) {
return toMpPage(new OrderItem(defaultSortBy, defaultAsc));
}
public <T> Page<T> toMpPageDefaultSortByCreateTime() {
return toMpPage(new OrderItem("create_time", false));
}
public <T> Page<T> toMpPageDefaultSortByUpdateTime() {
return toMpPage(new OrderItem("update_time", false));
}
}
pageDTO:
@Data
@ApiModel(description = "分页结果")
public class PageDTO<T> {
@ApiModelProperty("总条数")
private Long total;
@ApiModelProperty("总页数")
private Long pages;
@ApiModelProperty("集合")
private List<T> list;
public static <PO, VO> PageDTO<VO> of(Page<PO> p, Class<VO> clazz) {
PageDTO<VO> dto = new PageDTO<>();
//1.总条数
dto.setTotal(p.getTotal());
//2.总页数
dto.setPages(p.getPages());
//3.当前页数据
List<PO> records = p.getRecords();
if(CollUtil.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
//4.拷贝user的VO
dto.setList(BeanUtil.copyToList(records, clazz));
//5.返回
return dto;
}
public static <PO, VO> PageDTO<VO> of(Page<PO> p, Function<PO, VO> convertor) {
PageDTO<VO> dto = new PageDTO<>();
//1.总条数
dto.setTotal(p.getTotal());
//2.总页数
dto.setPages(p.getPages());
//3.当前页数据
List<PO> records = p.getRecords();
if(CollUtil.isEmpty(records)) {
dto.setList(Collections.emptyList());
return dto;
}
//4.拷贝user的VO
dto.setList(records.stream().map(convertor).collect(Collectors.toList()));
//5.返回
return dto;
}
}
Controller:
@ApiOperation("根据条件分页查询用户接口")
@GetMapping("/page")
public PageDTO<UserVO> queryUsersPage(UserQuery query) {
return userService.pageUsersPage(query);
}
Service:
PageDTO<UserVO> pageUsersPage(UserQuery query);
ServiceImpl:
@Override
public PageDTO<UserVO> pageUsersPage(UserQuery query) {
String name = query.getName();
Integer status = query.getStatus();
//1.构建分页条件
Page<User> page = query.toMpPageDefaultSortByUpdateTime();
//2.分页查询
Page<User> p = lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.page(page);
//3.封装VO结果
return PageDTO.of(p, UserVO.class);
}