一、基础了解
1.1、学前配置
- 创建一个实体类
public class User {
private int id;
private String username;
private String password;
private String address;
}
- 创建一个mapper
public interface UserMapper {}
- 启动类增加mapper扫描
- 引入依赖(可以用MybatisPlus的starter代替Mybatis的starter:
)
<!--MybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
- 让自己的mapper继承basemapper
public interface UserMapper extends BaseMapper<User> {}
1.2、常用注解
1.2.1、mybatisPlus工作原理
约定大于配置
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息。
- 实体类和表的映射关系
- 类名驼峰转下划线作为表名
uerInfo -> user_info
。 - 名为id的字段作为主键。
- 变量名驼峰转F划线作为表的字段名
userName - > user_name
- 类名驼峰转下划线作为表名
1.2.2、常用注解详解
@TableName
:用来指定表名。@Tableld
:用来指定表中的主键字段信息。value
:对应表内主键的名称type
:主键增长类型IdType.AUTO
:数据库自增长IdType.INPUT
:通过set方法自行输入IdType.ASSIGN_ID
:分配ID ,接口oldentifierGenerator
的方法nextld
来生成id,默认实现类为DefaultldentifierGenerator
雪花算法。
@TableField
:用来指定表中的普通字段信息。 使用场景如下- 成员变量名与数据库字段名不一致
- 成员变量名以 is 开头,且是布尔值。
- 成员变量名与教据库关健字冲突。@TableField(“
order
”)。 - 成员变量不是数据库字段。@TableField((exist=false)。
1.3、常用配置
MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。
mybatis-plus:
type-aliases-package: com.po #别名扫持包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mappe.xml文件地址,默认值
configuration:
map-underscore-to-camel-case: true 中是否开启下划线和轮峰的关射
cache-enabled: false #是否开启二级缓存
global-config:
db-config:
id-type: assign_id #id为雪花算法生成
update-strategy: not_null #更新策略: 只更新非空字段
其他配置官网可查
二、实战演练
2.1、条件构造器
2.1.1、详解
条件构造器的"族谱"
构造器举例
2.1.2、举例
1、Query
(1).查询出名字中带o的,存款大于等于1000元的人的id、 username、info、balance字段
sql语句
SELECT id ,username, info , balance
FROM user
WHERE username LIKE ? AND balance >= ?
代码
// 1.构建查询条件
QueryWrapper<User> wrapper=new QueryWrapper<User>()
.select("id", "username", "info", "balance"')
.like("username","o")
.ge("balance",1000);
// 2.查询
List<User> users = userMapper.selectlist(wrapper);
(2).更新用户名为jack的用户的余额为2000
sql语句
UPDATE user
SET balance = 2000
WHERE (username = "jack")
代码
// 1. 要更新的数据
new User();
user.setBalance(2000);
// 2.更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
// 3.执行更新
userMapper.update(user, wrapper);
2、Update
(1).更新id为1,2,4的用户的余额,扣200
sql语句
UPDATE user
SET balance = balance - 200
WHERE id in (1, 2, 4)
代码
List<Long> ids = List.of(1L, 2L,4L);
UpdateWrapper<User> wrapper = new UpdateWrapper<user>()
.setSql("balance = balance - 200")
.in("id", ids) ;
userMapper.update(null, wrapper);
3、Lambda
// 1.构建查询条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId,
User::getUsername,
User::getInfo,
User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
// 2.查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
4、总结
QueryWrapper
和LambdaQueryWrapper
通常用来构建select
、delete
、update
的where
条件部分。UpdateWrapper
和LambdaUpdateWrapper
通常只有在set
语句比较特殊才使用。- 尽量使用
LambdaQueryWrapper
和LambdaUpdateWrapper
,避免硬编码。
2.2、自定义Sql
我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
2.2.1、举例
1、将id在指定范围的用户(例如1. 2. 4 )的余额扣减指定值
- 原来的实现方式
- 自定义实现
可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。
- 基于wrapper构建where条件
ListcLong> ids = List.of(1L, 2L,4L);
int amount = 200 ;
// 1. 构建来件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);
// 2. 自定XSQL方法调用
userHapper .updateBalanceByIds (wrapper, amount);
- 在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew,可以用Constants.WRAPPER属性。
void updateBalanceByIds (@Param("ew") LambdaQueryWrapper<User> wrapper, @Param ("amount") int amount) ;
- 自定义SQL,并使用Wrapper条件
<update id="updateBalanceByIds">
UPDATE tb_user SET balance = balance - #{amount} ${ew.customsqtsegment}
</update>
2.3、Service接口
2.3.1、使用步骤
- 用自己的接口继承mp的接口
- 自己的实现类实现自己的接口并且继承mp的实现类。
2.4、Service实际案例
2.4.1、需求
基于Restful风格实现下面的接口:
2.4.2、配置
- pom
<!-- swagger-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- swagger的简单配置
knife4j:
enable: true
openapi :
title: 用户管理接口文档
description: "用户管理接口文档"
email:
concat: 作者
url: https://www.xxx.cn
version: V1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources :
- com.ai.www.controller
2.4.3、controller
@Api(tags="用户管理接口")
@RequestMapping("/users")
@RestController
@RequiredArgsConstructor//只会为需要注入的变量注入(加final或者@NonNull就是可以自动注入的)
public class UserController {
//@Autowired
//private IUserService userService;//spring不推荐
private final IUserService userService;
@ApiOperation("新增用户接口")
@PostMapping
public void saveUser (@RequestBody UserFormDTO userDTO){
// 1. 把DTO拷贝到PO
User user = Beanutil.copyProperties(userDT0, User.class) ;
// 2.新增
userService.save(user);
}
@ApiOperation("删除用户接口")
@DeleteMapping("{id}")
public void deleteUserById(@ApoParam("用户ID") @Pathvariable("id") Long id{
userService.removeById(user);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("{id}")
public UserVo queryUserById(@ApiParam("用户id") @PathVariable("id") Long id) {
// 1.查询用户PO
User user = userservice. getById(id);
// 2. 把PO拷贝到Vo
return BeanUtil.copyProperties(user, UserVo.class) ;
}
@ApiOperation("根据id批最查询用户接口")
@GetMapping
public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {
// 1.查沟用户PO
List<User> users = userService.listByIds(ids);
// 2. 把PO拷贝到Vo
return Beanutil.copyTolist(users, Uservo.class);
}
@ApiOperation("扣减用户余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deductMoneyById(@ApiParam("用户id")@Pathvariable("id") Long id,
@ApiParam("扣减的金额")@PathVariable("money") Integer money){
userService.removeById(id);
}
}
2.4.4、Mapper
代码
public interface UserMapper extends BaseMapper<user> {
List<User> queryUserByIds(@Param("ids") List<Long> ids);
void updateBalanceByIds(@Param(Constants.WRAPPER) {Querywrapper<User> wrapper, @Param("anount") int amount);
@Update("UPDATE tb_user SET balance = balance - #{money} WHERE id = #{id}")
void deductbalance (@Param("id") Long id, @Param("money")Integer money);
}
mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.mp.mapper.UserMapper">
<update id="updateBalanceByIds">
UPDATE tb_user SET balance = balance - #{amount} ${ew.customSqlSegment}
</update>
<select id="queryUserByIds" resultType="com.xxx.mp.donain.po.User">
SELECT *
FROM tb_user
<if test="ids != null">
WHERE id IN
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
LIMIT 10;
</select>
</mapper>
2.4.5、doman包
- UserFromDto
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户名")
private string username;
@ApiModelProperty("密码")
private string password;
@ApiModetProperty("注册手机号")
private String phone;
@ApiModelProperty("详细信息,JSON风格")
private string info;
@ApiModelProperty("账户余额")
private Integer balance;
}
- userPo
@Data
@TableName("tb_user")
public class pser {
//用户id
// @TableId(type.IdType.AUTO)
private Long id;
//用户名
// @TableField("username“")
private string username;
//密码
// @TableField(exist = false)
private string password;
//注册手机号
private string phone;
//详细信息
private string info;
//使用状态(1正常2陈结)
private Integer status;
//账户余额
private Integer balance;
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
- userVo
@Data
@ApiModel (description="用户Vo实体")
public class UserVo {
@ApiModelProperty("用户id"')
private Long id;
@ApiModetProperty("用户名")
private string user name;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常2陈结) ")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
2.4.6、Service
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
// 1. 查询用户
User user = this.getById(id);
// 2.校验用户状态
if (user == null || user.getstatus() == 2){
throw new RuntimeException("用户状态异常! ");
}
// 3. 校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足! ");
}
// 4.扣减余额update tb_ user set balance = balance -?
baseMapper.deductBlance(id, money);
}
2.5、Service的Lambda实际案例
2.5.1、查询案例
1、需求
需求:实现一个根据复杂条件查询用户的接口,查询条件如下:
- name:用户名关键字,可以为空。
- status:用户状态,可以为空。
- minBalance:最小余额,可以为空。
- maxBalance:最大余额,可以为空。
原版实现
2、代码
@Apioperat ion("根据复杂条件查询用户接口")
QGetMapping("/List")
public List<UserVo> queryusers(Userquery query){
// 1. 查淘用户PO
List<user> users = userService.queryUsers(query.getName(),query.getstatus(),query.getaxBalance(), query.getMaxBalance())
// 2. PO 拷贝到Wo
return Beanutil.copyTolist(users, Uservo.class);
- service
注意lambdaQuery()
可以换成Db.lambdaQuery
,当然可以直接导包
@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::igetstatus, status)
.gt(minBalance != null,User::getBalance,minBalance)
.lt(maxBalance != null, User::getBalance, maxBalance)
.list();//其他查询方法看编译器提醒就行
}
2.5.2、更新案例
1、需求
需求:改造根据id修改用户余额的接口,要求如下:
- 完成对用户状态校验
- 完成对用户余额校验
- 如果扣减后余额为0,则将用户status修改为冻结状态(2)
2、代码
@Transactional
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(1d);
// 2.校验用户状态
if (user == null | user.getstatus() == 2) {
throw new Runt imeExcept ion("用户状态异常! ");
}
// 3. 校验余额是否充足
if (user.getBalance() < money) {
throw new RuntimeExcept ion("用户余额不足! ");
}
// 4.加减余额 update tb_user set balonce = balance - 1
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();
2.5.3、批处理案例
连接时拼上参数
&rewriteBatchedStatements=true
三、拓展功能
3.1、代码生成
使用方式
新版在Tools,旧版在上面的Other
3.2、DB静态工具
import com.baomidou.mybatisplus.extension.toolkit.Db;
3.1、实例
需求:
- 改造根据id查询用户的接口,查询用户的同时,查询出用户对应的所有地址。
@Override
public Uservo queryUserAndAddressById(Long id) {
// 1.查询用户
User user = getById(4L) ;
if (user = null || user .getstatus() == 2) {
throw new Runt imeException("用户状态异常! ");
}
// 2.查询地址
List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId,id).1ist();
// 3.对Vo
// 3.1.转User的PO%为V0
UserVo userVo = BeanUtil.copyProperties(user, Uservo.class);
// 3.2.转地hv0
if (CollUtil.isNotEmpty(addresses)) {
uservo.setAddresses(BeanUtil.copyToList(addresses, AddressVo.class));
}
return uservo;
}
- 改造根据id批量查询用户的接口,查询用户的同时,查询出用户对应的所有地址。
// 1.查询用户
List<User> users = listByIds(ids);
if (Collutil.isEmpty(users)) {
return Collections.emptylist();
}
// 2.查询地址
// 2.1.获收用户id集合
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.tolist());
// 2.2. 根据用户id查询地址
List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId,userIds).list();
// 2.3. 转换地址VO
List<AddressVo> addressVoList = BeanUtil.copyTolist(addresses, AddressVo.class);
// 2.4.用户地址集合分出处理,相同用户的放入一个集合(组)中
Map<Long,List<AddressVo>> addressMap = new HashMap<>(0) ;
if(CollUtil.isNotEmpty(addressVoList )){
addressMap = addressVoList.stream().collect(Collectors.groupingBy(Addressvo::getUserId));
}
// 3.转换Vo返回
List<UserVo> list = new ArrayList<>(users.size());
for (User user : users) {
// 3.1.转换User MPO为VO
UserVO vo = BeanUtil.copyProperties(user,Uservo.class);
list.add(vo);
// 3.2.转换地hVO
vo.setAddresses(adlressMap.get(user.getId());
}
return list;
3.3、逻辑删除
逻辑删除就是基于代码逻辑模拟删除效果,但并不会真正删除数据。
3.3.1、实现思路
- 在表中添加一个字段标记数据是否被删除。
- 当删除数据时把标记置为1。
- 查询时,只查询标记为0的数据。
删除操作
查询操作
3.3.2、配置方式
MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可。
mybatis-plus:
globa1-config:
db-config:
logic-delete-field: flag #全局逻辑删除的实体字段名,字服类型可以是boolean, integer
logic-delete-value: 1 #逻辑已删除值(默认为1)
logic-not-delete-value: 0 #逻辑未删除值(默认为0)
3.3.3、注意
- 逻辑删除本身也有自己的问题,比如:
- 会导致数据库表垃圾数据越来越多, 影响查询效率。
- SQL中全都需要对逻辑删除字段做判断,影响查询效率。
- 因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
3.4、枚举处理器
3.4.1、使用方法
修改配置
mybatis-plus:
configuration:
default-enum-type-handter: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
枚举加注解
@Getter
public enum UserStatus {
NORMAL(1, "正常"),
FROZEN(2, "冻结"),
;
@EnumValue
@JsonValue()//枚举返回值,返回给前端value值
private final int value;
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
3.5、Json处理器
3.5.1、使用方式
@Data
@TableName ( value= "user", autoResultMap = true )
public class User {
private Long id;
private String username;
@TableField(typeHandler = JacksonTyeHandler.class)
private UserInfo info;
}
@Data
public class UserInfo {
private Integer age;
private String intro;
private String gender;
}
四、插件功能
MyBatisPlus提供的内置拦截器有下面这些:
4.1、分页插件
4.1.1、配置
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1.初始化核心播件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 2. 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
pageInterceptor.setMaxLimit(1000L); //设置分页上限
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
4.1.2、简单示例
@Test
void testPageQuery() {
// 1.查询
int pageNo = 1, pageSize = 5;
// 1.1.分页参数
Page<User> page = Page.of(pageNo, pageSize) ;
// 1.2. 排序参数,通orderItem来指定
page.addOrder(new OrderItem("balance", false));
// 1.3. 分页查询
Page<User> p = userService.page(page);
// 2.总来数
System.out.println("total = " + p.getTotal());
// 3.总页数
System.out.println("pages = " + p.getPages());
// 4.分页数据
List<User>records = p.getRecords();
records.forEach(System.out::println);
}
4.1.3、通用分页实体
1、请求实体
其他的查询继承此类即可
@Data
@ApiModel (description ="分页查削实体")
public class PageQuery{
@ApiModelProperty("页码")
private Integer pageNo;
@ApiModeProperty("页码")
private Integer pagesize;
@ApiModelProperty("排序字段")
private String sortBy;
@ApiModelProperty("是否升序")
private Boolean isAsc ;
public <T> Page<T> toMpPage(orderItem ... items){
// 1.分页条件
PagecT> 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;
}
}
2、返回值实体
@Data
@ApiModel (description = "分页结果")
public class PageDTO<T> {
QApiModelProperty("总条数")
private Integer total;
QApiModelProperty("总页数")
private Integer pages;
@ApiModetProperty("集合")
private List<T> list;
public static <Po,Vo>PageDTO<VO> of(Page<Po> p,Class<Vo> clazz){
PageDTO<VO> dto = new PageDT0<>();
// 1.总条数
dto.setTotal(p.getTotal());
// 2.总页数
dto.setPages(p.getPages());
// 3.当前页数
List<userPo> records = p.getRecords();
if (Collutil.isEmpty(records)) {
dto.setList(Collections.emptylist());
return dto;
}
// 4.拷贝V0
dto.setList(BeanUtil.copyTolist(records, clazz));
// 5.返回
return dto;
}
}
4.1.4、完整示例
遵循下面的接口规范,编写一个UserController接口, 实现User的分页查询。
省略其他一些东西,直接核心代码
service 代码
注意lambdaQuery()
可以换成Db.lambdaQuery
,当然可以直接导包
@override
public PageDTocUserV0> quer yUser sPage (UserQuery query) {
String name = query.getName();
Integer status = query.getstatus();
// 1. 构建分页条件
// 1.1.分页条件
Page<User> page = Page.of(query.getPageNo(), query.getPagesize());
// 1.2.那序条件
if(Strutil.isNotBlank(query.getSortBy())){
//不为空
page.addOrder(new orderItem(query.getSorBy(), query.getIsAsc());
}else{
//为空,默认按照更新时间排序
page.addOrder(new OrderItem("update_time", false));
// 2.分页查询
Page<User> p = lambdaQuery()
.like(nane != null, User::getUsername, nane)
.eq(status != null, User::gotStatus, status)
.page(page);
// 3.封装Vo结果
PageDTO<UserVo> dto = new PageDTO<>();
// 3.1.总条数
dto.setTotal(p.getTotal());
// 3.2.总页数
dto.setPages(p.getPoges());
// 3.3. 当前页数据
List<User> records = p.getRecords();
if(CollUtil.isEmpty(records)) {
dto.setList(Collections.emptylist();
return dto;
}
// 3.4.传userVO
dto.setList(Beanutil.copyTolist(records,UserVo.class));
// 4.返回
return dto;
}